Smaller bundles, subpath imports, and a required stylesheet

Published: August 10, 2026

v4.0.0

@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-components remains a peer dependency at ^5.2 || ^6.
  • TypeScript moduleResolution set to node16, nodenext, or bundler. v4 resolves all types through exports and no longer ships typesVersions, so the legacy node10 mode 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 to gap on Button, Hotkeys, Inline, Stack, Select, TextInput, Tree, TreeItem, Breadcrumbs, MenuGroup, and MenuItem. Flex and Grid never accepted space; they have always used gap.
  • Grid: columns and rows are replaced by gridTemplateColumns and gridTemplateRows. The column and row prop families are replaced by their gridColumn and gridRow equivalents.
  • Menu: focusFirst and focusLast are replaced by shouldFocus.
  • MenuButton: the top-level popover props are replaced by a single popover object prop.
  • Popover: boundaryElement is replaced by floatingBoundary and referenceBoundary. Use BoundaryElementProvider for constrainSize and max-width behavior.
  • Tooltip: allowedAutoPlacements is replaced by fallbackPlacements.
  • useClickOutside: replaced by useClickOutsideEvent, which takes a function returning its elements and returns nothing, instead of taking an array and returning a ref setter.
  • useElementRect: replaced by useElementSize, which returns {content, border} objects rather than a DOMRect. Callers reading rect.width move to size?.content.width.
  • useForwardedRef: removed. ref is 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:

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 content renders another Popover must gate the recursion on open. 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 :empty or 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

  • ErrorBoundary renders a plain <pre><code> element instead of the Code component, which keeps react-refractor out of the root dependency graph. Update styling that targeted the old markup.
  • The react-compiler-runtime dependency is dropped. Components are compiled with React Compiler targeting React 19's built-in runtime.
  • v3 is still maintained on the v3 branch. Fixes ship under the release-v3 npm dist-tag.

Migration guide

Follow the @sanity/ui v3 to v4 migration guide.

Loading...