> ## Documentation Index
> Fetch the complete documentation index at: https://react.email/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Inspector

> Add a contextual sidebar for document, node, and text editing.

The Inspector is a sidebar that provides a way to inspect and edit the email's properties. From the Inspector, you can edit document, node, and text properties such as colors, fonts, and padding.

View a minimal example of the Inspector at the [Standalone Editor Inspector](https://react.email/editor/examples/standalone-editor-inspector) demo page.

## Quick start

Add [EmailTheming](/editor/api-reference/theming-api) and render
`Inspector.Root` next to the editor content. The inspector switches
automatically between document, node, and text controls based on the current
selection.

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { StarterKit } from '@react-email/editor/extensions';
import { EmailTheming } from '@react-email/editor/plugins';
import { Inspector } from '@react-email/editor/ui';
import { EditorContent, EditorContext, useEditor } from '@tiptap/react';
import '@react-email/editor/themes/default.css';

const extensions = [StarterKit, EmailTheming];

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

  return (
    <EditorContext.Provider value={{ editor }}>
      <div className="flex min-h-0 overflow-hidden">
        <div className="flex-1 min-w-0 p-4">
          <EditorContent editor={editor} />
        </div>

        <Inspector.Root className="w-60 shrink-0 border-l p-4">
          <Inspector.Breadcrumb />
          <Inspector.Document />
          <Inspector.Node />
          <Inspector.Text />
        </Inspector.Root>
      </div>
    </EditorContext.Provider>
  );
}
```

Click the editor background to edit document-level styles. Click a node, such as a button, to edit its properties. Select text to switch to text controls.

<Tip>
  You can [customize the inspector](#customizing-the-inspector) with render props. The default panels can also be styled using [CSS variables and `data-re-*` selectors](/editor/features/styling#inspector).
</Tip>

## Using with EmailEditor

If you use the standalone `EmailEditor` component, pass the inspector as a child instead of setting up `EditorProvider` manually.

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { EmailEditor } from '@react-email/editor';
import { Inspector } from '@react-email/editor/ui';

export function MyEditor() {
  return (
    <div className="flex" style={{ height: '600px' }}>
      <EmailEditor
        content="<h1>Hello</h1><p>Click any element to inspect it.</p>"
        className="flex-1 min-w-0 overflow-y-auto p-4"
      >
        <Inspector.Root className="w-60 shrink-0 border-l p-4 overflow-y-auto">
          <Inspector.Breadcrumb />
          <Inspector.Document />
          <Inspector.Node />
          <Inspector.Text />
        </Inspector.Root>
      </EmailEditor>
    </div>
  );
}
```

`EmailEditor` includes `EmailTheming` in its default extensions, so the
[required extension](#required-extension) is already covered.

<Tip>
  Children of `EmailEditor` render inside the Tiptap `EditorProvider` as DOM
  siblings of the editor content area, so flex layouts work naturally.
</Tip>

## Required extension

`Inspector.Root` requires the [EmailTheming](/editor/api-reference/theming-api)
extension. Without it, the inspector cannot resolve theme defaults or apply
document-level style changes, and it will error.

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { StarterKit } from '@react-email/editor/extensions';
import { EmailTheming } from '@react-email/editor/plugins';

const extensions = [StarterKit, EmailTheming];
```

## Zero-config defaults

When you render the three default panels without children, the inspector gives
you a ready-made sidebar with sensible controls for common editing tasks using
the standard HTML elements like input and select.

| Component            | When it appears                                                     | Default behavior                                                                                                      |
| -------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `Inspector.Document` | When the editor is not focused on a specific node or text selection | Controls global theme-backed styles like page background and container settings                                       |
| `Inspector.Node`     | When a node is focused or selected                                  | Adapts to the current node type and shows sections like attributes, size, typography, padding, border, and background |
| `Inspector.Text`     | When text is selected                                               | Shows text formatting, alignment, typography, and link color controls                                                 |

This gives you a complete inspector with no custom UI code required.

## Breadcrumb navigation

Use `Inspector.Breadcrumb` to show the current path from the document root down
to the focused node, and let users jump back up the hierarchy.

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
<Inspector.Root>
  <Inspector.Breadcrumb>
    {(segments) =>
      segments.map((segment, index) => (
        <button key={index} type="button" onClick={() => segment.focus()}>
          {segment.node.nodeType}
        </button>
      ))
    }
  </Inspector.Breadcrumb>

  <Inspector.Document />
  <Inspector.Node />
  <Inspector.Text />
</Inspector.Root>
```

To use the default breadcrumb rendering:

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
<Inspector.Root>
  <Inspector.Breadcrumb/>
  <Inspector.Document />
  <Inspector.Node />
  <Inspector.Text />
</Inspector.Root>
```

## Customizing the Inspector

The Inspector.Root itself keeps track of what is meant to be inspected. This can either be the document, a node, or a text selection.

The `<Inspector.Document>`, `<Inspector.Node>`, and `<Inspector.Text>` components
are convenience components that only render when their respective target is active.

### Inspector.Document

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
<Inspector.Root>
  <Inspector.Document>
    {({ findStyleValue, setGlobalStyle, batchSetGlobalStyle }) => (
      <input
        type="color"
        value={findStyleValue('body', 'backgroundColor')}
        onChange={(e) =>
          setGlobalStyle('body', 'backgroundColor', e.target.value)
        }
      />
    )}
  </Inspector.Document>
</Inspector.Root>
```

<ResponseField name="findStyleValue" type="(classReference, property) => string | number">
  Looks up the current value for a document-level style property.
</ResponseField>

<ResponseField name="setGlobalStyle" type="(classReference, property, value) => void">
  Updates an existing style entry or adds a new one.
</ResponseField>

<ResponseField name="batchSetGlobalStyle" type="(changes) => void">
  Applies multiple document-level style updates in one call.
</ResponseField>

Internally, [EmailTheming](/editor/api-reference/theming-api) keeps track of the document-level styles through a
large JSON array with entries for each selector and style property.

### Inspector.Node

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
<Inspector.Root>
  <Inspector.Node>
    {({ 
      nodeType, 
      getStyle, 
      setStyle, 
      getAttr, 
      setAttr, 
      themeDefaults, 
      presetColors 
    }) => (
      <>
        <div>{nodeType}</div>
        <input
          type="text"
          value={String(getAttr('alt') ?? '')}
          onChange={(e) => setAttr('alt', e.target.value)}
        />
        <input
          type="color"
          value={String(getStyle('backgroundColor') ?? '')}
          onChange={(e) => setStyle('backgroundColor', e.target.value)}
        />
      </>
    )}
  </Inspector.Node>
</Inspector.Root>
```

<ResponseField name="nodeType" type="string">
  The currently focused node type, such as `image`, `button`, or `section`.
</ResponseField>

<ResponseField name="getStyle" type="(prop) => string | number | undefined">
  Reads a resolved style value from the active node.
</ResponseField>

<ResponseField name="setStyle" type="(prop, value) => void">
  Updates a single inline style on the active node.
</ResponseField>

<ResponseField name="batchSetStyle" type="(changes) => void">
  Applies multiple style updates in one call.
</ResponseField>

<ResponseField name="getAttr" type="(name) => unknown">
  Reads a node attribute such as `alt`, `href`, or `width`.
</ResponseField>

<ResponseField name="setAttr" type="(name, value) => void">
  Updates a node attribute.
</ResponseField>

<ResponseField name="themeDefaults" type="Record<string, string | number | undefined>">
  The resolved theme defaults for the current node before inline overrides.
</ResponseField>

<ResponseField name="presetColors" type="string[]">
  Colors collected from the current document that can be reused in custom controls.
</ResponseField>

<ResponseField name="nodePos" type="{ pos: number; inside: number }">
  The ProseMirror position metadata for the focused node.
</ResponseField>

### Inspector.Text

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
<Inspector.Root>
  <Inspector.Text>
    {({ 
      marks, 
      toggleMark, 
      alignment, 
      setAlignment, 
      linkHref, 
      linkColor, 
      setLinkColor, 
      isLinkActive, 
      getStyle, 
      setStyle, 
      presetColors 
    }) => (
      <>
        <button type="button" onClick={() => toggleMark('bold')}>
          {marks.bold ? 'Unbold' : 'Bold'}
        </button>
        <button type="button" onClick={() => setAlignment('center')}>
          {alignment === 'center' ? 'Centered' : 'Center'}
        </button>
      </>
    )}
  </Inspector.Text>
</Inspector.Root>
```

<ResponseField name="marks" type="Record<string, boolean>">
  A map of active text marks. These include `bold`, `italic`, `underline`, `strike`, and `code`.
</ResponseField>

<ResponseField name="toggleMark" type="(mark) => void">
  Toggles a text mark on the current selection.
</ResponseField>

<ResponseField name="alignment" type="string">
  The current alignment of the parent text block.
</ResponseField>

<ResponseField name="setAlignment" type="(value) => void">
  Updates the alignment of the parent text block.
</ResponseField>

<ResponseField name="linkHref" type="string">
  The current link URL when the selection is inside a link.
</ResponseField>

<ResponseField name="linkColor" type="string">
  The resolved color for the active link.
</ResponseField>

<ResponseField name="setLinkColor" type="(color) => void">
  Updates the color of the active link.
</ResponseField>

<ResponseField name="isLinkActive" type="boolean">
  Whether the current text selection is inside a link.
</ResponseField>

<ResponseField name="getStyle" type="(prop) => string | number | undefined">
  Reads a resolved style value from the parent text block.
</ResponseField>

<ResponseField name="setStyle" type="(prop, value) => void">
  Updates a style on the parent text block.
</ResponseField>

<ResponseField name="presetColors" type="string[]">
  Colors collected from the current document that can be reused in custom controls.
</ResponseField>

## Using portals without losing inspector focus

If your custom inspector uses portaled UI like Radix `Select`, `Popover`, or
dropdown content, moving focus into that portal can make the editor think it
blurred. That can switch the active inspector target or clear the current text
selection.

Wrap the portaled content with
[`EditorFocusScope`](/editor/api-reference/ui/editor-focus-scope) so focus
inside the portal is still treated as part of the editor UI.

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
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>
```

`Inspector.Root` already uses
[`EditorFocusScope`](/editor/api-reference/ui/editor-focus-scope) by default to
handle the editor's focus idiomatically and reliably. Inside a custom inspector
you usually only need to add `EditorFocusScope` around the portaled content.
The same pattern works for other portaled Radix components, not just `Select`.

## Reusing built-in sections

You can also mix custom layouts with the built-in section components.

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
<Inspector.Node>
  {(context) => (
    <>
      <Inspector.Size {...context} />
      <Inspector.Padding {...context} />
      <Inspector.Border {...context} />
    </>
  )}
</Inspector.Node>
```

Available section components:

| Component              | Description                                                                   |
| ---------------------- | ----------------------------------------------------------------------------- |
| `Inspector.Attributes` | Editable node attributes using field types inferred from the attribute schema |
| `Inspector.Background` | Background color control for nodes                                            |
| `Inspector.Border`     | Border width, style, color, and radius controls                               |
| `Inspector.Link`       | Link URL and link color controls for active text links                        |
| `Inspector.Padding`    | Four-sided padding editor                                                     |
| `Inspector.Size`       | Width and height controls                                                     |
| `Inspector.Typography` | Color, size, line height, marks, and alignment controls                       |

## Examples

See the inspector in action with runnable examples:

<CardGroup cols={2}>
  <Card title="Inspector — Defaults" icon="code" href="https://react.email/editor/examples/inspector-defaults">
    Zero-config document, node, and text inspectors.
  </Card>

  <Card title="Inspector — Composed" icon="code" href="https://react.email/editor/examples/inspector-composed">
    Showing how to use default sections and add a custom one.
  </Card>

  <Card title="Inspector — Fully Custom" icon="code" href="https://react.email/editor/examples/inspector-custom">
    Fully custom inspector UI built from render props.
  </Card>

  <Card title="Standalone Editor — Inspector" icon="code" href="https://react.email/editor/examples/standalone-editor-inspector">
    Inspector sidebar with the standalone EmailEditor component.
  </Card>
</CardGroup>
