Sanity UI
Keep your custom studio elements consistent with built-in UI components.
When you're creating new tools and custom inputs, it's important for your editor experience to make sure your customizations match the overall design of the studio. To create this consistency, you can use the Sanity UI component library to create custom experiences without creating custom designs or adding custom CSS.
Usage of Sanity UI
The Sanity UI package comes bundled for most studio usage, but if you're creating a plugin or tool, you'll want to install the package via NPM.
npm install @sanity/uipnpm add @sanity/uiyarn add @sanity/uibun add @sanity/uiFrom there, you can import the various components into your custom inputs, tools, or widgets. For example, if you wish to apply a tooltip to a string input, you can create a custom input that uses the Stack, Box, and TextInput design primitives to create one with all the design elements of your studio built right in.
// /components/MyCustomStringInput.jsx
import React, {useCallback} from 'react'
import {Stack, Text, TextInput} from '@sanity/ui'
import {set, unset} from 'sanity'
export const MyCustomStringInput = (props) => {
const {elementProps, onChange, value = ''} = props
const handleChange = useCallback((event) => {
const nextValue = event.currentTarget.value
onChange(nextValue ? set(nextValue) : unset())
}, [onChange])
return (
<Stack gap={2}>
<TextInput
{...elementProps}
onChange={handleChange}
value={value}
/>
<Text>Characters: {value.length}</Text>
</Stack>
)
}See this guide on creating custom inputs and tools with Sanity UI.
Compatibility and versioning
Sanity UI follows semantic versioning, and that guarantee covers the package's documented API: component props, hooks, and refs. Breaking changes to those ship in a major release, and the removed API stays in the TypeScript types as a deprecation message naming its replacement.
The markup a component renders is not part of that contract. DOM structure and internal attributes such as data-ui and data-testid can change without a major version bump, so code that queries rendered elements or asserts on them can break on a routine upgrade. Depend on the props, hooks, and refs a component documents instead.
Closed tooltips and popovers stay in the DOM
From Sanity UI v4, Tooltip and Popover keep their content mounted while closed, using React's <Activity> component to hide it with display: none. A check for the presence of an element finds content that isn't visible.
In unit tests, assert on visibility rather than existence: expect(screen.getByText('Tooltip content')).not.toBeVisible() replaces expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument(). End-to-end assertions change the same way, from toHaveCount(0) to toBeHidden(). Queries that skip inaccessible elements, such as getByRole(), need no change.
To react to clicks outside a component, including one rendered in a portal, pass element refs to the useClickOutsideEvent hook rather than looking elements up with document.querySelector. The hook tracks the elements the components render, so it keeps working when the markup changes. It replaces useClickOutside, which v4 removed.
import {Box, Button, Text, useClickOutsideEvent} from '@sanity/ui'
import {Popover} from '@sanity/ui/popover'
import {useCallback, useRef, useState} from 'react'
export function MyPopoverButton() {
const [open, setOpen] = useState(false)
const buttonRef = useRef<HTMLButtonElement | null>(null)
const popoverRef = useRef<HTMLDivElement | null>(null)
const handleClickOutside = useCallback(() => setOpen(false), [])
// Pass refs to the rendered elements, including the portaled popover card.
// Passing `false` while closed disables the listener.
useClickOutsideEvent(open && handleClickOutside, () => [
buttonRef.current,
popoverRef.current,
])
return (
<Popover
content={
<Box padding={3}>
<Text>Popover content</Text>
</Box>
}
open={open}
portal
ref={popoverRef}
>
<Button onClick={() => setOpen((prev) => !prev)} ref={buttonRef} text="Toggle" />
</Popover>
)
}Full documentation and playground
Sanity UI comes with a full set of UI primitives that can be mixed, matched, and composed into many different design patterns. The full list of components can be found in the official Sanity UI documentation. To get a better feel for creating design patterns, you can also experiment with all the components in this interactive component playground.