Skip to main content
Everything is accessed through a single import:
import {
  EditorFocusScope,
  EditorFocusScopeProvider,
} from '@react-email/editor/ui';
Use EditorFocusScopeProvider to keep the editor’s focus and blur behavior in sync across registered surfaces, then wrap each portaled surface that should still count as being inside the editor UI with EditorFocusScope.
Inspector.Root already renders EditorFocusScopeProvider and wraps itself in EditorFocusScope by default, so the Inspector handles the editor’s focus idiomatically and reliably. Inside a custom inspector, you usually only add EditorFocusScope around extra portaled content.

EditorFocusScopeProvider

Wrap your editor UI with EditorFocusScopeProvider when you need focus-aware portals outside the built-in Inspector. The provider must be rendered inside the current editor context so it can access the active editor instance.

Example

import {
  EditorFocusScope,
  EditorFocusScopeProvider,
} from '@react-email/editor/ui';
import { EditorContent, EditorContext, useEditor } from '@tiptap/react';
import * as Popover from '@radix-ui/react-popover';

export function MyEditor() {
  const editor = useEditor({
    extensions,
    content,
  });

  return (
    <EditorContext.Provider value={{ editor }}>
      <EditorFocusScopeProvider clearSelectionOnBlur={false}>
        <EditorContent editor={editor} />

        <Popover.Root>
          <Popover.Trigger type="button">Theme presets</Popover.Trigger>

          <Popover.Portal>
            <EditorFocusScope>
              <Popover.Content sideOffset={8}>
                <button type="button">Apply preset</button>
              </Popover.Content>
            </EditorFocusScope>
          </Popover.Portal>
        </Popover.Root>
      </EditorFocusScopeProvider>
    </EditorContext.Provider>
  );
}
Use EditorFocusScope for each portaled surface that should still count as being inside the editor UI.

Props

children
ReactNode
The editor UI subtree that should share focus tracking.
clearSelectionOnBlur
boolean
default:"true"
When focus leaves every registered scope, clear the current selection as part of blur handling. Set this to false if you want to preserve the selection when the editor truly loses focus.

EditorFocusScope

Use EditorFocusScope around portaled UI like Radix Select.Content, Popover.Content, or dialog content so moving focus into that portal is still treated as staying inside the editor UI. EditorFocusScope must be used inside EditorFocusScopeProvider, which tracks registered focus scopes and updates the editor’s focus state for them.

Example

import { EditorFocusScope, Inspector } from '@react-email/editor/ui';
import * as Select from '@radix-ui/react-select';

<Inspector.Node>
  {({ getAttr, setAttr }) => (
    <Select.Root
      value={String(getAttr('alignment') ?? 'left')}
      onValueChange={(value) => setAttr('alignment', value)}
    >
      <Select.Trigger>
        <Select.Value />
      </Select.Trigger>

      <Select.Portal>
        <EditorFocusScope>
          <Select.Content>
            <Select.Viewport>
              <Select.Item value="left">
                <Select.ItemText>Left</Select.ItemText>
              </Select.Item>
              <Select.Item value="center">
                <Select.ItemText>Center</Select.ItemText>
              </Select.Item>
              <Select.Item value="right">
                <Select.ItemText>Right</Select.ItemText>
              </Select.Item>
            </Select.Viewport>
          </Select.Content>
        </EditorFocusScope>
      </Select.Portal>
    </Select.Root>
  )}
</Inspector.Node>
If you are building a focus-aware editor shell outside Inspector.Root, wrap the editor with EditorFocusScopeProvider and then use EditorFocusScope for each portaled surface.

Props

children
ReactNode
The element or subtree to register as an additional editor focus scope. Wrap the portaled container that receives focus.