Overlays
Overlays is the part of Visual Editing that creates interactivity between elements in an application and the Sanity Studio. They enable click-to-edit, which takes you directly from content on your front end to the document where you can edit it. Overlays can be automatically enabled for most text-based content using Stega strings and manually added to your components with helper functions.
This article unpacks how overlays work, how to set them up manually, and how to troubleshoot styling.
Overlays are part of the Visual Editing component that you usually integrate from a framework toolkit. For overlays to work, they need to do two things:
- Identify what elements in your DOM (that is, the HTML in your front end) have content sourced from your Sanity Content Lake to enable the hover/focus effect
- Set up the correct mapping so that when you click the overlay, you are taken to the proper document and field in the Studio, where you can edit it.
Most CMSes with visual editing functionality solve this in two ways:
- They assume that a page sources content from just one document
- They require you to use their opinionated component system or require you to manually mark up every component with data attributes with the mapping to the CMS
We found these approaches too limiting and inflexible because:
- You might have content from multiple documents on a given page, especially when using GROQ and GraphQL
- You should be able to accommodate Visual Editing with any framework and component system
- A component might be used in several contexts and have different content sources, and it’s a lot of work to manually map them up
The Visual Editing tooling solves this by:
- Adding Content Source Maps (CSM) to query responses that map the content in a request to the documents and fields they’re sourced from
- Encoded CSM into Stega strings, that is, invisible characters that are added to text strings and read by the overlays package to generate the visual affordances and click-to-edit functionality
You can also use CSM and built-in functions to manually add overlays to elements with non-text-string content types.
Stega is an encoding method we developed alongside Vercel to provide an easy-to-implement mechanism for automatically embedding metadata mappings into an application. It uses Content Source Maps, a standard we developed for annotating fragments in a JSON document with metadata about their origin.
Protip
With Stega enabled, although data rendered in our application looks exactly the same, it contains invisible metadata that Sanity’s Visual Editing tooling can detect.
Stega encoding is usually enabled at the client or loader level, depending on your framework.
Stega encoding only works for text strings, including alternative text for images. In some cases, you want to manually map content in a component to its source.
The two manual methods involve inserting Sanity specific data attributes into our application:
createDataAttribute
takes a document ID, type, and field pathencodeDataAttribute
works with a loader and takes a field name
createDataAttribute
is a function that uses content metadata to create data-sanity
attributes.
At a minimum, you need to provide a document id
, a document type
, and the path
to the field the data originated from. Below is a straightforward and illustrative example of how we might create a React component that displays a title with a data-sanity
attribute.
import { createDataAttribute } from "@sanity/visual-editing";
export function TitleComponent(props: {
title: string;
documentId: string;
documentType: string;
fieldPath: string;
}) {
const { title, documentId, documentType, fieldPath } = props;
const attr = createDataAttribute({
id: documentId,
type: documentType,
path: fieldPath
});
return <h1 data-sanity={attr()}>{title}</h1>;
}
Note that the result of our createDataAttribute
call is a function. This is because this API also allows for incrementally scoping and reusing attributes. We can also provide further optional metadata, for example, to target a particular workspace. See the API reference for more details.
import { createDataAttribute } from "@sanity/visual-editing";
export function HeaderComponent(props: { document: SanityDocument }) {
const { document } = props;
// Create a data attribute scoped to the document and a custom workspace
const attr = createDataAttribute({
id: document._id,
type: document._type,
workspace: 'staging'
});
// Pass field names into the attr() function
return (
<div>
<h1 data-sanity={attr("title")}>{document.title}</h1>
<h2 data-sanity={attr("subtitle")}>{document.subtitle}</h2>
</div>
);
}
encodeDataAttribute
is a loader specific API, it can be thought of as a pre-scoped or context aware version of createDataAttribute
. Depending on the loader used, it is usually returned by a useQuery
hook or composable. As document context is provided by the loader itself, it only requires a field path to be explicitly defined.
Below is an example of how encodeDataAttribute
can create data attributes for multiple field values in a React client component.
"use client";
import { QueryResponseInitial, useQuery } from "@sanity/react-loader";
import { actorQuery, type ActorResult } from "./queries";
type Props = {
params: { slug: string };
initial: QueryResponseInitial<ActorResult>;
};
export default function MoviesByActor(props: Props) {
const { initial, params } = props;
const { data: actor, encodeDataAttribute } = useQuery<ActorResult>(
actorQuery,
params,
{ initial }
);
return (
<div>
{/* Create an attribute for the actor's name */}
<h1 data-sanity={encodeDataAttribute(["name"])}>
Movies featuring {actor.name}
</h1>
{actor.movies.map((movie, index) => (
<ul key={movie._id}>
{/* Use the index to scope to each item in the array of movies */}
<li data-sanity={encodeDataAttribute([index, "title"])}>
{movie.title}
</li>
</ul>
))}
</div>
);
}
Check the documentation specific to the loader you are using for more information about encodeDataAttribute
.
If using Vercel's visual editing, overlays will appear automatically when the Vercel Toolbar is visible – and the page contains stega encoding – such as in preview builds.
You may still wish to configure overlays, but only have them enabled when your front end is rendered inside an iframe, so they work within Presentation.