Smaller bundles, subpath imports, and a required stylesheet
Published: August 10, 2026
@sanity/ui v4.0.0 is available. The theme of the release is bundle size: heavy components now live behind subpath entry points, unused components tree-shake out, and styles ship as a stylesheet you import yourself. Alongside that, a batch of long-deprecated props, hooks, and components are gone, and the package is ESM-only, requiring Node.js 22.12 or later and React 19.2 or later.
Follow the @sanity/ui v3 to v4 migration guide to upgrade.
ESM-only, with new Node.js, React, and TypeScript requirements
@sanity/ui v4 is now an ESM-only package. CommonJS consumers are no longer supported. Your project must run Node.js 22.12 or later and React 19.2 or later. If you are on an older version of either, upgrade those dependencies before installing v4.
v4 raises three requirements:
- Node.js 22.12 or later.
- React 19.2 or later.
styled-componentsremains a peer dependency at^5.2 || ^6. - TypeScript
moduleResolutionset tonode16,nodenext, orbundler. v4 resolves all types throughexportsand no longer shipstypesVersions, so the legacynode10mode cannot resolve the subpath types on TypeScript 5.x, and is removed outright in TypeScript 7.
A stylesheet import is now required
Styles are no longer injected automatically. Import @sanity/ui/styles.css in your application entry point, before any code that renders @sanity/ui components. Step 2 of the migration guide below shows the import.
This is what makes the bundle-size work possible. Components no longer carry displayName side effects, so the ones you don't use tree-shake out of your bundle, and SrOnly and Spinner no longer use styled-components at all. Without the stylesheet, those components render unstyled. More components move off runtime CSS-in-JS across the 4.x minors.
Heavy components moved to subpath entry points
Several heavier components have been moved out of the main package entry point and into dedicated subpath imports. This change significantly reduces bundle size for consumers who do not use these components. The affected components are now available at the following subpaths:
@sanity/ui/toast:Toast,ToastProvider,useToast@sanity/ui/popover:Popover@sanity/ui/tooltip:Tooltip,TooltipDelayGroupProvider,TooltipDelayGroupContext,useTooltipDelayGroup@sanity/ui/menu:Menu,MenuButton,MenuDivider,MenuGroup,MenuItem@sanity/ui/autocomplete:Autocomplete@sanity/ui/breadcrumbs:Breadcrumbs@sanity/ui/code:Code
The @sanity/ui/theme entry point is unchanged from v3 — theme APIs did not move.
Deprecated props, hooks, and components removed
Long-deprecated APIs are removed in v4, but they don't disappear cleanly. Removed props are typed never and ignored at runtime, so passing one produces the TypeScript error Type 'number' is not assignable to type 'undefined' rather than an unknown-prop error. Removed hooks and components are still exported and throw when called, for example Error: `ConditionalWrapper` was removed in @sanity/ui v4. Inline the conditional wrapping logic instead. Expect these messages rather than a missing-export error.
space: renamed togaponButton,Hotkeys,Inline,Stack,Select,TextInput,Tree,TreeItem,Breadcrumbs,MenuGroup, andMenuItem.FlexandGridnever acceptedspace; they have always usedgap.Grid:columnsandrowsare replaced bygridTemplateColumnsandgridTemplateRows. Thecolumnandrowprop families are replaced by theirgridColumnandgridRowequivalents.Menu:focusFirstandfocusLastare replaced byshouldFocus.MenuButton: the top-level popover props are replaced by a singlepopoverobject prop.Popover:boundaryElementis replaced byfloatingBoundaryandreferenceBoundary. UseBoundaryElementProviderforconstrainSizeand max-width behavior.Tooltip:allowedAutoPlacementsis replaced byfallbackPlacements.useClickOutside: replaced byuseClickOutsideEvent, which takes a function returning its elements and returns nothing, instead of taking an array and returning a ref setter.useElementRect: replaced byuseElementSize, which returns{content, border}objects rather than aDOMRect. Callers readingrect.widthmove tosize?.content.width.useForwardedRef: removed.refis a regular prop in React 19.useArrayProp: removed.ConditionalWrapper: removed. Inline the conditional wrapping logic instead.
The most common change is the space-to-gap rename:
// Before
<Stack space={3}>
<Text>Hello</Text>
<Text>World</Text>
</Stack>// After
<Stack gap={3}>
<Text>Hello</Text>
<Text>World</Text>
</Stack>Tooltip and Popover keep closed content mounted
Tooltip and Popover now use React's <Activity> component to keep closed content mounted in the DOM but hidden, which avoids repeated mount and unmount work for content that is expensive to render. Most code needs no changes, but two cases do:
- Recursive content: a Popover whose
contentrenders another Popover must gate the recursion onopen. Without that gate,<Activity>renders an infinitely deep hidden tree and the page hangs. - Tests and selectors: closed content is now present in the DOM. Tests that asserted a tooltip was absent, and CSS relying on
:emptyor descendant matching, need to check visibility instead of presence.
With animate enabled, hiding is deferred until the exit animation finishes.
All components are plain function components
With React 19 semantics, ref is a regular prop and all forwardRef wrappers are removed. Pass ref directly to any @sanity/ui component. One consequence: components no longer satisfy react-is checks such as isForwardRef, so code that branches on those checks needs updating.
The private _visual-editing entry point is removed
The @sanity/ui/_visual-editing entry point is removed. It was private and undocumented, and there is no public replacement. Importing it now fails with ERR_PACKAGE_PATH_NOT_EXPORTED and the message Package subpath './_visual-editing' is not defined by "exports". Remove any direct imports before upgrading.
The @juggle/resize-observer polyfill is removed
@juggle/resize-observer is no longer bundled, and _ResizeObserver is no longer exported. Modern browsers and Node.js 22 and later support ResizeObserver natively. If you target an environment that doesn't, install and import a polyfill yourself.
Additional changes
ErrorBoundaryrenders a plain<pre><code>element instead of theCodecomponent, which keepsreact-refractorout of the root dependency graph. Update styling that targeted the old markup.- The
react-compiler-runtimedependency is dropped. Components are compiled with React Compiler targeting React 19's built-in runtime. - v3 is still maintained on the
v3branch. Fixes ship under therelease-v3npm dist-tag.
Migration guide
Follow the @sanity/ui v3 to v4 migration guide.