@create-ui/cli 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,11 @@
1
1
  # Create UI MCP Server
2
2
 
3
- The Create UI MCP server lets AI assistants browse, search, and install components from registries through the Model Context Protocol.
3
+ The Create UI MCP server lets AI assistants browse, search, and install components from the `@createui` registry through the Model Context Protocol.
4
4
 
5
5
  ## Contents
6
6
 
7
7
  - [Setup](#setup)
8
8
  - [Available Tools](#available-tools)
9
- - [Configuring Registries](#configuring-registries)
10
9
  - [Usage Examples](#usage-examples)
11
10
 
12
11
  ## Setup
@@ -33,14 +32,14 @@ Supported clients and the config path each one writes:
33
32
  | `codex` | `.codex/config.toml` |
34
33
  | `opencode` | `opencode.json` |
35
34
 
36
- The generated config always runs `npx createui@latest mcp`. For example, the `claude` client writes:
35
+ The generated config always runs `npx @create-ui/cli mcp` (the package is `@create-ui/cli`; there is no bare `createui` npm package). For example, the `claude` client writes:
37
36
 
38
37
  ```json
39
38
  {
40
39
  "mcpServers": {
41
40
  "createui": {
42
41
  "command": "npx",
43
- "args": ["createui@latest", "mcp"]
42
+ "args": ["@create-ui/cli", "mcp"]
44
43
  }
45
44
  }
46
45
  }
@@ -48,7 +47,7 @@ The generated config always runs `npx createui@latest mcp`. For example, the `cl
48
47
 
49
48
  ## Available Tools
50
49
 
51
- The MCP server exposes seven tools:
50
+ The MCP server exposes seven tools. **`@createui` is the only registry** — it is built in and needs no configuration, and there are no other registries to add. Wherever a tool takes a `registries` array, pass `["@createui"]`.
52
51
 
53
52
  ### get_project_registries
54
53
 
@@ -80,7 +79,7 @@ Searches for items across registries by query string.
80
79
  Returns detailed information about specific items.
81
80
 
82
81
  **Inputs:**
83
- - `items` (string[]) — e.g. `@createui/button`
82
+ - `items` (string[]) — bare item names, e.g. `button` (the MCP server also accepts a `@createui/` prefix and strips it)
84
83
 
85
84
  ### get_item_examples_from_registries
86
85
 
@@ -105,31 +104,6 @@ Returns a checklist to verify after adding components.
105
104
 
106
105
  > **Tip:** The MCP server has no equivalent for inspecting project configuration. To print the resolved aliases, framework, and Tailwind setup, run the `info` command directly: `npx @create-ui/cli info`.
107
106
 
108
- ## Configuring Registries
109
-
110
- Configure additional registries under the `registries` field in `components.json`:
111
-
112
- ```json
113
- {
114
- "registries": {
115
- "@acme": "https://acme.com/r/{name}.json",
116
- "@private": {
117
- "url": "https://api.company.com/r/{name}.json",
118
- "headers": {
119
- "Authorization": "Bearer ${MY_TOKEN}"
120
- }
121
- }
122
- }
123
- }
124
- ```
125
-
126
- - Registry namespaces must start with `@`.
127
- - A registry can be a plain URL string (`@acme`) or an object with `url`, `headers`, and `params` (`@private`).
128
- - URL templates contain a `{name}` placeholder that is replaced with the item name.
129
- - `${VAR}` expands from environment variables, which is useful for tokens and secrets.
130
-
131
- The `@createui` registry is always built-in and available without any configuration.
132
-
133
107
  ## Usage Examples
134
108
 
135
109
  Browse the Create UI registry:
@@ -147,5 +121,5 @@ Search for a date picker in @createui
147
121
  Install a component:
148
122
 
149
123
  ```
150
- Add the button and card components from @createui
124
+ Add the button and select components from @createui
151
125
  ```
@@ -1,18 +1,15 @@
1
1
  # Component Composition
2
2
 
3
- How Create UI components fit together. Compose primitives — never reroll a `<select>`, a custom callout, or a hand-styled loading button when a component already exists. Every component name below is in the Create UI registry; add any of them with `npx @create-ui/cli add <name>`.
3
+ How Create UI components fit together. Compose primitives — never reroll a `<select>`, a custom callout, or a hand-styled loading button when a component already exists. Every component name below is in the `@createui` registry; add any of them with `npx @create-ui/cli add <name>`.
4
4
 
5
5
  ## Contents
6
6
 
7
7
  - Items always inside their Group component
8
- - Callouts use Alert
9
- - Empty states use the Empty component
10
- - Toast notifications use sonner
8
+ - Callouts use InlineAlert
9
+ - Toasts use the Toast component
11
10
  - Choosing between overlay components
12
- - Dialog, Sheet, and Drawer always need a Title
13
- - Card structure
14
11
  - Button has a `loading` prop — never hand-build a spinner button
15
- - TabsTrigger must be inside TabsList
12
+ - Tabbed navigation uses TabMenu
16
13
  - Avatar always needs AvatarFallback
17
14
  - Use existing components instead of custom markup
18
15
 
@@ -49,15 +46,13 @@ This applies to every group-based component:
49
46
  |------|-------|
50
47
  | `SelectItem`, `SelectLabel` | `SelectGroup` |
51
48
  | `DropdownMenuItem`, `DropdownMenuLabel` | `DropdownMenuGroup` |
52
- | `MenubarItem`, `MenubarLabel` | `MenubarGroup` |
53
- | `ContextMenuItem`, `ContextMenuLabel` | `ContextMenuGroup` |
54
49
  | `CommandItem` | `CommandGroup` |
55
50
 
56
51
  ---
57
52
 
58
- ## Callouts use Alert
53
+ ## Callouts use InlineAlert
59
54
 
60
- Use `Alert` for inline callouts. Set the tone with the `variant` prop (`default` or `danger`) and compose the parts `AlertTitle`, `AlertDescription`, and `AlertAction`. Don't hand-roll a styled `<div>`.
55
+ Use `InlineAlert` for callouts. Don't hand-roll a styled `<div>` and don't look for a shadcn-style generic alert (or a page-banner) component; `InlineAlert` is the callout primitive.
61
56
 
62
57
  **Incorrect:**
63
58
 
@@ -71,108 +66,118 @@ Use `Alert` for inline callouts. Set the tone with the `variant` prop (`default`
71
66
  **Correct:**
72
67
 
73
68
  ```tsx
74
- <Alert variant="danger">
75
- <AlertTitle>Payment failed</AlertTitle>
76
- <AlertDescription>Update your card to continue.</AlertDescription>
77
- <AlertAction>
78
- <Button variant="danger" size="sm">Update card</Button>
79
- </AlertAction>
80
- </Alert>
69
+ import { RiErrorWarningFill } from "@create-ui/assets/icons"
70
+ import { Button } from "@/components/ui/button"
71
+ import {
72
+ InlineAlert,
73
+ InlineAlertActions,
74
+ InlineAlertContent,
75
+ InlineAlertDescription,
76
+ InlineAlertHeading,
77
+ InlineAlertIcon,
78
+ InlineAlertTitle,
79
+ } from "@/components/ui/inline-alert"
80
+
81
+ <InlineAlert variant="danger">
82
+ <InlineAlertIcon>
83
+ <RiErrorWarningFill />
84
+ </InlineAlertIcon>
85
+ <InlineAlertContent>
86
+ <InlineAlertHeading>
87
+ <InlineAlertTitle>Payment failed</InlineAlertTitle>
88
+ <InlineAlertDescription>Update your card to continue.</InlineAlertDescription>
89
+ </InlineAlertHeading>
90
+ <InlineAlertActions>
91
+ <Button variant="danger" size="md">Update card</Button>
92
+ </InlineAlertActions>
93
+ </InlineAlertContent>
94
+ </InlineAlert>
81
95
  ```
82
96
 
83
- Related callouts ship as their own components: `inline-alert` for compact, text-row callouts and `alert-banner` for full-width page-level banners. Reach for those instead of restyling `Alert` into a different shape.
97
+ `InlineAlert` takes `variant` (`primary` | `neutral` | `danger` | `success` | `warning` | `info` | `away`) and `appearance` (`default` | `solid` | `soft` | `outline`). For a dismissible callout, add `<InlineAlertClose />` as a direct child and handle `onDismiss` on the root. For a full-width page banner, place an `InlineAlert` in a full-width container there is no separate banner component.
84
98
 
85
99
  ---
86
100
 
87
- ## Empty states use the Empty component
101
+ ## Toasts use the Toast component
88
102
 
89
- Don't hand-build empty/zero states. `Empty` composes a header (`EmptyHeader` `EmptyMedia`, `EmptyTitle`, `EmptyDescription`) and a `EmptyContent` action area. Use `EmptyMedia variant="icon"` to get the boxed icon treatment.
103
+ Toasts are the registry's own `toast` component **not `sonner`**. There is no `toast()` function to import; compose the `Toast` parts and render it from your notification state.
90
104
 
91
- ```tsx
92
- import { RiFolderLine } from "lucide-react" // or your project's iconLibrary
93
-
94
- <Empty>
95
- <EmptyHeader>
96
- <EmptyMedia variant="icon">
97
- <RiFolderLine />
98
- </EmptyMedia>
99
- <EmptyTitle>No projects yet</EmptyTitle>
100
- <EmptyDescription>Get started by creating a new project.</EmptyDescription>
101
- </EmptyHeader>
102
- <EmptyContent>
103
- <Button>Create Project</Button>
104
- </EmptyContent>
105
- </Empty>
106
- ```
107
-
108
- ---
109
-
110
- ## Toast notifications use sonner
111
-
112
- Toasts come from `sonner`. Import the `toast` function directly — don't build a custom toast component.
105
+ **Incorrect:**
113
106
 
114
107
  ```tsx
115
108
  import { toast } from "sonner"
116
109
 
117
- toast.success("Changes saved.")
118
- toast.error("Something went wrong.")
119
- toast("File deleted.", {
120
- action: { label: "Undo", onClick: () => undoDelete() },
121
- })
110
+ toast.success("Draft saved.")
122
111
  ```
123
112
 
124
- ---
125
-
126
- ## Choosing between overlay components
127
-
128
- Pick the overlay that matches the interaction — they all exist in the registry.
129
-
130
- | Use case | Component |
131
- |----------|-----------|
132
- | Focused task that requires input | `Dialog` |
133
- | Destructive action confirmation | `AlertDialog` |
134
- | Side panel with details or filters | `Sheet` |
135
- | Mobile-first bottom panel | `Drawer` |
136
- | Quick info on hover | `HoverCard` |
137
- | Small contextual content on click | `Popover` |
113
+ **Correct:**
138
114
 
139
- ---
115
+ ```tsx
116
+ import { RiCheckboxCircleFill } from "@create-ui/assets/icons"
117
+ import {
118
+ Toast,
119
+ ToastAction,
120
+ ToastBody,
121
+ ToastContent,
122
+ ToastDescription,
123
+ ToastIcon,
124
+ ToastTitle,
125
+ } from "@/components/ui/toast"
126
+
127
+ <Toast variant="success" appearance="solid">
128
+ <ToastBody>
129
+ <ToastIcon>
130
+ <RiCheckboxCircleFill />
131
+ </ToastIcon>
132
+ <ToastContent>
133
+ <ToastTitle>Draft saved</ToastTitle>
134
+ <ToastDescription>Your changes are synced to the cloud.</ToastDescription>
135
+ </ToastContent>
136
+ </ToastBody>
137
+ <ToastAction>Undo</ToastAction>
138
+ </Toast>
139
+ ```
140
140
 
141
- ## Dialog, Sheet, and Drawer always need a Title
141
+ `Toast` takes `variant` (`primary` | `neutral` | `danger` | `success` | `warning` | `info` | `away`) and `appearance` (`solid` | `soft` | `outline` | `default`), plus an `onDismiss` callback. Add `<ToastClose />` for an explicit close affordance and `<ToastProgress />` for an auto-dismiss countdown bar.
142
142
 
143
- `DialogTitle`, `SheetTitle`, and `DrawerTitle` are required for accessibilityscreen readers announce them. Keep one even when the design hides it visually; add `className="sr-only"` in that case.
143
+ There is no provider, queue, or stacking systemyou own the notification state and the placement. Render active toasts from your state into a fixed container:
144
144
 
145
145
  ```tsx
146
- <DialogContent>
147
- <DialogHeader>
148
- <DialogTitle>Edit Profile</DialogTitle>
149
- <DialogDescription>Update your profile.</DialogDescription>
150
- </DialogHeader>
151
- ...
152
- </DialogContent>
146
+ const [toasts, setToasts] = React.useState<AppToast[]>([])
147
+
148
+ <div className="fixed right-4 bottom-4 flex flex-col gap-2">
149
+ {toasts.map((t) => (
150
+ <Toast
151
+ key={t.id}
152
+ variant={t.variant}
153
+ appearance="solid"
154
+ onDismiss={() => setToasts((all) => all.filter((x) => x.id !== t.id))}
155
+ >
156
+ <ToastBody>
157
+ <ToastContent>
158
+ <ToastTitle>{t.title}</ToastTitle>
159
+ </ToastContent>
160
+ </ToastBody>
161
+ <ToastClose />
162
+ </Toast>
163
+ ))}
164
+ </div>
153
165
  ```
154
166
 
155
167
  ---
156
168
 
157
- ## Card structure
169
+ ## Choosing between overlay components
158
170
 
159
- Compose `Card` from its parts don't dump everything into `CardContent`. `CardAction` slots an action into the header row.
171
+ Pick the overlay that matches the interaction these are the overlays that exist.
160
172
 
161
- ```tsx
162
- <Card>
163
- <CardHeader>
164
- <CardTitle>Team Members</CardTitle>
165
- <CardDescription>Manage your team.</CardDescription>
166
- <CardAction>
167
- <Button appearance="ghost" size="sm">Settings</Button>
168
- </CardAction>
169
- </CardHeader>
170
- <CardContent>...</CardContent>
171
- <CardFooter>
172
- <Button>Invite</Button>
173
- </CardFooter>
174
- </Card>
175
- ```
173
+ | Use case | Component |
174
+ |----------|-----------|
175
+ | Short hint on hover | `Tooltip` |
176
+ | "What is this?" helper next to a label | `InfoTooltip` |
177
+ | Action menu on a trigger | `DropdownMenu` |
178
+ | Command palette / quick switcher | `Command` (inline) / `CommandDialog` (modal) |
179
+
180
+ There is **no dialog, popover, sheet, drawer, alert-dialog, or hover-card component**. The only modal surface is `CommandDialog` (shipped with `command`). For other modal or contextual-panel needs, don't invent a lookalike from raw markup — surface the flow inline (e.g. an expanding section, a dedicated route, or an `InlineAlert` confirmation) or ask the user before hand-rolling an overlay.
176
181
 
177
182
  ---
178
183
 
@@ -198,7 +203,7 @@ Compose `Card` from its parts — don't dump everything into `CardContent`. `Car
198
203
  For icons, use the `leadingIcon` / `trailingIcon` props (or `iconOnly` for an icon-only button) — never wrap raw `<svg>` children or add sizing classes; the component sizes the icon per `size`.
199
204
 
200
205
  ```tsx
201
- import { RiSearchLine } from "lucide-react" // or your project's iconLibrary
206
+ import { RiSearchLine } from "@create-ui/assets/icons"
202
207
 
203
208
  <Button leadingIcon={<RiSearchLine />}>Search</Button>
204
209
  <Button iconOnly aria-label="Search" leadingIcon={<RiSearchLine />} />
@@ -208,20 +213,25 @@ Remember the Button API: `variant` is `primary | neutral-solid | neutral-light |
208
213
 
209
214
  ---
210
215
 
211
- ## TabsTrigger must be inside TabsList
216
+ ## Tabbed navigation uses TabMenu
212
217
 
213
- Never render `TabsTrigger` directly inside `Tabs` — always wrap the triggers in `TabsList`.
218
+ Tabs are the `tab-menu` component there is no shadcn-style tabs / tabs-list / tabs-trigger set. `TabMenu` wraps `TabMenuItem`s and owns the selection (`defaultValue`, or controlled `value` / `onValueChange`). It renders the menu only render the active panel yourself from the value; there is no content component.
214
219
 
215
220
  ```tsx
216
- <Tabs defaultValue="account">
217
- <TabsList>
218
- <TabsTrigger value="account">Account</TabsTrigger>
219
- <TabsTrigger value="password">Password</TabsTrigger>
220
- </TabsList>
221
- <TabsContent value="account">...</TabsContent>
222
- </Tabs>
221
+ import { TabMenu, TabMenuItem } from "@/components/ui/tab-menu"
222
+
223
+ const [tab, setTab] = React.useState("overview")
224
+
225
+ <TabMenu variant="horizontal-line" indicator="bottom" value={tab} onValueChange={setTab}>
226
+ <TabMenuItem value="overview" label="Overview" />
227
+ <TabMenuItem value="billing" label="Billing" />
228
+ <TabMenuItem value="settings" label="Settings" />
229
+ </TabMenu>
230
+ {tab === "overview" && <OverviewPanel />}
223
231
  ```
224
232
 
233
+ `variant` is `vertical-button` (default) | `vertical-line` | `horizontal-line` | `horizontal-button`; `size` is `sm` | `md` | `lg`. `TabMenuItem` takes `label`, `leadingIcon` / `trailingIcon`, `disabled`, and `asChild` for link tabs.
234
+
225
235
  ---
226
236
 
227
237
  ## Avatar always needs AvatarFallback
@@ -244,6 +254,7 @@ If a primitive already covers the job, use it — don't reach for raw elements o
244
254
  | Instead of | Use |
245
255
  |---|---|
246
256
  | `<hr>` or `<div className="border-t">` | `<Separator />` |
247
- | `<div className="animate-pulse">` with styled divs | `<Skeleton className="h-4 w-3/4" />` |
248
257
  | `<span className="rounded-full bg-green-100 …">` | `<Badge variant="success">Active</Badge>` |
249
- | A status dot built from a styled `<span>` | `<StatusBadge variant="success">Online</StatusBadge>` |
258
+ | A status dot built from a styled `<span>` | `<StatusBadge variant="success" />` (it renders the dot only — put the label next to it). `variant`: `primary`, `danger`, `success`, `warning`, `info`, `highlighted`, `away`, `verified`, `cyan`, `lime`, `neutral`, `white` — note `danger`, not `error` |
259
+ | A removable tag built from `<span>` + `<button>` | `<Chip onClose={…}>…</Chip>` |
260
+ | A hand-rolled `animate-spin` loading indicator | `<Spinner />` |
@@ -6,7 +6,7 @@
6
6
  - Choosing the right form control
7
7
  - InputGroup requires InputGroupControl/InputGroupTextarea
8
8
  - Buttons inside inputs use InputGroup + InputGroupButton
9
- - Option sets (2–7 choices) use ToggleGroup
9
+ - Option sets (2–7 choices) use SegmentedControl
10
10
  - Dropdowns use Select
11
11
  - FieldSet + FieldLegend for grouping related fields
12
12
  - Field validation and disabled states
@@ -59,6 +59,8 @@ import { Input } from "@/components/ui/input"
59
59
 
60
60
  Use `FieldLabel className="sr-only"` for a visually hidden but accessible label.
61
61
 
62
+ Beyond the parts above, `field.tsx` also ships `FieldTitle` (a label for non-labelable controls, connect via `aria-labelledby`), `FieldContent` (wraps the control + description in horizontal layouts), `FieldFooter` (a footer row, e.g. helper + counter), `FieldHelper` (small helper text), and `FieldSeparator` (a divider between fields).
63
+
62
64
  ---
63
65
 
64
66
  ## Choosing the right form control
@@ -70,14 +72,17 @@ Every control below exists in the registry. Pick by intent:
70
72
  | Single-line text | `Input` |
71
73
  | Multi-line text | `Textarea` |
72
74
  | Dropdown of predefined options | `Select` |
73
- | Searchable dropdown | `Combobox` |
74
- | Native HTML select (no JS) | `native-select` |
75
75
  | Boolean in a settings row | `Switch` |
76
76
  | Boolean in a form | `Checkbox` |
77
77
  | One choice from a few options | `RadioGroup` |
78
- | One choice across 2–7 visible options | `ToggleGroup` |
78
+ | One choice across 2–7 visible options | `SegmentedControl` |
79
+ | Several related on/off options | `CheckboxGroup` / `SwitchGroup` |
79
80
  | Verification / OTP code | `InputOTP` |
80
- | Numeric value with step controls | `input-stepper` |
81
+ | Numeric value with step controls | `InputStepper` |
82
+ | Date | `DateInput` |
83
+ | Phone number | `PhoneInput` |
84
+ | Card details | `CreditCardInput` |
85
+ | Password with a strength meter | `PasswordStrength` |
81
86
 
82
87
  ---
83
88
 
@@ -134,7 +139,7 @@ Never absolutely-position a `Button` over an `Input`. Compose `InputGroup` + `In
134
139
 
135
140
  ```tsx
136
141
  import { InputGroup, InputGroupControl, InputGroupButton } from "@/components/ui/input-group"
137
- import { RiSearchLine } from "lucide-react"
142
+ import { RiSearchLine } from "@create-ui/assets/icons"
138
143
 
139
144
  <InputGroup>
140
145
  <InputGroupControl placeholder="Search…" />
@@ -156,9 +161,9 @@ import { InputGroup, InputGroupControl, InputGroupAddon } from "@/components/ui/
156
161
 
157
162
  ---
158
163
 
159
- ## Option sets (2–7 choices) use ToggleGroup
164
+ ## Option sets (2–7 choices) use SegmentedControl
160
165
 
161
- For a small set of mutually-exclusive (or multi-select) choices, use `ToggleGroup` + `ToggleGroupItem`. Don't hand-roll a row of `Button`s with manual active state. `ToggleGroup` is built on Radix, so it takes `type="single"` or `type="multiple"` and the matching value props (`value` / `defaultValue` / `onValueChange`).
166
+ For a small set of mutually-exclusive choices, use `SegmentedControl` + `SegmentedControlItem`. Don't hand-roll a row of `Button`s with manual active state.
162
167
 
163
168
  **Incorrect:**
164
169
 
@@ -181,28 +186,30 @@ const [selected, setSelected] = useState("daily")
181
186
  **Correct:**
182
187
 
183
188
  ```tsx
184
- import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
189
+ import { SegmentedControl, SegmentedControlItem } from "@/components/ui/segmented-control"
185
190
 
186
- <ToggleGroup type="single" defaultValue="daily">
187
- <ToggleGroupItem value="daily">Daily</ToggleGroupItem>
188
- <ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
189
- <ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
190
- </ToggleGroup>
191
+ <SegmentedControl defaultValue="daily">
192
+ <SegmentedControlItem value="daily">Daily</SegmentedControlItem>
193
+ <SegmentedControlItem value="weekly">Weekly</SegmentedControlItem>
194
+ <SegmentedControlItem value="monthly">Monthly</SegmentedControlItem>
195
+ </SegmentedControl>
191
196
  ```
192
197
 
193
- Use `type="multiple"` (value is a string array) when more than one option can be active at once. Wrap a labelled toggle group in a `Field` and connect them with `aria-labelledby`:
198
+ `SegmentedControl` is single-select: the value props are `value` / `defaultValue` / `onValueChange` (a string — there is no `type="multiple"`). Style it with `variant` (`primary` | `neutral`) and `appearance` (`flat` | `grouped`); items take `leadingIcon`. When more than one option can be active at once, that's not a segmented control use `CheckboxGroup` (or `SwitchGroup`) instead.
199
+
200
+ Wrap a labelled segmented control in a `Field` and connect them with `aria-labelledby`:
194
201
 
195
202
  ```tsx
196
203
  import { Field, FieldTitle } from "@/components/ui/field"
197
- import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
204
+ import { SegmentedControl, SegmentedControlItem } from "@/components/ui/segmented-control"
198
205
 
199
206
  <Field orientation="horizontal">
200
207
  <FieldTitle id="theme-label">Theme</FieldTitle>
201
- <ToggleGroup type="single" defaultValue="system" aria-labelledby="theme-label">
202
- <ToggleGroupItem value="light">Light</ToggleGroupItem>
203
- <ToggleGroupItem value="dark">Dark</ToggleGroupItem>
204
- <ToggleGroupItem value="system">System</ToggleGroupItem>
205
- </ToggleGroup>
208
+ <SegmentedControl defaultValue="system" aria-labelledby="theme-label">
209
+ <SegmentedControlItem value="light">Light</SegmentedControlItem>
210
+ <SegmentedControlItem value="dark">Dark</SegmentedControlItem>
211
+ <SegmentedControlItem value="system">System</SegmentedControlItem>
212
+ </SegmentedControl>
206
213
  </Field>
207
214
  ```
208
215
 
@@ -237,7 +244,7 @@ import {
237
244
  </Field>
238
245
  ```
239
246
 
240
- `Select` accepts `size` (`"xs" | "sm" | "md"`), `invalid`, `disabled`, and `loading`. When it's not inside a `Field`, set these on the `Select` itself. For a dropdown with no JS, reach for `native-select` instead. For a searchable list, use `Combobox`.
247
+ `Select` accepts `size` (`"xs" | "sm" | "md"`), `invalid`, `disabled`, and `loading`. When it's not inside a `Field`, set these on the `Select` itself.
241
248
 
242
249
  ---
243
250
 
@@ -272,7 +279,7 @@ import { Checkbox } from "@/components/ui/checkbox"
272
279
  </FieldSet>
273
280
  ```
274
281
 
275
- For a single-choice group, swap the checkboxes for a `RadioGroup` inside the same `FieldSet`.
282
+ For a single-choice group, swap the checkboxes for a `RadioGroup` inside the same `FieldSet`. When the group is one logical control, reach for the grouped-control primitives directly: `CheckboxGroup`, `SwitchGroup`, and `RadioGroup`.
276
283
 
277
284
  ---
278
285
 
@@ -298,4 +305,4 @@ import { Input } from "@/components/ui/input"
298
305
  </Field>
299
306
  ```
300
307
 
301
- This pattern works for every control: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroup`, `Switch`, `Slider`, `native-select`, and `InputOTP`.
308
+ This pattern works for every control: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroup`, `Switch`, and `InputOTP`.