@create-ui/cli 0.5.8 → 0.6.0

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.
Files changed (72) hide show
  1. package/dist/{chunk-RMTTHCB3.js → chunk-2ELKDGGM.js} +3 -3
  2. package/dist/{chunk-RMTTHCB3.js.map → chunk-2ELKDGGM.js.map} +1 -1
  3. package/dist/chunk-643QI2I2.js +102 -0
  4. package/dist/chunk-643QI2I2.js.map +1 -0
  5. package/dist/{chunk-NQFMXHMH.js → chunk-KQTXDVKV.js} +3 -3
  6. package/dist/chunk-KQTXDVKV.js.map +1 -0
  7. package/dist/index.d.ts +360 -360
  8. package/dist/index.js +26 -26
  9. package/dist/index.js.map +1 -1
  10. package/dist/mcp/index.js +1 -1
  11. package/dist/registry/index.d.ts +2 -2
  12. package/dist/registry/index.js +1 -1
  13. package/dist/schema/index.d.ts +715 -715
  14. package/dist/skills/createui/SKILL.md +199 -177
  15. package/dist/skills/createui/agents/openai.yml +1 -1
  16. package/dist/skills/createui/cli.md +42 -42
  17. package/dist/skills/createui/customization.md +20 -15
  18. package/dist/skills/createui/evals/evals.json +68 -5
  19. package/dist/skills/createui/mcp.md +14 -5
  20. package/dist/skills/createui/reference/accordion.md +127 -0
  21. package/dist/skills/createui/reference/app-store-badge.md +88 -0
  22. package/dist/skills/createui/reference/aspect-ratio.md +52 -0
  23. package/dist/skills/createui/reference/avatar.md +230 -0
  24. package/dist/skills/createui/reference/badge.md +110 -0
  25. package/dist/skills/createui/reference/breadcrumb.md +153 -0
  26. package/dist/skills/createui/reference/button-group.md +116 -0
  27. package/dist/skills/createui/reference/button.md +104 -0
  28. package/dist/skills/createui/reference/checkbox-group.md +118 -0
  29. package/dist/skills/createui/reference/checkbox.md +79 -0
  30. package/dist/skills/createui/reference/chip.md +115 -0
  31. package/dist/skills/createui/reference/close-button.md +83 -0
  32. package/dist/skills/createui/reference/country-flag.md +109 -0
  33. package/dist/skills/createui/reference/credit-card-input.md +76 -0
  34. package/dist/skills/createui/reference/date-input.md +71 -0
  35. package/dist/skills/createui/reference/dropdown-menu.md +164 -0
  36. package/dist/skills/createui/reference/field.md +186 -0
  37. package/dist/skills/createui/reference/info-tooltip.md +110 -0
  38. package/dist/skills/createui/reference/inline-alert.md +146 -0
  39. package/dist/skills/createui/reference/input-group.md +171 -0
  40. package/dist/skills/createui/reference/input-otp.md +130 -0
  41. package/dist/skills/createui/reference/input-stepper.md +120 -0
  42. package/dist/skills/createui/reference/input.md +118 -0
  43. package/dist/skills/createui/reference/label.md +121 -0
  44. package/dist/skills/createui/reference/pagination.md +157 -0
  45. package/dist/skills/createui/reference/phone-input.md +77 -0
  46. package/dist/skills/createui/reference/progress.md +158 -0
  47. package/dist/skills/createui/reference/radio-group.md +133 -0
  48. package/dist/skills/createui/reference/radio.md +79 -0
  49. package/dist/skills/createui/reference/scroll-area.md +212 -0
  50. package/dist/skills/createui/reference/segmented-control.md +146 -0
  51. package/dist/skills/createui/reference/select.md +204 -0
  52. package/dist/skills/createui/reference/separator.md +99 -0
  53. package/dist/skills/createui/reference/social-login-button.md +130 -0
  54. package/dist/skills/createui/reference/spinner.md +68 -0
  55. package/dist/skills/createui/reference/status-badge.md +89 -0
  56. package/dist/skills/createui/reference/switch-group.md +122 -0
  57. package/dist/skills/createui/reference/switch.md +75 -0
  58. package/dist/skills/createui/reference/tab-menu.md +165 -0
  59. package/dist/skills/createui/reference/text-link.md +84 -0
  60. package/dist/skills/createui/reference/textarea.md +50 -0
  61. package/dist/skills/createui/reference/toast.md +162 -0
  62. package/dist/skills/createui/reference/tooltip.md +63 -0
  63. package/dist/skills/createui/rules/composition.md +41 -25
  64. package/dist/skills/createui/rules/design.md +266 -0
  65. package/dist/skills/createui/rules/forms.md +44 -15
  66. package/dist/skills/createui/rules/icons.md +64 -18
  67. package/dist/skills/createui/rules/styling.md +53 -14
  68. package/dist/utils/index.js +1 -1
  69. package/package.json +1 -1
  70. package/dist/chunk-M5DYT2NE.js +0 -64
  71. package/dist/chunk-M5DYT2NE.js.map +0 -1
  72. package/dist/chunk-NQFMXHMH.js.map +0 -1
@@ -0,0 +1,266 @@
1
+ # Design & Showcase Quality
2
+
3
+ How to make pages built with Create UI look premium, not just API-correct - the taste layer for marketing, landing, and showcase UI.
4
+
5
+ **Build to the user's brand and what they asked for - never reproduce the Create UI marketing site.** The recipes below are worked examples of the principles in action (which token belongs on which surface, how to keep a footer neutral, how to avoid bare-default poverty). Lift the *principle*, adapt the specifics: your colors, gradients, copy, spacing, and layout should serve the user's design, not clone createui.co. The universal rules - spacing rhythm, the richness checklist, the anti-patterns, the dark-panel token rule - apply to every project regardless of brand.
6
+
7
+ ---
8
+
9
+ ## Surface & color recipes
10
+
11
+ ### Hero *(source: hero-section.tsx)*
12
+
13
+ Heading is `text-heading-h1` (it auto-scales down at smaller breakpoints - no `md:`/`xl:` prefixes) with `text-strongest font-semibold`; the lede is `text-body-lg text-body`. The CTA pair is one solid neutral button plus one soft neutral button, both `size="xl" shape="rounded"`, in a `gap-component-sm` row:
14
+
15
+ ```tsx
16
+ <div className="gap-component-xl flex max-w-[800px] flex-col">
17
+ <h1 className="text-heading-h1 text-strongest font-semibold">Stop rebuilding UI.</h1>
18
+ <p className="text-body-lg text-body">One library, one design system, one source.</p>
19
+ <div className="gap-component-sm flex flex-wrap">
20
+ <Button variant="neutral-solid" appearance="solid" size="xl" shape="rounded" leadingIcon={<RiCodeFill />}>
21
+ Start Free
22
+ </Button>
23
+ <Button variant="neutral-light" appearance="soft" size="xl" shape="rounded">View Pricing</Button>
24
+ </div>
25
+ </div>
26
+ ```
27
+
28
+ **One solid action per section.** The secondary action is always a softer appearance (`soft`, `ghost`, or `outline`), never a second solid button.
29
+
30
+ ### Dark CTA / premium panel *(source: cta-section.tsx)*
31
+
32
+ The surface is a dark linear gradient, not flat black: `bg-[linear-gradient(82deg,#030712_-0.01%,#1e2939_55.88%)]` with `px-layout-sm py-layout-lg`. On top of it:
33
+
34
+ - Eyebrow: `Badge variant="primary" appearance="outline" size="md" className="dark"`; the `dark` class flips the badge's tokens so the outline reads correctly on the dark panel. Heading: `text-heading-h2 text-white font-medium`; body: `text-body-lg text-white/70`. **Use raw `text-white` here, never `text-static`** (see the note below).
35
+ - Buttons switch to `variant="inverse-solid"` (`appearance="solid"` primary, `appearance="soft"` secondary, both `size="xl"`); optional micro-caption under them: `text-ui-overline-xs text-white/50 uppercase`.
36
+
37
+ ```tsx
38
+ <section className="px-layout-sm py-layout-lg gap-component-md flex flex-col items-start bg-[linear-gradient(82deg,#030712_-0.01%,#1e2939_55.88%)]">
39
+ <Badge variant="primary" appearance="outline" size="md" className="dark">
40
+ PRE-SALE · 70% OFF · LIMITED TIME
41
+ </Badge>
42
+ <h2 className="text-heading-h2 text-white font-medium">Stop deciding.</h2>
43
+ <p className="text-body-lg text-white/70">The system is ready.</p>
44
+ <div className="gap-component-sm flex flex-col xl:flex-row">
45
+ <Button variant="inverse-solid" appearance="solid" size="xl" leadingIcon={<RiCodeFill />}>Get 70% off now</Button>
46
+ <Button variant="inverse-solid" appearance="soft" size="xl" trailingIcon={<RiArrowRightSLine />}>Start for Free</Button>
47
+ </div>
48
+ </section>
49
+ ```
50
+
51
+ > **Why raw `text-white`, not `text-static`, on a fixed dark gradient:** the gradient is a hardcoded dark color (not a theme surface token), so the text must be light in BOTH themes. `text-static` is theme-relative - white in light, **black under `.dark`** - so on a dark-mode page the heading turns black-on-dark. (The Create UI marketing site does use `text-static` on this exact panel, but only because its marketing routes are force-light; don't copy that into a dark-mode-capable page.) Rule: on any fixed-color surface, always-light text is raw `text-white`. Alternative architecture: put `className="dark"` on the whole panel and use `text-strongest` / `text-body` / `text-placeholder`, which resolve light under `.dark`.
52
+
53
+ ### Footer *(source: site-footer.tsx, footer-link-row.tsx)*
54
+
55
+ **Footers are neutral surfaces. Never use random saturated colors in a footer.** The shell is `bg-static`, every divider is `border-light`, the bottom bar is `bg-weak`. The only color moment is the newsletter panel's primary gradient (below).
56
+
57
+ - Shell: `<footer className="bg-static">`, inner wrapper `px-layout-sm py-layout-lg`, top-level blocks stacked with `gap-layout-sm`, content capped at `max-w-[1280px]`.
58
+ - Column dividers: `<Separator />` between stacked blocks; for VERTICAL dividers between side-by-side columns the site uses `<span aria-hidden className="border-light border-t md:border-t-0 md:border-l" />` inside the `gap-section-md` grid (Separator has no `orientation` prop - it is horizontal only).
59
+ - Brand row: logo plus a version chip `Badge variant="neutral" appearance="outline" size="xs"`; tagline `text-body-md text-body`.
60
+ - Social links: icon-only ghost buttons in a `gap-component-xs` row, never raw `<a>` tags with an svg: `<Button size="md" iconOnly variant="neutral-solid" appearance="ghost" aria-label="GitHub" asChild>` wrapping an `<a target="_blank" rel="noreferrer">` with an `Ri*` icon.
61
+ - Bottom bar: `bg-weak px-layout-sm py-section-md`; copyright `text-body-md text-body`; policy links `TextLink variant="neutral" size="sm"`.
62
+
63
+ Link columns: title `text-body-md text-strongest font-semibold`, then a `gap-component-sm` stack of neutral TextLinks; "soon" markers are pill badges next to the link, never recolored links:
64
+
65
+ ```tsx
66
+ <div className="gap-component-lg flex flex-col">
67
+ <h3 className="text-body-md text-strongest font-semibold">Products</h3>
68
+ <div className="gap-component-sm flex flex-col items-start">
69
+ <TextLink href="/docs" variant="neutral" size="sm" leadingIcon={<RiArrowRightSFill />}>
70
+ Documentation
71
+ </TextLink>
72
+ <div className="gap-component-xs flex items-center">
73
+ <TextLink href="/templates" variant="neutral" size="sm">Templates</TextLink>
74
+ <Badge variant="primary" appearance="soft" size="sm" shape="pill">Soon</Badge>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ ```
79
+
80
+ ### Newsletter panel *(source: site-footer.tsx, newsletter-form.tsx)*
81
+
82
+ The footer's single intentional color block: a primary gradient panel with an inverse-outline eyebrow, `text-white` title, `text-white/70` description. (Same rule as the dark CTA - the panel is a fixed primary color, so its text is raw `text-white`, never `text-static`, which would flip to black under `.dark`.)
83
+
84
+ ```tsx
85
+ <div className="p-section-xl from-primary-base via-primary-weak to-primary-weakest gap-component-md flex flex-col items-start rounded-xl bg-linear-to-b from-[31.34%] via-[65.67%]">
86
+ <Badge variant="inverse" appearance="outline" size="md">NEWSLETTER</Badge>
87
+ <h2 className="text-heading-h4 text-white">Stay in the loop</h2>
88
+ <p className="text-body-lg text-white/70">Releases and updates, no spam.</p>
89
+ </div>
90
+ ```
91
+
92
+ The form is a `Field` (with `invalid` / `loading` bound to submit state) wrapping an `InputGroup` with a leading mail icon, plus a solid primary submit:
93
+
94
+ ```tsx
95
+ <Field size="md" invalid={status === "error"} loading={status === "submitting"}>
96
+ <InputGroup>
97
+ <InputGroupLeadingIcon><RiMailOpenLine /></InputGroupLeadingIcon>
98
+ <InputGroupSlot>
99
+ <InputGroupControl type="email" placeholder="hi@createui.co" aria-label="Email address" />
100
+ </InputGroupSlot>
101
+ </InputGroup>
102
+ </Field>
103
+ <Button type="submit" variant="primary" appearance="solid" size="xl">Sign Up</Button>
104
+ ```
105
+
106
+ Submit feedback is inline and the form stays mounted (source: newsletter-form.tsx): on success render a `FieldDescription` under the input (give it `className="text-white"` on the colored gradient panel - not `text-static`), on error render `FieldError`. Never unmount the form to swap in a success badge or alert.
107
+
108
+ The form is the footer's ONLY client boundary: put it in its own file with `"use client"` and import it; the footer shell stays a Server Component. Never hoist `"use client"` over a whole static section to accommodate one interactive form.
109
+
110
+ ### Pricing *(source: pricing-section.tsx)*
111
+
112
+ The billing toggle is a grouped `SegmentedControl` (animated sliding indicator), never a pair of hand-styled buttons. The site uses `variant="neutral"` at `size="xl"` (`lg` on mobile), controlled:
113
+
114
+ ```tsx
115
+ <SegmentedControl appearance="grouped" variant="neutral" size="xl" value={billing} onValueChange={setBilling}>
116
+ <SegmentedControlItem value="individual">Individual</SegmentedControlItem>
117
+ <SegmentedControlItem value="teams">Teams</SegmentedControlItem>
118
+ </SegmentedControl>
119
+ ```
120
+
121
+ A discount badge rides INSIDE the item as a plain extra child (the item's content slot is already a gapped inline-flex row; no wrapper span, no manual gap classes):
122
+
123
+ ```tsx
124
+ <SegmentedControlItem value="yearly">
125
+ Yearly
126
+ <Badge variant="success" appearance="soft" size="sm" shape="pill">-20%</Badge>
127
+ </SegmentedControlItem>
128
+ ```
129
+
130
+ - Section eyebrow: `Badge variant="primary" appearance="soft" size="md"` above a `text-heading-h2 text-strongest font-medium` heading and a `text-body-lg text-body` lede.
131
+ - Section background: a subtle vertical gradient `from-weakest to-light bg-gradient-to-b from-25%`, not a flat gray.
132
+ - Cards: `bg-static` with `shadow-neutral-md`; the highlighted plan gets `shadow-neutral-lg` plus a gradient frame (`from-primary-base via-primary-strong to-light bg-gradient-to-b via-10%`).
133
+ - Discount treatment: struck-through anchor price next to a soft badge, then the real price large:
134
+
135
+ ```tsx
136
+ <div className="gap-component-sm flex items-center">
137
+ <span className="text-heading-h5 text-placeholder font-medium line-through">$399</span>
138
+ <Badge variant="warning" appearance="soft" size="md">70% OFF</Badge>
139
+ </div>
140
+ <span className="text-heading-h2 text-strongest font-semibold">$119</span>
141
+ ```
142
+
143
+ - Feature-list group breaks ride on a Separator with an outline badge (`<Separator align="start"><Badge variant="neutral" appearance="outline" size="sm">COMING SOON</Badge></Separator>`); feature rows pair the label with a small soft badge (`appearance="soft" size="sm"`, tones `success` / `verified` / `primary`, optionally `trailingIcon={<RiCheckLine />}`).
144
+
145
+ ---
146
+
147
+ ## Spacing rhythm
148
+
149
+ Three semantic tiers. They auto-scale across breakpoints, so never prefix them with `md:` / `xl:` (see [styling.md](./styling.md)).
150
+
151
+ | Tier | Tokens | Scale (px) | Used for |
152
+ | --- | --- | --- | --- |
153
+ | Component | `gap-component-xs..xl`, `p-component-*` | 4 / 8 / 12 / 16 / 24 | inside one block: CTA button rows (`gap-component-sm`), heading-to-lede stacks (`gap-component-md`, `gap-component-xl`), card internals (`p-component-xl`), footer link lists (`gap-component-sm`) |
154
+ | Section | `gap-section-xs..xl`, `p-section-*` | 12 / 16 / 24 / 32 / 48 | between clusters in one section: header cluster to toggle (`gap-section-lg`), plan-card row (`gap-section-sm`), newsletter panel padding (`p-section-xl`), footer grid columns (`gap-section-md`) |
155
+ | Layout | `gap-layout-xs..xl`, `p-layout-*` | 32 / 48 / 64 / 96 / 128 | page level: section outer padding (`px-layout-sm py-layout-lg`), gaps between a page's top-level blocks (`gap-layout-sm`) |
156
+
157
+ Rule of thumb from the site: a marketing section is `px-layout-sm py-layout-lg` outside, `gap-section-*` between its clusters, `gap-component-*` inside each cluster. If a Figma spec hands you a static value (e.g. `space-space-4`), use the plain Tailwind class (`gap-4`) instead; semantic spacing is for token-specified or site-recipe spacing.
158
+
159
+ ---
160
+
161
+ ## Richness checklist
162
+
163
+ No UI should ship bare defaults - exercise each component's axes per SKILL.md "Use the full component API". This is most critical in marketing, landing, and showcase work; before calling a section done:
164
+
165
+ - **Switch**: never a bare `<Switch />`. Pick a `variant` (`primary` | `info` | `neutral` | `inverse` | `semantic`) and exercise at least one extra axis: `ioTrigger` (I/O glyphs in the track), `thumbIcon` (check / cross on the thumb), `thumbType="long"`, or `shape="rounded"`.
166
+ - **Badge**: choose `appearance` deliberately. `soft` is the workhorse tint (feature tags, discounts), `outline` is the quiet chip (version numbers, eyebrows on dark, COMING SOON), `solid` is for high-priority counts and statuses. Vary `variant` to match meaning and use `shape="pill"` for tag-like chips.
167
+ - **SegmentedControl**: marketing and pricing toggles use `appearance="grouped"`; `flat` is for dense app toolbars.
168
+ - **Avatar**: use a real color variant (`variant="gradient-blue"`, `"weak-green"`, `"base-indigo"`; pattern `{gradient|strong|base|weak|alpha}-{color}`) with `AvatarText` initials, or an `AvatarImage` plus `AvatarBadge` with `AvatarBadgeStatus variant="online"`. Never an empty gray circle.
169
+ - **Button**: exactly one `appearance="solid"` action per section; secondary actions are `soft`, `ghost`, or `outline`. On dark panels both use `variant="inverse-solid"`. Icons go through `leadingIcon` / `trailingIcon`, never as children next to text (`iconOnly` buttons are the exception: there the icon is the child).
170
+ - **Links**: inline and footer links are `TextLink` (e.g. `variant="neutral" size="sm"`), not styled `<a>` tags.
171
+
172
+ ---
173
+
174
+ ## Anti-patterns
175
+
176
+ ### Icon as a child inside Badge
177
+
178
+ **Incorrect:**
179
+
180
+ ```tsx
181
+ <Badge variant="success"><RiCheckLine className="size-3" /> Ready</Badge>
182
+ ```
183
+
184
+ **Correct:**
185
+
186
+ ```tsx
187
+ <Badge variant="success" appearance="soft" size="sm" trailingIcon={<RiCheckLine />}>Ready</Badge>
188
+ ```
189
+
190
+ ### Hand-rolled tab buttons
191
+
192
+ **Incorrect:**
193
+
194
+ ```tsx
195
+ <div className="flex gap-2 border-b">
196
+ <button className="border-b-2 border-primary-500 px-3 py-2">Preview</button>
197
+ <button className="px-3 py-2">Code</button>
198
+ </div>
199
+ ```
200
+
201
+ **Correct:**
202
+
203
+ ```tsx
204
+ <TabMenu variant="horizontal-line" indicator="bottom" value={tab} onValueChange={setTab}>
205
+ <TabMenuItem value="preview" label="Preview" />
206
+ <TabMenuItem value="code" label="Code" leadingIcon={<RiCodeFill />} />
207
+ </TabMenu>
208
+ {tab === "preview" ? <PreviewPanel /> : <CodePanel />}
209
+ ```
210
+
211
+ ### Flat pricing toggle
212
+
213
+ **Incorrect:**
214
+
215
+ ```tsx
216
+ <div className="flex rounded-lg border">
217
+ <button className="bg-primary-500 px-4 py-2 text-white">Monthly</button>
218
+ <button className="px-4 py-2">Yearly</button>
219
+ </div>
220
+ ```
221
+
222
+ **Correct:**
223
+
224
+ ```tsx
225
+ <SegmentedControl appearance="grouped" variant="neutral" size="xl" value={billing} onValueChange={setBilling}>
226
+ <SegmentedControlItem value="monthly">Monthly</SegmentedControlItem>
227
+ <SegmentedControlItem value="yearly">Yearly</SegmentedControlItem>
228
+ </SegmentedControl>
229
+ ```
230
+
231
+ ### Bare Switch in a showcase
232
+
233
+ **Incorrect:**
234
+
235
+ ```tsx
236
+ <Switch />
237
+ ```
238
+
239
+ **Correct:**
240
+
241
+ ```tsx
242
+ <Switch variant="primary" size="md" ioTrigger defaultChecked />
243
+ <Switch variant="neutral" size="md" shape="rounded" thumbIcon />
244
+ ```
245
+
246
+ ### Raw palette colors in the footer
247
+
248
+ **Incorrect:**
249
+
250
+ ```tsx
251
+ <footer className="bg-slate-900">
252
+ <h3 className="text-indigo-400">Products</h3>
253
+ <a className="text-blue-500 hover:text-blue-400" href="/docs">Docs</a>
254
+ </footer>
255
+ ```
256
+
257
+ **Correct:**
258
+
259
+ ```tsx
260
+ <footer className="bg-static">
261
+ <h3 className="text-body-md text-strongest font-semibold">Products</h3>
262
+ <TextLink href="/docs" variant="neutral" size="sm">Docs</TextLink>
263
+ </footer>
264
+ ```
265
+
266
+ When unsure, adapt a recipe from this file as a starting point (to the user's brand, not as a createui.co clone), or fetch a real implementation to study: `get_item_examples_from_registries` over MCP, or `npx @create-ui/cli view <name>`.
@@ -7,6 +7,7 @@
7
7
  - InputGroup requires InputGroupControl/InputGroupTextarea
8
8
  - Buttons inside inputs use InputGroup + InputGroupButton
9
9
  - Option sets (2–7 choices) use SegmentedControl
10
+ - Switches are richer than the bare default
10
11
  - Dropdowns use Select
11
12
  - FieldSet + FieldLegend for grouping related fields
12
13
  - Field validation and disabled states
@@ -15,7 +16,7 @@
15
16
 
16
17
  ## Forms use FieldGroup + Field
17
18
 
18
- Always lay out a form with `FieldGroup` + `Field` never a raw `div` with `space-y-*`. `Field` owns the size, invalid, disabled and loading state for everything inside it, and the nested control reads that state automatically.
19
+ Always lay out a form with `FieldGroup` + `Field` - never a raw `div` with `space-y-*`. `Field` owns the size, invalid, disabled and loading state for everything inside it, and the nested control reads that state automatically.
19
20
 
20
21
  **Incorrect:**
21
22
 
@@ -47,11 +48,12 @@ import { Input } from "@/components/ui/input"
47
48
  </FieldGroup>
48
49
  ```
49
50
 
50
- `Field` accepts `size` (`"xs" | "sm" | "md"`, default `"sm"`) and `orientation` (`"vertical" | "horizontal" | "responsive"`, default `"vertical"`). Size cascades top-down: set it once on `Field` and the control, label, and description inherit it never re-set the size on each child.
51
+ `Field` accepts `size` (`"xs" | "sm" | "md"`, default `"sm"`) and `orientation` (`"vertical" | "horizontal" | "responsive"`, default `"vertical"`). Size cascades top-down: set it once on `Field` and the control, label, and description inherit it - never re-set the size on each child.
51
52
 
52
53
  ```tsx
53
- // Horizontal layout for settings rows.
54
- <Field orientation="horizontal">
54
+ // Horizontal layout for settings rows (apply-immediately rows use SwitchGroup instead;
55
+ // Field size does not cascade to Switch, so pair the sizes explicitly).
56
+ <Field size="md" orientation="horizontal">
55
57
  <FieldLabel htmlFor="notifications">Email notifications</FieldLabel>
56
58
  <Switch id="notifications" />
57
59
  </Field>
@@ -72,11 +74,12 @@ Every control below exists in the registry. Pick by intent:
72
74
  | Single-line text | `Input` |
73
75
  | Multi-line text | `Textarea` |
74
76
  | Dropdown of predefined options | `Select` |
75
- | Boolean in a settings row | `Switch` |
77
+ | Bare boolean toggle (no label row) | `Switch` |
78
+ | Labelled apply-immediately settings row | `SwitchGroup` (one labelled row per switch) |
76
79
  | Boolean in a form | `Checkbox` |
77
80
  | One choice from a few options | `RadioGroup` |
78
81
  | One choice across 2–7 visible options | `SegmentedControl` |
79
- | Several related on/off options | `CheckboxGroup` / `SwitchGroup` |
82
+ | Several related on/off options | one labelled row per option - stack `CheckboxGroup` rows (submit-to-save) / `SwitchGroup` rows (apply-immediately) |
80
83
  | Verification / OTP code | `InputOTP` |
81
84
  | Numeric value with step controls | `InputStepper` |
82
85
  | Date | `DateInput` |
@@ -122,7 +125,7 @@ import { InputGroup, InputGroupTextarea } from "@/components/ui/input-group"
122
125
 
123
126
  ## Buttons inside inputs use InputGroup + InputGroupButton
124
127
 
125
- Never absolutely-position a `Button` over an `Input`. Compose `InputGroup` + `InputGroupButton` (or `InputGroupAddon` for non-interactive affordances). `InputGroupButton` inherits the group's size and state, so you don't set `size` on it. Pass the icon through `leadingIcon`, or use `iconOnly` for an icon-only button never a `data-icon` attribute.
128
+ Never absolutely-position a `Button` over an `Input`. Compose `InputGroup` + `InputGroupButton` (or `InputGroupAddon` for non-interactive affordances). `InputGroupButton` inherits the group's size and state, so you don't set `size` on it. With a text label pass the icon through `leadingIcon`; for an icon-only button set `iconOnly` and pass the icon as the child (like `Button`, `iconOnly` ignores `leadingIcon`) - never a `data-icon` attribute.
126
129
 
127
130
  **Incorrect:**
128
131
 
@@ -143,7 +146,9 @@ import { RiSearchLine } from "@create-ui/assets/icons"
143
146
 
144
147
  <InputGroup>
145
148
  <InputGroupControl placeholder="Search…" />
146
- <InputGroupButton iconOnly aria-label="Search" leadingIcon={<RiSearchLine />} />
149
+ <InputGroupButton iconOnly aria-label="Search">
150
+ <RiSearchLine />
151
+ </InputGroupButton>
147
152
  </InputGroup>
148
153
  ```
149
154
 
@@ -195,7 +200,7 @@ import { SegmentedControl, SegmentedControlItem } from "@/components/ui/segmente
195
200
  </SegmentedControl>
196
201
  ```
197
202
 
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.
203
+ `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` - `grouped` wraps the items in a padded `bg-weak` container, the classic pricing-toggle look; the active pill slides with an animated indicator in **both** appearances). Items take `leadingIcon` / `trailingIcon` / `iconOnly`. When more than one option can be active at once, that's not a segmented control - stack `CheckboxGroup` (or `SwitchGroup`) rows instead.
199
204
 
200
205
  Wrap a labelled segmented control in a `Field` and connect them with `aria-labelledby`:
201
206
 
@@ -213,17 +218,39 @@ import { SegmentedControl, SegmentedControlItem } from "@/components/ui/segmente
213
218
  </Field>
214
219
  ```
215
220
 
221
+ The `Field` here must be `orientation="horizontal"` (or wrap the control in a plain `div`): a vertical Field applies `[&>*]:w-full` to every direct child, so an intrinsic-width control like `SegmentedControl` gets stretched to the full field width - `self-start` does not prevent it.
222
+
223
+ ---
224
+
225
+ ## Switches are richer than the bare default
226
+
227
+ `Switch` is a Radix switch (`checked` / `onCheckedChange` / `defaultChecked`) with a much richer API than `<Switch />`: `variant` (`primary` | `info` | `neutral` | `inverse` | `semantic` - semantic is red when off, green when on), `size` (`xs` | `sm` | `md`), `shape` (`pill` | `rounded`), `thumbType` (`short` | `long`), and the `ioTrigger` / `thumbIcon` booleans for I/O glyphs and check/close icons. Bare defaults are for dense forms; settings pages and showcases should pick deliberate options:
228
+
229
+ ```tsx
230
+ <Field size="md" orientation="horizontal">
231
+ <FieldLabel htmlFor="2fa">Enforce two-factor auth</FieldLabel>
232
+ <Switch id="2fa" thumbIcon defaultChecked />
233
+ </Field>
234
+
235
+ <Switch variant="semantic" thumbType="long" ioTrigger aria-label="Accept" />
236
+ ```
237
+
238
+ `Field` size does not cascade to `Switch` (it reads only `SwitchContext`) - pair them explicitly: a default (sm) `Field` row takes `<Switch size="sm">`, and a bare (md) `Switch` belongs in a `<Field size="md">` row. `SwitchGroup` pairs the two automatically.
239
+
240
+ Apply-immediately settings rows use `SwitchGroup` - one labelled row per switch; there is no `SwitchGroupItem` (compose `Switch` + `FieldContent` + `LabelMain` per `reference/switch-group.md`). Inside a submit-to-save form prefer `Checkbox` / `CheckboxGroup`: a switch implies the setting takes effect the moment it flips. If a task explicitly demands toggles plus a Save button, keep the toggles deliberately - but don't present that as the default form pattern.
241
+
216
242
  ---
217
243
 
218
244
  ## Dropdowns use Select
219
245
 
220
- `Select` is composed from Radix parts: `SelectTrigger`, `SelectValue`, `SelectContent`, and `SelectItem`. There is no `items` prop render `SelectItem` children. Inside a `Field`, the trigger inherits the field's size, invalid, and disabled state automatically, so set them on `Field`, not on each part.
246
+ `Select` is composed from Radix parts: `SelectTrigger`, `SelectValue`, `SelectContent`, and `SelectItem`. There is no `items` prop - render `SelectItem` children. Inside a `Field`, the trigger inherits the field's size, invalid, and disabled state automatically, so set them on `Field`, not on each part.
221
247
 
222
248
  ```tsx
223
249
  import { Field, FieldLabel } from "@/components/ui/field"
224
250
  import {
225
251
  Select,
226
252
  SelectContent,
253
+ SelectGroup,
227
254
  SelectItem,
228
255
  SelectTrigger,
229
256
  SelectValue,
@@ -236,21 +263,23 @@ import {
236
263
  <SelectValue placeholder="Select a role" />
237
264
  </SelectTrigger>
238
265
  <SelectContent>
239
- <SelectItem value="admin">Admin</SelectItem>
240
- <SelectItem value="editor">Editor</SelectItem>
241
- <SelectItem value="viewer">Viewer</SelectItem>
266
+ <SelectGroup>
267
+ <SelectItem value="admin">Admin</SelectItem>
268
+ <SelectItem value="editor">Editor</SelectItem>
269
+ <SelectItem value="viewer">Viewer</SelectItem>
270
+ </SelectGroup>
242
271
  </SelectContent>
243
272
  </Select>
244
273
  </Field>
245
274
  ```
246
275
 
247
- `Select` accepts `size` (`"xs" | "sm" | "md"`), `invalid`, `disabled`, and `loading`. When it's not inside a `Field`, set these on the `Select` itself.
276
+ `Select` accepts `size` (`"xs" | "sm" | "md"`), `invalid`, `disabled`, and `loading`. When it's not inside a `Field`, set these on the `Select` itself; inside a `Field` they cascade automatically - don't re-set them. Inside an `InputGroup`, also pass `variant="compact"` yourself - only the dedicated `InputGroupSelect` applies compact automatically.
248
277
 
249
278
  ---
250
279
 
251
280
  ## FieldSet + FieldLegend for grouping related fields
252
281
 
253
- Use `FieldSet` + `FieldLegend` to group related checkboxes, radios, or switches not a `div` with a heading. `FieldLegend` takes `variant="legend"` (default, section-sized) or `variant="label"` (form-control-sized).
282
+ Use `FieldSet` + `FieldLegend` to group related checkboxes, radios, or switches - not a `div` with a heading. `FieldLegend` takes `variant="legend"` (default, section-sized) or `variant="label"` (form-control-sized).
254
283
 
255
284
  ```tsx
256
285
  import {
@@ -3,7 +3,9 @@
3
3
  ## Contents
4
4
 
5
5
  - [Icons and assets come from @create-ui/assets](#icons-and-assets-come-from-create-uiassets)
6
- - [Icons in Button use the leadingIcon / trailingIcon props](#icons-in-button-use-the-leadingicon--trailingicon-props)
6
+ - [Icon props across components](#icon-props-across-components)
7
+ - [Chip takes its icon as the first child](#chip-takes-its-icon-as-the-first-child)
8
+ - [InlineAlert and Toast use icon subcomponents](#inlinealert-and-toast-use-icon-subcomponents)
7
9
  - [Icon-only buttons](#icon-only-buttons)
8
10
  - [No sizing classes on icons inside components](#no-sizing-classes-on-icons-inside-components)
9
11
  - [Pass icons as component values, not string keys](#pass-icons-as-component-values-not-string-keys)
@@ -12,7 +14,7 @@
12
14
 
13
15
  ## Icons and assets come from @create-ui/assets
14
16
 
15
- **All icons and marks come from `@create-ui/assets`** Create UI's own asset library. Registry components already import from it, and the CLI ships those imports as-is. Never reach for `lucide-react`, `@tabler/icons-react`, or any other icon package.
17
+ **All icons and marks come from `@create-ui/assets`** - Create UI's own asset library. Registry components already import from it, and the CLI ships those imports as-is. Never reach for `lucide-react`, `@tabler/icons-react`, or any other icon package.
16
18
 
17
19
  **General-purpose UI icons** → `@create-ui/assets/icons` (Remix Icon, `Ri*` names). This subpath re-exports [`@remixicon/react`](https://remixicon.com), so the full Remix set is available:
18
20
 
@@ -29,15 +31,15 @@ import { VisaColor } from "@create-ui/assets/payments"
29
31
  import { Docker } from "@create-ui/assets/brands"
30
32
  ```
31
33
 
32
- Subpaths: `icons` (Remix UI icons), `social`, `flags`, `payments`, `brands`, `banks`, `badges`, `crypto` (Web3 Icons). Marks that ship multiple treatments carry a suffix pick the variant: `VisaColor` / `VisaBlack` / `VisaWhite`.
34
+ Subpaths: `icons` (Remix UI icons), `social`, `flags`, `payments`, `brands`, `banks`, `badges`, `crypto` (Web3 Icons). Marks that ship multiple treatments carry a suffix - pick the variant: `VisaColor` / `VisaBlack` / `VisaWhite`.
33
35
 
34
- **Sizing & color differ from UI icons.** Standalone marks have their source dimensions stripped, so set a size explicitly (`className="size-6"` or `width`/`height`), and their brand colors are baked in don't recolor; use the `Black` / `White` variant for contrast. This is the opposite of `Ri*` icons rendered *inside* Create UI components (`Button`, `DropdownMenuItem`, …), which are auto-sized via CVA see [No sizing classes on icons inside components](#no-sizing-classes-on-icons-inside-components).
36
+ **Sizing & color differ from UI icons.** Standalone marks have their source dimensions stripped, so set a size explicitly - and match the mark's aspect ratio: social/brand/crypto/badge marks are square 24×24 (`className="size-6"`), but payment marks are wide (most are 38×24; the `*Logotype` variants 58×24) and need paired classes like `className="h-5 w-8 shrink-0"`, never a square `size-*`. Brand colors are baked in - don't recolor; use the `Black` / `White` variant for contrast. This is the opposite of `Ri*` icons rendered *inside* Create UI components (`Button`, `DropdownMenuItem`, …), which are auto-sized via CVA - see [No sizing classes on icons inside components](#no-sizing-classes-on-icons-inside-components).
35
37
 
36
38
  ---
37
39
 
38
- ## Icons in Button use the leadingIcon / trailingIcon props
40
+ ## Icon props across components
39
41
 
40
- `Button` accepts icons through the `leadingIcon` and `trailingIcon` props not as children. The button sizes the icon automatically per `size` (via its CVA `[&_svg]:size-N`), so the icon never needs a sizing class. There is no `data-icon` attribute in Create UI.
42
+ **Every component that supports inline icons takes them through the `leadingIcon` / `trailingIcon` props - never as children next to text.** This applies to `Button`, `Badge`, `SegmentedControlItem`, `TabMenuItem`, `BreadcrumbItem`, `TextLink`, and `ButtonGroupItem` (the generated icon matrix in SKILL.md is the authoritative list). The component sizes the icon automatically per `size` (via its CVA `[&_svg]:size-N`), so the icon never needs a sizing class. There is no `data-icon` attribute in Create UI.
41
43
 
42
44
  **Incorrect:**
43
45
 
@@ -47,10 +49,14 @@ Subpaths: `icons` (Remix UI icons), `social`, `flags`, `payments`, `brands`, `ba
47
49
  Search
48
50
  </Button>
49
51
 
50
- <Button>
51
- Next
52
- <RiArrowRightLine className="ml-2 size-4" />
53
- </Button>
52
+ <Badge variant="primary" appearance="soft">
53
+ <RiSparklingFill />
54
+ Built with Create UI
55
+ </Badge>
56
+
57
+ <SegmentedControlItem value="grid">
58
+ <RiLayoutGridFill /> Grid
59
+ </SegmentedControlItem>
54
60
  ```
55
61
 
56
62
  **Correct:**
@@ -58,36 +64,76 @@ Subpaths: `icons` (Remix UI icons), `social`, `flags`, `payments`, `brands`, `ba
58
64
  ```tsx
59
65
  <Button leadingIcon={<RiSearchLine />}>Search</Button>
60
66
 
61
- <Button trailingIcon={<RiArrowRightLine />}>Next</Button>
67
+ <Badge variant="primary" appearance="soft" leadingIcon={<RiSparklingFill />}>
68
+ Built with Create UI
69
+ </Badge>
70
+
71
+ <SegmentedControlItem value="grid" leadingIcon={<RiLayoutGridFill />}>
72
+ Grid
73
+ </SegmentedControlItem>
74
+
75
+ <TabMenuItem value="billing" label="Billing" leadingIcon={<RiBankCardLine />} />
76
+ ```
77
+
78
+ The icon-as-children cases are: `iconOnly` mode on `Button`, `Badge`, `SegmentedControlItem`, and `ButtonGroupItem` (the icon IS the children there - `leadingIcon` / `trailingIcon` are ignored), and menu-ish items like `DropdownMenuItem` / `CommandItem`, where a bare `<RiIcon />` child before the text is the convention. All four `iconOnly` components follow the same rule, so `<SegmentedControlItem iconOnly aria-label="Grid"><RiLayoutGridFill /></SegmentedControlItem>`. When in doubt, check `reference/<component>.md`.
79
+
80
+ ---
81
+
82
+ ## Chip takes its icon as the first child
83
+
84
+ `Chip` has **no** `leadingIcon` / `trailingIcon` props. Its first element child (an icon or an `Avatar`) is auto-slotted into the lead position and sized by the chip; the close affordance comes from `closable` / `onClose`.
85
+
86
+ ```tsx
87
+ <Chip closable onClose={remove}><RiUserLine />Ayse Yilmaz</Chip>
88
+ <Chip><Avatar size="2xs"><AvatarText>AY</AvatarText></Avatar>Ayse</Chip>
62
89
  ```
63
90
 
64
91
  ---
65
92
 
93
+ ## InlineAlert and Toast use icon subcomponents
94
+
95
+ `InlineAlert` and `Toast` take their icon through dedicated subcomponents, not props:
96
+
97
+ ```tsx
98
+ <InlineAlert variant="danger" appearance="soft">
99
+ <InlineAlertIcon><RiErrorWarningFill /></InlineAlertIcon>
100
+ <InlineAlertContent>
101
+ <InlineAlertTitle>Payment failed</InlineAlertTitle>
102
+ </InlineAlertContent>
103
+ </InlineAlert>
104
+ ```
105
+
106
+ `FieldHelper` is the exception that takes an `icon` prop.
107
+
108
+ ---
109
+
66
110
  ## Icon-only buttons
67
111
 
68
- For a button that shows only an icon, set `iconOnly`, pass the icon via `leadingIcon`, and always supply an `aria-label`. There is no separate icon-button component. For a close affordance, use the `close-button` component instead.
112
+ For a button that shows only an icon, set `iconOnly`, pass the icon as the CHILD, and always supply an `aria-label`. In `iconOnly` mode `leadingIcon` / `trailingIcon` are silently ignored - a `<Button iconOnly leadingIcon={...} />` renders empty. There is no separate icon-button component. For a close affordance, use the `close-button` component instead.
69
113
 
70
114
  **Incorrect:**
71
115
 
72
116
  ```tsx
73
- <Button>
74
- <RiSearchLine className="size-4" />
75
- </Button>
117
+ <Button iconOnly aria-label="Search" leadingIcon={<RiSearchLine />} />
76
118
  ```
77
119
 
78
120
  **Correct:**
79
121
 
80
122
  ```tsx
81
- <Button iconOnly aria-label="Search" leadingIcon={<RiSearchLine />} />
123
+ <Button iconOnly aria-label="Search">
124
+ <RiSearchLine />
125
+ </Button>
82
126
 
83
- <Button iconOnly aria-label="More options" appearance="ghost" leadingIcon={<RiMore2Line />} />
127
+ <Button iconOnly aria-label="More options" appearance="ghost">
128
+ <RiMore2Line />
129
+ </Button>
84
130
  ```
85
131
 
86
132
  ---
87
133
 
88
134
  ## No sizing classes on icons inside components
89
135
 
90
- Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons rendered inside `Button`, `DropdownMenuItem`, `InlineAlert`, `Toast`, or other Create UI components unless the user explicitly asks for a custom icon size.
136
+ Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons rendered inside `Button`, `DropdownMenuItem`, `InlineAlert`, `Toast`, or other Create UI components - unless the user explicitly asks for a custom icon size.
91
137
 
92
138
  **Incorrect:**
93
139