Visual Editing

Customize preview header and navigation

Learn to customize the preview header in Sanity Studio's Presentation tool, enabling users to interact with overlays and toggle features.

Experimental feature

The Presentation tool also lets you customize the preview header, giving you the flexibility to add controls, status indicators, or other UI elements that enhance the editor experience.

This is particularly useful for enabling users to:

Prerequisites

Before getting started, ensure the following:

  • Visual Editing enabled with up-to-date dependencies in your front end
  • Sanity Studio v3.65.0 or later (npm install sanity@latest)

Define a custom preview header component

First, create a custom header component to mount in the Presentation tool. In this case, you are creating a dropdown with a single toggle for enabling and disabling highlighting.

Loading...
A menu appended to the preview header.

You can use the renderDefault prop to keep existing functionality while appending a custom control. You use @sanity/ui to render the new elements.

Protip

Share state between overlays and the preview header

If you have multiple custom overlays, you may need to allow content editors the ability to toggle specific overlays on or off, or provide more fine-grained control over which overlay UI elements to render.

To do this, you need to share state between your Presentation tool in the studio and your front end in preview mode that is rendered in the tool's iframe.

Both the Presentation tool and the @sanity/visual-editing package provide useSharedState hooks. These hooks let you share state defined in the Presentation tool with custom overlay components in your front end. The Presentation tool hook takes two parameters: a unique string identifier as the key, and the state value itself. The @sanity/visual-editing hook takes only the key and returns the shared value.

Gotcha

This example shows a custom preview header that renders the out-of-box header UI (props.renderDefault(props)), with an additional new menu with a toggle for highlight components using Sanity UI:

// ./CustomPreviewHeader.tsx

import {CheckmarkIcon} from '@sanity/icons/Checkmark'
import {CloseIcon} from '@sanity/icons/Close'
import {EllipsisVerticalIcon} from '@sanity/icons/EllipsisVertical'
import {useSharedState, type PreviewHeaderProps} from 'sanity/presentation'
import {Button} from '@sanity/ui'
import {Menu, MenuButton, MenuItem} from '@sanity/ui/menu'
import {useState, type FunctionComponent} from 'react'

export const CustomPreviewHeader: FunctionComponent<PreviewHeaderProps> = (props) => {
  const [enabled, setEnabled] = useState(false)
  useSharedState('highlighting', enabled)

  // Render the default header component, and append a new control
  return (
    <>
      {props.renderDefault(props)}
      <MenuButton
        button={
          <Button fontSize={1} icon={EllipsisVerticalIcon} mode="bleed" padding={2} gap={2} />
        }
        id="custom-menu"
        menu={
          <Menu style={{maxWidth: 240}}>
            <MenuItem
              fontSize={1}
              icon={enabled ? CloseIcon : CheckmarkIcon}
              onClick={() => setEnabled((enabled) => !enabled)}
              padding={3}
              tone={enabled ? 'caution' : 'positive'}
              text={enabled ? 'Disable Highlighting' : 'Enable Highlighting'}
            />
          </Menu>
        }
        popover={{
          animate: true,
          constrainSize: true,
          placement: 'bottom',
          portal: true,
        }}
      />
    </>
  )
}

Now, you can access this state in a custom overlay component. Find instructions for how to do this in the custom overlay component documentation.

Mount a custom preview header component

Unstable features

Pass your custom preview header component using components.unstable_header in the Presentation tool configuration object:

// sanity.config.ts
import {defineConfig} from "sanity"
import {presentationTool} from "sanity/presentation"
import {CustomPreviewHeader} from "./CustomPreviewHeader"

export default defineConfig({
  // ...
  plugins: [
    presentationTool({
      // ...
      components: {
        unstable_header: {
          component: CustomPreviewHeader,
        },
      },
    }),
  ],
});

Mount a custom navigator component

Optionally, you can enhance the Presentation tool with a custom document navigator component to help users select different documents or views in the front-end UI.

Example:

Navigator properties

  • Requiredcomponent

    React component

    Specifies the navigator component to use with the Presentation tool. The component specified will be rendered as the content of the navigator panel.

  • minWidth

    number

    Sets the minimum width of the navigator component when it’s rendered in the UI. For the component to render, its value must be > 0 and other than null.

  • maxWidth

    number

    Sets the maximum width of the navigator component when it’s rendered in the UI. For the component to render, its value must be > 0 and other than null.

Hooks reference

useSharedState

  • useSharedState(key, value): Your serializeable state

    The useSharedState hook enables you to share state between the Presentation tool and your custom overlay components in your front end’s preview.

    Parameters

    • key

      string

      Acts as a unique identifier for the shared state within the context. This key is used to associate a specific state value with a logical “slot” in the shared state object.

      Best practice:

      • Use descriptive and unique keys to avoid conflicts between multiple shared states.
      • Keys should be stable (that is, not dynamically generated) to ensure predictable behavior.

      Example: useSharedState('highlighting', true);

    • value

      A serializable state

      Represents the state value associated with the given key. This value will be shared with other components that query the state using the same key.

      Requirements: Must be JSON serializable (string, number, boolean, null, arrays, or plain objects) to ensure compatibility with mechanisms like serialization, storage, or sharing across contexts.

      Best practices:

      • Ensure the value is minimal and only includes the necessary data.
      • Avoid passing complex or deeply nested structures to keep the shared state manageable.

Resources

Was this page helpful?