Skip to content

feat!: UI 4 - #2505

Merged
stipsan merged 61 commits into
mainfrom
next
Aug 10, 2026
Merged

feat!: UI 4#2505
stipsan merged 61 commits into
mainfrom
next

Conversation

@stipsan

@stipsan stipsan commented Jul 30, 2026

Copy link
Copy Markdown
Member

Summary

The next branch is the integration branch for @sanity/ui 4.0. Merging this PR lands 4.0 on main; the Version Packages PR that follows publishes the stable @sanity/[email protected].

Prerelease mode has been exited, the Changesets baseBranch and the CI/release workflow triggers are restored to main-only, and the pending changesets are combined into a single 4.0 release note for @sanity/ui. The 4.x line was validated end-to-end as 4.0.0-next.* prereleases published from this branch under the next npm dist-tag.

@sanity/themer is excluded from this PR. Its published package stays exactly as main ships it (0.3.1): no changeset, no version bump, no peer/engines changes, and it is on the Changesets ignore list so the @sanity/ui major cannot auto-bump and republish it as a dependent. Only in-repo dev shims remain (subpath imports of the workspace @sanity/ui and a vanilla-extract vitest plugin), which the monorepo needs to build and test themer against the ui v4 source. A follow-up PR updates and releases themer against ui 4 and removes the ignore entry.

The 3.x line lives on: a copy of main is on the v3 branch, and #2551 sets up its maintenance config (release-v3 dist-tag, workflows and Changesets on v3, docs) — same pattern as v2/release-v2.

Validated by downstream consumers

The new major has been evaluated against the main consumers of @sanity/ui:

Migration guide & docs

  • MIGRATION.md (repo root) walks through the 3 → 4 upgrade as an ordered checklist, and points people staying on 3.x at the v3 branch / release-v3 dist-tag; the docs navbar links to it ("Migrate to v4"). The combined 4.0 release note links to it as well.
  • CONTRIBUTING.md and AGENTS.md describe the maintenance-branch layout (v3/release-v3, v2/release-v2) from the main side, mirroring how #2551 documents it from the v3 side.
  • The docs site (apps/docs) now renders from the v4 nav document (primaryNavId: 'v4'). Content fixes to the v4 nav happen in the Studio.
  • In the docs dataset, the stale "UI4" content release (staged against the legacy main nav in Oct 2025) is archived; a fresh empty "UI 4" release is active for staging v4 content updates.

Breaking changes

1. ESM-only, Node.js >=22.12 (#2510)

@sanity/ui no longer ships a CommonJS build. The require export condition and the .cjs / .d.cts outputs are gone, along with the legacy main / module / typesVersions fields. This is not a code change for consumers: modern Node.js supports require(esm), so require('@sanity/ui') keeps working on supported Node versions — things only break if the Node version is too old. The engines range on @sanity/ui is raised to >=22.12 to match sanity.

2. React 19.2+ required (#2507)

Peer ranges tighten from ^18 || >=19.0.0-0 to ^19.2 for both react and react-dom. The 19.2 floor is set by the new <Activity> component, which shipped in React 19.2.0 and which v4 uses for Tooltip/Popover mounting. The React Compiler now targets '19' and uses React's built-in runtime, so the react-compiler-runtime dependency is dropped.

3. Subpath entry points for heavy components (#2522)

Importing @sanity/ui no longer pulls in @floating-ui/react-dom, motion or react-refractor, regardless of bundler treeshaking. Components that need them moved to their own entry points, along with their prop, context and message types (PopoverProps, ToastParams, MenuItemProps, AutocompleteState, TooltipDelayGroupContextValue, …):

Entry point Exports
@sanity/ui/toast Toast, ToastProvider, useToast
@sanity/ui/popover Popover
@sanity/ui/tooltip Tooltip, TooltipDelayGroupProvider, useTooltipDelayGroup
@sanity/ui/menu Menu, MenuButton, MenuDivider, MenuGroup, MenuItem
@sanity/ui/autocomplete Autocomplete
@sanity/ui/breadcrumbs Breadcrumbs
@sanity/ui/code Code
-import {MenuButton, ToastProvider, useToast} from '@sanity/ui'
+import {MenuButton} from '@sanity/ui/menu'
+import {ToastProvider, useToast} from '@sanity/ui/toast'

The root entry keeps @deprecated never-typed tombstones for every moved symbol, so TypeScript points at the new location instead of erroring with "does not exist". ErrorBoundary renders a plain <pre><code> so the root entry stays free of the react-refractor module graph.

4. Static CSS moved to @sanity/ui/styles.css (#2523)

Styles that don't depend on the theme or props are extracted into a static stylesheet at build time instead of injected at runtime — in this release that covers SrOnly, Spinner and internal text-overflow styling. These components have moved off styled-components; more follow in future 4.x minors, and UI5 ships without styled-components or any other runtime CSS-in-JS. The stylesheet is not loaded automatically — add the import once, next to where the app renders <ThemeProvider>:

import "@sanity/ui/styles.css"

The stylesheet ships unminified for diffability (#2528) with a TypeScript declaration (#2536).

5. Deprecated props, hooks and components tombstoned (#2509, #2514)

Removed props stay on the public types as never, so TypeScript surfaces the migration message. Deprecated hooks and ConditionalWrapper keep callable exports that throw at runtime.

Removed Replacement
space gap
Grid columns / rows / column* / row* gridTemplateColumns / gridTemplateRows / gridColumn* / gridRow*
Menu focusFirst / focusLast shouldFocus
MenuButton top-level popover props popover={{…}}
Popover boundaryElement floatingBoundary / referenceBoundary (plus BoundaryElementProvider for max-width / constrainSize)
Tooltip allowedAutoPlacements fallbackPlacements
useClickOutside useClickOutsideEvent
useElementRect useElementSize
useForwardedRef useRef + useImperativeHandle
useArrayProp(value) Array.isArray(value) ? value : [value]

6. Private _visual-editing entry point removed (#2513)

@sanity/ui/_visual-editing is gone — import from @sanity/ui instead. The slim subset is unnecessary now that displayName side effects are gone and unused components drop out of consuming bundles.

7. Native ResizeObserver (#2518)

The @juggle/resize-observer polyfill dependency and the internal _ResizeObserver export are dropped. Use the global ResizeObserver.

Behavioural changes worth calling out

  • <Activity>-based mounting (#2508) — Tooltip and Popover keep content mounted with React's <Activity> when closed, preserving internal state and pre-rendering hidden content; with animate they use AnimateActivity to defer hiding until exit animations finish. Popovers with recursive content must gate the recursion on open, otherwise they render an infinitely deep hidden tree. Note for test suites: closed tooltips/popovers now exist in the DOM as hidden content, so e2e assertions must check visibility, not DOM presence.
  • No more forwardRef (#2519) — all components are plain function components taking ref as a regular prop. Refs behave the same, but components no longer pass react-is checks such as isForwardRef.

@sanity/themer

Not part of this release. The published package stays at main's 0.3.1 and is on the Changesets ignore list, so the ui major cannot auto-bump it. A follow-up PR adopts ui 4 in themer (peer ranges, engines, compiler target), releases it, and cleans up the abandoned 1.0.0-next.* prereleases on the next dist-tag.


⚠️ Merge checklist

Done:

  • Exit Changesets prerelease mode and delete pre.json (verified by dry run: changeset version produces @sanity/[email protected])
  • Restore main-oriented release config (Changesets baseBranch: "main", CI/release workflows trigger on main only)
  • Pending changesets combined into a single 4.0 release note for @sanity/ui (it links to MIGRATION.md)
  • @sanity/themer excluded from the release (no changeset, publish contract identical to main, Changesets ignore; verified by dry run that a version run leaves it untouched)
  • New major evaluated against downstream consumers (PRs linked in the summary)
  • 3 → 4 migration guide written (MIGRATION.md, linked from the docs navbar)
  • Docs site swapped to the v4 nav document (v4 nav content fixes tracked in the Studio)
  • v3 maintenance branch created from mainv3 exists, and this branch's docs point at it

Remaining before merging:

  • Merge #2551, which configures v3 releases (release-v3 dist-tag, workflows and Changesets baseBranch on v3, maintenance-branch docs) so 3.x hotfixes never touch latest

After merging, the Version Packages PR on main will contain the stable @sanity/[email protected] — merging it publishes to npm under the latest dist-tag.

Open in Web Open in Cursor 

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
icons Ready Ready Preview Aug 10, 2026 8:53am
sanity-ui-storybook Ready Ready Preview Aug 10, 2026 8:53am
ui-docs Ready Ready Preview Aug 10, 2026 8:53am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
sanity-ui-workshop Ignored Ignored Aug 10, 2026 8:53am

Request Review

@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 91b42ee

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@sanity/ui Major
@sanity/themer Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cursor cursor Bot changed the title chore: enter changesets prerelease mode on next feat!: UI 4 Jul 30, 2026
@squiggler-app squiggler-app Bot mentioned this pull request Jul 30, 2026
cursoragent and others added 2 commits August 7, 2026 17:11
The tombstone said popover={{boundaryElement}}, but PopoverProps.
boundaryElement is itself removed in v4 - the working replacements are
floatingBoundary/referenceBoundary. Also correct the _visual-editing
migration note: the moved components (Menu*, Popover) import from their
new entry points, not the root. Both spotted in review by @jordanl17.

Co-authored-by: Cody Olsen <[email protected]>
Dropping typesVersions means moduleResolution "node" (node10) cannot
resolve the subpath entry points' types - the root entry still resolves
via the kept top-level types field. Document that v4 needs node16,
nodenext or bundler. Raised in review by @jordanl17.

Co-authored-by: Cody Olsen <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 167 out of 169 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/ui/src/exports/autocomplete.ts:12

  • AutocompleteInputFoocusMsg looks like a public type name typo ("Foocus"). Since this new subpath entrypoint is a good chance to improve the API surface, consider exporting a correctly spelled alias (while keeping the existing name for compatibility).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 167 out of 169 changed files in this pull request and generated 1 comment.

Comment thread packages/ui/src/core/utils/srOnly/srOnly.tsx

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 166 out of 168 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/ui/src/exports/autocomplete.ts:12

  • The exported type name AutocompleteInputFoocusMsg contains a typo ("Foocus"). Since this file is the public @sanity/ui/autocomplete entry point, consider also exporting a correctly spelled alias (AutocompleteInputFocusMsg) to avoid spreading the typo in consumer code while keeping backwards compatibility.
    .changeset/ui-4.md:5
  • Typo in the changeset intro: "4.0." reads like an incomplete version. Consider using the full version string (e.g. 4.0.0) and keep punctuation outside the inline code span.
**`@sanity/ui` 4.0.** The full upgrade procedure is in the [migration guide](https://github.com/sanity-io/ui/blob/main/MIGRATION.md). TypeScript walks you through most of it: every removed or moved API stays in the types as a `@deprecated` tombstone whose message names its replacement.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 165 out of 167 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/ui/src/core/components/menu/menu.tsx:67

  • focusFirst, focusLast, and space are tombstoned in MenuProps, but they’re not destructured out of props. If a JS consumer (or TS via any) still passes them, they’ll end up in restProps and get forwarded to StyledMenu/the DOM, which can trigger React unknown-prop warnings. Destructure and ignore these props so they never reach the DOM.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 165 out of 167 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/ui/src/exports/autocomplete.ts:12

  • The exported message type AutocompleteInputFoocusMsg appears to have a spelling mistake in its identifier (Foocus). Since v4 is a major, consider introducing a correctly spelled AutocompleteInputFocusMsg and keeping AutocompleteInputFoocusMsg as a deprecated alias to reduce long-term API friction.
    packages/ui/tsdown.config.mts:29
  • ./styles.css dev export always points default at ./dist/styles.css, but packages/ui/dist is not present in the repo checkout until pnpm --filter @sanity/ui build is run. That makes import '@sanity/ui/styles.css' fail for workspace consumers during local dev/test runs that rely on dev exports (e.g. Storybook/Next/Vite). Consider making the non-publish default resolve to a TS stub (or a checked-in placeholder CSS file) so the side-effect import is always resolvable in-source.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 165 out of 167 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/ui/src/exports/autocomplete.ts:22

  • AutocompleteInputFoocusMsg has a spelling mistake in its public export name. Since this is a major release, consider also exporting a correctly spelled alias (AutocompleteInputFocusMsg) while keeping the existing name for compatibility.

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​sanity/​vanilla-extract-vite-plugin@​0.2.117410010096100
Addednpm/​@​vanilla-extract/​next-plugin@​2.5.21001009992100

View full report

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 164 out of 166 changed files in this pull request and generated no new comments.

Suppressed comments (1)

.changeset/ui-4.md:5

  • Release note heading has a stray trailing dot and reads as an incomplete version ("4.0."). This should be a stable version string for the major release note.
**`@sanity/ui` 4.0.** The full upgrade procedure is in the [migration guide](https://github.com/sanity-io/ui/blob/main/MIGRATION.md). TypeScript walks you through most of it: every removed or moved API stays in the types as a `@deprecated` tombstone whose message names its replacement.

@squiggler-app squiggler-app Bot mentioned this pull request Aug 10, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 164 out of 166 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants