# 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 `` | | 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 (`
` 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-`