Files
suaveplan/openspec/changes/enterprise-react-ui-v2/COMPONENT-RECONCILIATION.md
2026-05-27 23:29:53 +01:00

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 Field which existed but as separate entry), 3c, 3d (minus StatusBadge/Empty which need separate notes), 3e, 3f, 3g (minus ConfirmDeleteDialog/ErrorBoundary/ThemeToggle — see notes), 3h (minus MultiSelect/SearchableSelect/MoneyInput/LanguagePicker).
  • Split / renamed in upstream: Combobox is a separate item in shadcn (good — confirms cross-cutting design §3.4 hint was wrong; we keep Combobox as a discrete component, NOT folded into Select). Field ships as field — confirms the form-primitives split.
  • Suaveplan-only additions (10 components): StatusBadge, Empty (now exists upstream as empty), ConfirmDeleteDialog, ErrorBoundary, ThemeToggle, MultiSelect, SearchableSelect, MoneyInput, LanguagePicker, FieldGroup (shadcn ships field + field-group separately — field-group exists, mapped to our FieldGroup).
  • Upstream additions we should consider for future batches (5 components): button-group, direction, kbd, native-select, spinner (already in 3d), form (legacy alias — react-forms will provide; we do NOT ship a Form component in react-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

  1. Field/FieldGroup/FieldSet split (Wave 3b): shadcn ships field and field-group as discrete registry items. The FieldSet primitive in our proposal has no upstream peer — we ship it as a suaveplan addition. The Wave 3b implementer should mirror the shadcn field + field-group API where possible, then layer FieldSet as an additive primitive (<fieldset> semantics + label aggregation).

  2. Combobox is NOT folded into Select (Wave 3h): the cross-cutting design §3.4 hinted at a possible fold; the live registry ships Combobox as a recipe (command + popover). We ship Combobox as a discrete component with the same props surface so consumers do not need to compose recipes themselves. MultiSelect and SearchableSelect are suaveplan-only additions atop the same recipe.

  3. button-group and kbd exist 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-group and kbd are deferred to a v1.1 follow-up change. No Wave 3 work needed.

  4. direction and native-select exist upstream but are out-of-scope (post-Wave 3): direction is a Radix re-export (we already use @radix-ui/react-direction directly via the chassis useDirection hook); native-select is shadcn's bare-<select> wrapper which we deliberately replace with Select (Radix-backed) for a11y and styling consistency. Both deferred indefinitely.

  5. form ships in shadcn as the react-hook-form-bound wrapper: per the proposal, this is owned by @suaveplan/react-forms (sibling change enterprise-react-forms-v2), NOT by react-ui. We deliberately do NOT add form to any react-ui batch; the form factory's react-hook-form + Zod-resolvers dep weight is incompatible with consumers wanting only a Button.

  6. Empty and Spinner already exist upstream (Wave 3d): the proposal listed both as suaveplan additions in 3d — they have since landed in @shadcn as discrete items. Wave 3d implementer should mirror the upstream shape rather than invent; tag the implementation notes accordingly.

  7. StatusBadge, DataTable, MoneyInput, LanguagePicker, MultiSelect, SearchableSelect, ConfirmDeleteDialog, ErrorBoundary, ThemeToggle are 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 into registry/r/ so external consumers can npx shadcn@latest add @suaveplan/<name> against https://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).