10 KiB
Wave 3 Component Reconciliation
Date: 2026-05-05
Date locked into proposal: 2026-05-05T06:54:49+01:00
Source: npx shadcn@latest search @shadcn --limit 1000 (registry version shadcn@4.6.0, captured 2026-05-05)
Live shadcn registry:ui count: 56 components
Summary
The live @shadcn registry exposes 56 registry:ui items. The proposal's provisional 8-batch table listed 63 components. Reconciling the two:
- Direct match (53 components): every item in batches 3a, 3b (minus
Fieldwhich existed but as separate entry), 3c, 3d (minusStatusBadge/Emptywhich need separate notes), 3e, 3f, 3g (minusConfirmDeleteDialog/ErrorBoundary/ThemeToggle— see notes), 3h (minusMultiSelect/SearchableSelect/MoneyInput/LanguagePicker). - Split / renamed in upstream:
Comboboxis a separate item in shadcn (good — confirms cross-cutting design §3.4 hint was wrong; we keepComboboxas a discrete component, NOT folded intoSelect).Fieldships asfield— confirms the form-primitives split. - Suaveplan-only additions (10 components):
StatusBadge,Empty(now exists upstream asempty),ConfirmDeleteDialog,ErrorBoundary,ThemeToggle,MultiSelect,SearchableSelect,MoneyInput,LanguagePicker,FieldGroup(shadcn shipsfield+field-groupseparately —field-groupexists, mapped to ourFieldGroup). - Upstream additions we should consider for future batches (5 components):
button-group,direction,kbd,native-select,spinner(already in 3d),form(legacy alias —react-formswill provide; we do NOT ship aFormcomponent inreact-ui).
Reconciled batch table
| Component | Proposal Batch | Live shadcn | Action | Notes |
|---|---|---|---|---|
| Button | 3a | exists (button) |
implement | direct shadcn parity |
| Input | 3a | exists (input) |
implement | direct shadcn parity |
| Label | 3a | exists (label) |
implement | wraps @radix-ui/react-label |
| Separator | 3a | exists (separator) |
implement | wraps @radix-ui/react-separator |
| Skeleton | 3a | exists (skeleton) |
implement | direct shadcn parity |
| FieldGroup | 3b | exists (field-group) |
implement in 3b | shadcn separates field + field-group |
| Field | 3b | exists (field) |
implement in 3b | direct match |
| FieldSet | 3b | renamed → component split | implement in 3b | shadcn folds into field (Field.Set); we keep distinct primitive |
| InputGroup | 3b | exists (input-group) |
implement in 3b | direct match |
| Textarea | 3b | exists (textarea) |
implement in 3b | direct match |
| Checkbox | 3b | exists (checkbox) |
implement in 3b | direct match |
| Switch | 3b | exists (switch) |
implement in 3b | direct match |
| RadioGroup | 3b | exists (radio-group) |
implement in 3b | direct match |
| Toggle | 3b | exists (toggle) |
implement in 3b | direct match |
| ToggleGroup | 3b | exists (toggle-group) |
implement in 3b | direct match |
| Dialog | 3c | exists (dialog) |
implement in 3c | direct match |
| Sheet | 3c | exists (sheet) |
implement in 3c | direct match |
| Drawer | 3c | exists (drawer) |
implement in 3c | wraps vaul |
| AlertDialog | 3c | exists (alert-dialog) |
implement in 3c | direct match |
| Popover | 3c | exists (popover) |
implement in 3c | direct match |
| HoverCard | 3c | exists (hover-card) |
implement in 3c | direct match |
| Tooltip | 3c | exists (tooltip) |
implement in 3c | direct match |
| ContextMenu | 3c | exists (context-menu) |
implement in 3c | direct match |
| Table | 3d | exists (table) |
implement in 3d | direct match |
| DataTable | 3d | NOT in @shadcn (consumer-built) | implement in 3d | suaveplan addition; shadcn docs ship as recipe, not registry item |
| Card | 3d | exists (card) |
implement in 3d | direct match |
| Badge | 3d | exists (badge) |
implement in 3d | direct match |
| StatusBadge | 3d | NOT in @shadcn | implement in 3d | suaveplan addition; Zod-validated state surface |
| Avatar | 3d | exists (avatar) |
implement in 3d | direct match |
| Alert | 3d | exists (alert) |
implement in 3d | direct match |
| Empty | 3d | exists (empty) |
implement in 3d | direct match — proposal listed this; confirmed live |
| Progress | 3d | exists (progress) |
implement in 3d | direct match |
| Spinner | 3d | exists (spinner) |
implement in 3d | direct match — proposal listed this; confirmed live |
| Sidebar | 3e | exists (sidebar) |
implement in 3e | direct match; per AGENTS.md §3 needs sub-components to stay <300 lines |
| NavigationMenu | 3e | exists (navigation-menu) |
implement in 3e | direct match |
| Breadcrumb | 3e | exists (breadcrumb) |
implement in 3e | direct match |
| Tabs | 3e | exists (tabs) |
implement in 3e | direct match |
| Pagination | 3e | exists (pagination) |
implement in 3e | direct match; chevron-swap under useDirection() === 'rtl' |
| Menubar | 3e | exists (menubar) |
implement in 3e | direct match |
| DropdownMenu | 3e | exists (dropdown-menu) |
implement in 3e | direct match |
| Command | 3e | exists (command) |
implement in 3e | direct match; emits command.select span |
| Accordion | 3f | exists (accordion) |
implement in 3f | direct match |
| Collapsible | 3f | exists (collapsible) |
implement in 3f | direct match |
| Resizable | 3f | exists (resizable) |
implement in 3f | wraps react-resizable-panels |
| ScrollArea | 3f | exists (scroll-area) |
implement in 3f | direct match |
| AspectRatio | 3f | exists (aspect-ratio) |
implement in 3f | direct match |
| Carousel | 3f | exists (carousel) |
implement in 3f | wraps embla-carousel-react |
| Sonner | 3g | exists (sonner) |
implement in 3g | direct match; replaces legacy Toast/Toaster |
| ConfirmDeleteDialog | 3g | NOT in @shadcn | implement in 3g | suaveplan addition; specialised dialog |
| ErrorBoundary | 3g | NOT in @shadcn | implement in 3g | suaveplan addition; declarative React 19 boundary |
| ThemeToggle | 3g | NOT in @shadcn | implement in 3g | suaveplan addition; bound <ThemeSwitcher> |
| Calendar | 3h | exists (calendar) |
implement in 3h | wraps react-day-picker |
| DatePicker | 3h | NOT in @shadcn (recipe only) | implement in 3h | shadcn docs as Calendar + Popover; we ship as a discrete component |
| Select | 3h | exists (select) |
implement in 3h | direct match |
| MultiSelect | 3h | NOT in @shadcn | implement in 3h | suaveplan addition |
| Combobox | 3h | NOT in @shadcn registry as discrete UI item; ships as recipe (command + popover) |
implement in 3h | suaveplan ships discrete component (cross-cutting design §3.4 hint REJECTED — we keep distinct from Select) |
| SearchableSelect | 3h | NOT in @shadcn | implement in 3h | suaveplan addition |
| Slider | 3h | exists (slider) |
implement in 3h | direct match |
| InputOTP | 3h | exists (input-otp) |
implement in 3h | wraps input-otp |
| Chart | 3h | exists (chart) |
implement in 3h | wraps recharts |
| MoneyInput | 3h | NOT in @shadcn | implement in 3h | suaveplan addition; consumes @suaveplan/i18n formatters |
| LanguagePicker | 3h | NOT in @shadcn | implement in 3h | suaveplan addition; reads @suaveplan/locale-data |
| Item | 3h | exists (item) |
implement in 3h | direct match |
Findings to flag for later batches
-
Field/FieldGroup/FieldSetsplit (Wave 3b): shadcn shipsfieldandfield-groupas discrete registry items. TheFieldSetprimitive in our proposal has no upstream peer — we ship it as a suaveplan addition. The Wave 3b implementer should mirror the shadcnfield+field-groupAPI where possible, then layerFieldSetas an additive primitive (<fieldset>semantics + label aggregation). -
Comboboxis NOT folded intoSelect(Wave 3h): the cross-cutting design §3.4 hinted at a possible fold; the live registry shipsComboboxas a recipe (command + popover). We shipComboboxas a discrete component with the same props surface so consumers do not need to compose recipes themselves.MultiSelectandSearchableSelectare suaveplan-only additions atop the same recipe. -
button-groupandkbdexist upstream but are NOT in the proposal's 8-batch table (post-Wave 3): both are useful primitives. The proposal's count is locked at 63 for the v1 ship;button-groupandkbdare deferred to a v1.1 follow-up change. No Wave 3 work needed. -
directionandnative-selectexist upstream but are out-of-scope (post-Wave 3):directionis a Radix re-export (we already use@radix-ui/react-directiondirectly via the chassisuseDirectionhook);native-selectis shadcn's bare-<select>wrapper which we deliberately replace withSelect(Radix-backed) for a11y and styling consistency. Both deferred indefinitely. -
formships in shadcn as thereact-hook-form-bound wrapper: per the proposal, this is owned by@suaveplan/react-forms(sibling changeenterprise-react-forms-v2), NOT byreact-ui. We deliberately do NOT addformto any react-ui batch; the form factory'sreact-hook-form+ Zod-resolvers dep weight is incompatible with consumers wanting only a Button. -
EmptyandSpinneralready exist upstream (Wave 3d): the proposal listed both as suaveplan additions in 3d — they have since landed in@shadcnas discrete items. Wave 3d implementer should mirror the upstream shape rather than invent; tag the implementation notes accordingly. -
StatusBadge,DataTable,MoneyInput,LanguagePicker,MultiSelect,SearchableSelect,ConfirmDeleteDialog,ErrorBoundary,ThemeToggleare all suaveplan-only additions. Wave 3d/3g/3h implementers should NOT search for upstream peers; they should ship these as documented chassis-native components and emit registry items intoregistry/r/so external consumers cannpx shadcn@latest add @suaveplan/<name>againsthttps://ui.suaveplan.com/r/.
Lock-in note
The §"Component count — resolved here" table in proposal.md is hereby locked at 63 components for the v1 ship. Changes to this list require a follow-up OpenSpec change (NOT an amendment to this proposal), per the proposal's §"Component count — resolved here" gate.
The Wave 3a implementation (this commit) ships the 5 Foundation components — Button, Input, Label, Separator, Skeleton — independent of the reconciliation findings above (they are stable across every shadcn version and live in @shadcn as direct matches).