Unlock seamless workflows and faster delivery with our latest releases- get the details
Experimental feature

Custom Presentation tool preview header

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

The Presentation tool also allows you to 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.

A simple menu appended to the preview header.

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

Protip

Remember to install Sanity UI as a dependency, if you haven't already:

npm install @sanity/ui

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 @sanity/visual-editing package provide useSharedState hooks. These hooks allow you to share state defined in the Presentation tool with custom overlay components in your front end. Both accepts two parameters: a unique string identifier as the key, and the state value itself.

Gotcha

Only JSON serializable state can be passed to useSharedState, this is data types like string, number, boolean, null, arrays, or plain objects.

Below is an example of 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, CloseIcon, EllipsisVerticalIcon} from '@sanity/icons'
import {useSharedState, type PreviewHeaderProps} from '@sanity/presentation'
import {Button, Menu, MenuButton, MenuItem} from '@sanity/ui'
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} space={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

Pass custom preview header component via components.unstable_header in the Presentation tool configuration object, as below:

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

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

Hooks reference

useSharedState

  • useSharedState(key, value): Your serializeable state

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

    Parameters

    • keystring

      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 (i.e., not dynamically generated) to ensure predictable behavior.

      Example: useSharedState('highlighting', true);

    • valueA serializeable 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 article helpful?