Visual Editing

Enable drag and drop for Visual Editing

Core concepts for drag-and-drop functionality in the Presentation Tool

Visual Editing offers page building capabilities that let content editors add, move, remove, and reorder content sections directly within their website's preview. Drag and drop lets content editors visually rearrange content in the context of their application or website. They can reorder array items with immediate visual feedback and dynamic zoomed-out overviews.

Loading...

Prerequisites

To implement page building features, you need:

  • Visual Editing configured and enabled, with up-to-date dependencies.
  • Content structured using arrays for reorderable sections.
  • Some understanding of Stega/Content Source Maps and how to enable overlays manually.
  • Studio on version 3.65.0 or above (npm install sanity@latest). Also requires @sanity/visual-editing 5.7.3 or later, which needs React 19.2 or later and @sanity/client 7.24.0 or later.

Browser/device support

Drag and drop is supported in the following browsers/versions:

  • Chrome ≥ 108
  • Safari ≥ 15.6
  • Firefox ≥ 115
  • Edge ≥ 126

Gotcha

Drag and drop building blocks

The Presentation Tool's drag-and-drop functionality is framework-agnostic and can be implemented without significant changes to your codebase. It uses Overlays for visual representation, and updates your structured content directly. It does not mutate or reorder the DOM.

In a Presentation Tool drag-and-drop sequence:

  • An Overlay element is dragged to a new position on the page.
  • The array order in the Presentation Tool is updated, reflecting the item’s new position.
  • Your front end receives the updated Sanity data and re-renders as normal.

Content modeling for page building

Drag and drop for page building, and similar layout systems, works with array-based content. Your schema (content model) should:

  • Use arrays to represent reorderable sections
  • Define content blocks as object types
// Example schema
defineField({
  name: 'sections',
  type: 'array',
  of: [
    defineArrayMember({ type: 'hero' }),
    defineArrayMember({ type: 'features' }),
    defineArrayMember({ type: 'callToAction' })
  ]
})

Protip

Enable drag and drop in your front-end application

To enable drag-and-drop functionality in your front end, you must:

  • Implement Visual Editing
  • Apply data attributes to the array items, and optionally the array parent if you want to enable click-to-edit for it
  • Make sure the array is rendering as a client-side component ('use client' with React Server Components-based frameworks)

Add data attributes to elements

Protip

To enable drag-and-drop functionality:

  • Add data-sanity attributes to the array elements
  • Include required information:
    • Document ID (_id)
    • Document type (_type)
    • Array item key (_key)
    • Path to array schema type (arrayName[_key=="SECTION_KEY"])

These attributes connect your UI elements to the underlying content structure.

You can use the createDataAttribute helper function to achieve this:

Implement optimistic updates

Load the array item data through the useOptimistic hook from the Visual Editing package (or framework-specific toolkit) to ensure that the user experience is fast and not slowed down by network latency.

The useOptimistic hook exposes ways of controlling the state and when to update the UI, which you typically want only when the array data has changed:

const sections = useOptimistic<PageSection[] | undefined, SanityDocument<PageData>>(
  initialSections,
  (currentSections, action) => {
    // The action contains updated document data from Sanity
    // when someone makes an edit in the Studio

    // If the edit was to a different document, ignore it
    if (action.id !== documentId) {
      return currentSections
    }

    // If there are sections in the updated document, use them
    if (action.document.sections) {
      return action.document.sections
    }

    // Otherwise keep the current sections
    return currentSections
  }
)

Protip

How the useOptimistic hook works

Typically, mutations created in your application need to be committed to Content Lake through the Presentation Tool, and content refetched before the UI can be updated.

Loading...
Mutation flow without useOptimistic

The useOptimistic hook uses a local document store to enable developers to opt-in to instant updates for specific content. UI can be updated with the anticipated result of a mutation, avoiding the delay required when submitting and refetching data from Content Lake.

useOptimistic detects when up-to-date content does eventually arrive and resets its internal state, ready to handle the next mutation.

Loading...
Mutation flow with useOptimistic

Reconcile references

Array reordering is an ideal use case for useOptimistic. However, when composing pages with reusable blocks, array items may contain references to other documents.

useOptimistic actions only provide an up-to-date snapshot of the mutated document, so you need to ensure that any references within the array item itself point to the correct documents in your original query result.

Typically, the optimistic ordering of an updated array can be used, with each item's content set using the data from the passthrough state value, if it exists.

const sections = useOptimistic(page.sections, (state, action) => {
  if (action.id === page._id && action.document.sections) {
    return action.document.sections.map(
      (section) => state?.find((s) => s._key === section?._key) || section
    );
  }
  return state;
});

You can find the useOptimistic reference documentation here.

Minimal example

The following example implements drag and drop in React:

Protip

The drag-and-drop-enabled sections can now be imported into a page route component:

Understand drag-and-drop behavior

Once an array child has a data-sanity attribute, drag and drop is enabled by default. This is reflected in the element’s Overlay label:

Loading...

Drag and drop is designed for a straightforward user experience and low-touch integration. To achieve this, it makes some assumptions:

  • The web page is using a left-to-right, top-to-bottom format with a logical content flow.
  • Drag groups can be broken into two categories: horizontal and vertical.

The Presentation Tool calculates the direction of a drag group based on the alignment of its children.

A drag group with children that share a y-axis is horizontal:

Loading...
Horizontal layout of array items that share a y-axis

A drag group with children that do not share a y-axis is vertical:

Loading...
Drag group of array items that do not share a y-axis

Minimap

When dragging an item that belongs to a group that is larger than the screen height, press the shift key while scrolling or dragging to enter minimap mode. This applies a three-dimensional transform to the page, focusing the group within the viewport. This makes it easier to move sections to slots outside of the immediate viewport:

Customize drag and drop

You can customize drag-and-drop behavior in the following ways:

Data attributes

Drag and drop’s default behavior can be customized using HTML data-attributes:

  • data-sanity-drag-disable: Disable drag and drop.
  • data-sanity-drag-flow=(horizontal|vertical): Override the default drag direction.
  • data-sanity-drag-group: Manually assign an element to a drag group. Useful when there are multiple elements representing the same data on a page.
  • data-sanity-drag-prevent-default: Prevent data from updating after drag sequences. Useful for defining custom insert behavior (see the "Custom events" section).
  • data-sanity-drag-minimap-disable: Disable the minimap for a specific element.

Custom events

Drag and drop emits a custom sanity/dragEnd event when an element is dropped.

sanity/dragEnd events can be used alongside the Presentation Tool's useDocuments functionality to override the default drag-and-drop mutation logic. This is useful for defining custom behavior for non left-to-right/top-to-bottom languages, or other bespoke use cases.

This example requires two additional packages: npm install @sanity/mutate @sanity/util. The following code provides a boilerplate for adding custom patching logic to drag-and-drop events:

Gotcha

Troubleshooting

Prevent Stega children from overriding array paths

Occasionally, a Stega-encoded string can override drag and drop on a parent array item. Here, the title string occupies the entire <button> element. The title automatically has an Overlay created for it, which prevents interaction with the parent Overlay:

// Your Sanity configuration
const config = {
  projectId: 'YOUR_PROJECT_ID',
  dataset: 'YOUR_DATASET',
  baseUrl: 'https://your-studio-url.sanity.studio',
}

<button
  data-sanity={createDataAttribute({
    ...config,
    id: parentDocument._id,
    type: parentDocument._type,
    path: `arrayItems[_key=="${arrayItem._key}"]`,
  }).toString()}
>
  {arrayItem.title}
</button>

To prevent this, use stegaClean:

import {stegaClean} from '@sanity/client/stega'

<button
  ...
>
  {stegaClean(arrayItem.title)}
</button>

Or add some visual padding to the array child to create space for the “draggable” area:

<button
  ...
  style={{padding: '1rem'}}
>
  {arrayItem.title}
</button>

Was this page helpful?