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:
- Interact with specific overlays
- Toggle features
- Access contextual tools directly from the preview interface.
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
)
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.
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
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.
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,
},
},
}),
],
});
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 plainobjects
) 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.