Skip to content

Base UI + nova overhaul: ui-x catches up with modern shadcn/ui - #82

Merged
junwen-k merged 133 commits into
mainfrom
next
Jul 19, 2026
Merged

junwen-k merged 133 commits into
mainfrom
next

Conversation

@junwen-k

Copy link
Copy Markdown
Owner

Summary

Final ship PR for the ui-x overhaul tracked in ROADMAP.md. Opened as a draft so the full main...next diff can be reviewed in one place before merging — do not merge yet, this is for review purposes while a final component-by-component audit runs.

Highlights:

  • Base UI replaces Radix everywhere; zero @radix-ui/* packages remain.
  • Docs site rebuilt on fumadocs (from velite), Next 16 / React 19.2.
  • Single published style: nova (matches shadcn's current default preset).
  • 7 components superseded by shadcn's own additions (kbd, button-group, input-group, combobox, attachment, native-select, calendar) removed entirely.
  • apps/v3 (Tailwind v3 app) removed from the monorepo; the deployed v3-ui-x.junwen-k.dev build keeps serving independently.
  • API Reference + Accessibility sections added to all docs pages.
  • Canonical domain / SEO (metadataBase, OG, sitemap, robots) finalized.

See ROADMAP.md for the full history and decision log.

Test plan

  • Verified next on a Vercel preview: full docs smoke test, zero console errors
  • Final component-by-component audit against shadcn docs/principles (in progress)

🤖 Generated with Claude Code

junwen-k and others added 30 commits July 9, 2026 12:11
Tracks the effort to catch up with the modern shadcn/ui ecosystem:
docs refresh, registry modernization, styling refresh, and Base UI
support. Work merges into the long-running 'next' branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Tailwind v3 site is now frozen; a banner above the header points
visitors to the Tailwind v4 documentation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Installation now documents the registry-based install flow and
requirements instead of only deferring to shadcn/ui. Removes
year-old "New" labels from the sidebar config.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kbd, Control Group, Input Base and Combobox predate their shadcn/ui
equivalents. Each page now leads with a prominent callout recommending
the official version; the ui-x components remain provided as-is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs: documentation refresh (overhaul phase 1)
Add aria-invalid destructive ring support via has-[[data-slot][aria-invalid=true]],
port current Input/Textarea classes (file:inline-flex file:h-7, field-sizing-content,
min-w-0) and InputGroupAddon typography onto adornments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace calendar with shadcn/ui's current react-day-picker v9 implementation
(getDefaultClassNames, --cell-size sizing, CalendarDayButton, native dropdown
styling) and drop the Select registry dependency. Update date picker content
to the current popover idiom (transform origin, outline-hidden).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port current Command item/group classes (outline-hidden, svg sizing and muted
icon color, text-xs group headings), popover transform origin on content, and
the Spinner loading idiom. Drop stale focus ring from kbd (pointer-events-none
element) and add the current svg sizing rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopt the latest shadcn/ui focus ring and aria-invalid patterns on
DropzoneZone, and switch dropzone, file-list, and badge-group to
Icon-suffixed lucide imports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat: refresh component styling to match latest shadcn/ui
Supersedes the Radix + Base UI dual-support plan. Phase 4 becomes a
single migration: drop Radix entirely, port components to Base UI with
nova styling from the base-nova preset sources, and rebuild the docs
site on fumadocs (following shadcn's own stack, including Next 16).
Marks Phase 3 (new-york-v4 style refresh, PR #55) complete as the
final Radix-era snapshot and adds a keep/drop audit table for the
component migration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the Radix-based vendored ui components with CLI-installed
base-nova sources (Base UI primitives + nova style), update consumers
from asChild to render props, and rebuild registry payloads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
InputBase is now fluid (w-full) like shadcn's nova Input, so demos
constrain width at the example layer instead, matching shadcn's
preview approach. Also forward className from DatePickerInput to the
outer InputBase so width utilities size the visible input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vercel deploys fail on a FULL TURBO cache hit because the build tasks
declare no outputs, so turbo replays the logs without restoring the
.next directory ("The Next.js output directory .next was not found").
Declare .next (and v4's velite/registry artifacts) as task outputs so
cached runs restore them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(v4): migrate to Base UI primitives and nova style
Replace the velite content pipeline with fumadocs-core and fumadocs-mdx,
matching the infrastructure used by ui.shadcn.com:

- Move MDX content to content/docs with meta.json ordering; sidebar,
  pager and command menu are now driven by the fumadocs page tree
- Rebuild docs routes on source.generateParams/getPage with static
  generation, and add an /api/search route via createFromSource
- Port mdx-components, ComponentPreview/ComponentSource and
  CodeBlockCommand to RSC with rehype-pretty-code highlighting
- Delete velite.config.ts, the custom rehype pipeline and docs config
- Upgrade to Next 16 and rewrite the eslint config as native flat
  config; new react-hooks v6 rules downgraded to warn for now
- Wrap the command palette content in a Command root at the call site;
  the nova CommandDialog no longer provides one, which crashed the
  renderer when opening search

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the retained ui-x docs chrome with a faithful port of
shadcn/ui's own site UI on top of the fumadocs migration:

- Port shadcn's site header, main/mobile nav, command palette,
  docs sidebar, TOC rail and pager, keeping ui-x branding,
  version dropdown and GitHub links
- Port shadcn's mdx components and code pipeline: CodeBlockCommand
  with package-manager tabs, CodeTabs (CLI/Manual), collapsible
  component source, ComponentPreview with View Code gradient
- Adopt shadcn's typeset typography, code themes (vesper /
  github-light-default) and surface tokens
- Convert ported Radix idioms to Base UI (data-active, data-open,
  render props)
- Remove orphaned velite-era components (layout/*, toc, pager,
  code-block, underlined-tabs, nav config)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BProgress's Next.js integration page (bprogress/next.mdx) was being
flattened into the Components sidebar as its own top-level entry.
Add getOwnPagesFromFolder, which only surfaces a folder's index page
for nested sub-folders instead of recursing into them, matching how
shadcn/ui keeps framework-specific sub-pages (e.g. dark-mode/next)
out of the sidebar and reachable only via in-page linked cards.
Command palette search keeps using the recursive getPagesFromFolder
so these sub-pages remain findable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CommandInput (ui/command.tsx) already renders its own bordered
InputGroup with a search icon. The command menu's Command className
was additionally putting a border and background on the outer
command-input-wrapper div, producing two nested boxes that looked
like a stacked, duplicate input. Drop the wrapper override and size
the InputGroup itself instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pages already live under the "Primitives" sidebar section, so repeating
the word in every title (e.g. "Combobox Primitive") was redundant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(v4): migrate docs to fumadocs and adopt shadcn's site design
shadcn now supports installing directly from a public GitHub repo's
registry.json (no build/publish step). Add a root registry.json that
includes apps/v4/registry.json, drop the shadcn build step and the
committed apps/v4/public/r/*.json output (the docs site's live previews
never depended on it), update every doc's install snippet to
`npx shadcn@latest add junwen-k/ui-x/<item>`, and add a CI job that
runs `shadcn registry validate` against the pushed commit. Less code,
one source of truth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Root turbo.json only declares .next/** as the build task's output, so a
full cache hit restores .next but skips regenerating .contentlayer
(contentlayer2 build never re-runs). Next's file tracing then can't find
.contentlayer/generated/Doc/_index.json and the Vercel build errors out.
This has been failing intermittently on every v3-ui-x deploy that hits a
warm cache (fast ~1m builds in the deploy history, vs. ~5m on a real
rebuild). Add an apps/v3/turbo.json overriding the build task's outputs
to also include .contentlayer/**, so cache replays restore it too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat(registry): migrate to shadcn GitHub registry, drop build step
…ings

TabsContent lacked min-w-0, so its flex item's default content-based
min-width kept long unbreakable command blocks from shrinking inside
Tabs, overflowing the viewport on narrow install snippets.

Button instances rendered as Next.js Link now pass nativeButton={false}
per Base UI's custom-links guidance, since they no longer render a
native <button> element.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The docs tabs layout breakage was not a source bug — the dev server was
serving stale compiled CSS predating the shadcn/tailwind.css import, so
the data-horizontal custom variant never applied. A fresh CSS compile
resolves it; tabs.tsx now matches shadcn base-nova upstream exactly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
junwen-k and others added 11 commits July 18, 2026 21:12
No pointer to the frozen v3-ui-x.junwen-k.dev registry from the
changelog going forward — the clean-cut policy already means ui-x
isn't maintaining or promoting the old version, so there's nothing to
advertise. The old deployment keeps serving on its own for anyone who
already has the URL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs(changelog): drop the radix git-tag plan for a live registry pointer
Replace the orphaned static public/llms.txt with a dynamically
generated /llms.txt index and /llms-full.txt, plus a per-page raw
markdown export at /docs/<slug>.md, so the LLM docs can never drift
from the sidebar again. Since apps/v4 only depends on fumadocs-core/-mdx
(headless, no fumadocs-ui), the "Copy Page" / "Open in ChatGPT/Claude/
GitHub" control shadcn shows on each doc page is rebuilt from ui-x's
own Button/ButtonGroup/DropdownMenu and the existing copyToClipboard
util, rather than pulling in fumadocs-ui.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat(docs): add shadcn-style llms.txt support and page actions
Follow shadcn's own approach exactly: a "[llms.txt](/llms.txt)" link
entry in meta.json's pages array, which fumadocs-core's loader turns
into a page-tree node without a real content file. Flows through the
desktop sidebar, mobile nav, and command palette for free since they
all read from the same tree — no component changes needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs: surface llms.txt in the docs sidebar
…primitives

Primitive demos previously rendered with no visual styling, out of step
with the shadcn-look Components section. Style all 6 primitive demos
(date-picker, date-time-field, date-time-range-field, dropzone,
password-input, phone-input) to match their styled sibling components,
and add an "Unstyled" callout on each primitive doc page linking to the
ready-to-use styled version.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Styling the primitive demos to look like their shadcn siblings blurred
the distinction the callout itself makes ("this is unstyled, see the
styled version"). Revert all 6 demos back to plain unstyled markup;
dropzone previously had no example at all, so it keeps a bare
unstyled demo instead of none.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs(primitives): style demos with shadcn Tailwind and flag unstyled primitives
shadcn's own apps/v4 has no equivalent (checked their repo and live
site — neither /robots.txt nor /sitemap.xml resolve), so this follows
idiomatic Next.js MetadataRoute conventions instead. The sitemap walks
source.getPages() so it tracks the fumadocs page tree the same way
llms.txt does, and can't drift from the sidebar. Closes the last open
Phase 1 item in ROADMAP.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat(docs): add sitemap and robots routes for the v4 site
@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
ui-x Ready Ready Preview, Comment Jul 19, 2026 3:52pm

junwen-k and others added 2 commits July 19, 2026 02:26
Fixes surfaced by a full component/docs audit ahead of the next -> main
merge: broken installation links, leftover Tailwind styling in an
unstyled primitive demo, the virtualizer/virtualized naming mismatch
across demos and docs, a stale changelog section on the timeline page,
nova styling drift in dropzone and phone-input, a dead null-check in
useVirtualized(), missing Accessibility sections on confirmer and
wheel-picker docs, and a registry:validate script for local registry
checks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous commit picked up only the virtualizer->virtualized file
renames; the accompanying content edits (naming, docs, styling, and
the registry:validate script) were left unstaged by a lint-staged
stash/restore hiccup. This commit lands that content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
junwen-k and others added 8 commits July 19, 2026 15:39
The guard is unreachable in isolation (VirtualizedContext's default is
a real object, never null), but sortable.tsx's useSortable() has the
identical shape and every other "must be used within" hook in the
registry follows the same throw-guard pattern. Removing it only here
broke consistency rather than fixing a real bug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drop DropzoneGroup, DropzoneTitle, DropzoneDescription and
DropzoneUploadIcon in favor of shadcn/ui's Empty component. Dropzone now
covers only the interactive parts; the zone's icon, title and description
are composed with Empty/EmptyHeader/EmptyMedia/EmptyTitle/EmptyDescription.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Restore DropzoneUploadIcon as a thin, behavior-only wrapper. The icon
swap reads the primitive's drag context (DragAccepted/DragRejected/
DragDefault), so it's genuine Dropzone behavior rather than styling
Empty owns — the same principle DatePicker follows when it wraps
Calendar/DateField to bind primitive context. Title and description
stay composed with Empty directly.

Demos, docs and the changelog now render the icon via
<EmptyMedia variant="icon"><DropzoneUploadIcon /></EmptyMedia> instead
of inlining the primitive drag-state blocks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…orting

Drop the thin re-export wrappers that only renamed a data-slot without
adding behavior, styling, or a composed default — these are better
composed from the outside, matching how Date Picker already composes
InputGroup and Calendar directly.

- password-input: remove PasswordInputAdornment and the unused
  PasswordInputAdornmentButton; demos compose InputGroupAddon directly
- phone-input: remove PhoneInputCountrySelectTrigger; demos compose the
  styled SelectTrigger directly
- docs: update Usage, Accessibility and API Reference accordingly

Behavioral wrappers (PhoneInputCountrySelect/Value/Content, the toggle,
etc.) are unchanged — they wire context, styling or defaults and stay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capture the project philosophy and API-design rules as the default prompt
for agents and contributors: ui-x as a natural extension of shadcn (not a
fork), Base UI + base-nova only, the primitive/styled two-layer
architecture, and — front and center — the wrapping principle (wrap +
re-export only for behavior/styling/composed defaults; compose cosmetic
parts from the outside) that the component audit produced.

Also add .mcp.json so agents get live shadcn registry access.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Not every component is primitive + styled: document the three real
  shapes (styled composition, primitive+styled, library-adapter primitive).
- Lead with the library-first philosophy — lean on popular headless
  libraries (react-dropzone, react-phone-number-input, timescape, …) and
  only author behavior on Base UI when none fits.
- Reframe "Base UI only" as "Base UI, not Radix" (we do use domain libs).
- Trim verbosity and soften the rulebook tone into principles + a short
  hard-rules list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix: address audit findings ahead of next -> main merge
@junwen-k
junwen-k marked this pull request as ready for review July 19, 2026 13:05
junwen-k and others added 2 commits July 19, 2026 21:39
Vercel auto-detects pnpm 9 for this project (old project, lockfileVersion
9.0), against the repo's pnpm@10.34.5 pin. The version drift leaves the
pnpm store inconsistent across builds, which intermittently fails install
with an ENOENT on the content-addressable store index.

Override the install command to run corepack, which reads the version
from package.json's packageManager field — one source of truth, no
hardcoded version to keep in sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(ci): pin Vercel's pnpm to the packageManager field via corepack
@junwen-k
junwen-k merged commit 800d4f4 into main Jul 19, 2026
3 checks passed
@junwen-k
junwen-k deleted the next branch July 19, 2026 17:09
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.

1 participant