@create-ui/cli 0.5.7 → 0.5.9
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.
- package/dist/{chunk-RMTTHCB3.js → chunk-2ELKDGGM.js} +3 -3
- package/dist/{chunk-RMTTHCB3.js.map → chunk-2ELKDGGM.js.map} +1 -1
- package/dist/chunk-643QI2I2.js +102 -0
- package/dist/chunk-643QI2I2.js.map +1 -0
- package/dist/{chunk-NQFMXHMH.js → chunk-KQTXDVKV.js} +3 -3
- package/dist/chunk-KQTXDVKV.js.map +1 -0
- package/dist/index.js +35 -35
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/skills/createui/SKILL.md +201 -177
- package/dist/skills/createui/agents/openai.yml +1 -1
- package/dist/skills/createui/cli.md +42 -42
- package/dist/skills/createui/customization.md +20 -15
- package/dist/skills/createui/evals/evals.json +68 -5
- package/dist/skills/createui/mcp.md +14 -5
- package/dist/skills/createui/reference/accordion.md +127 -0
- package/dist/skills/createui/reference/app-store-badge.md +88 -0
- package/dist/skills/createui/reference/aspect-ratio.md +52 -0
- package/dist/skills/createui/reference/avatar.md +230 -0
- package/dist/skills/createui/reference/badge.md +110 -0
- package/dist/skills/createui/reference/breadcrumb.md +153 -0
- package/dist/skills/createui/reference/button-group.md +116 -0
- package/dist/skills/createui/reference/button.md +104 -0
- package/dist/skills/createui/reference/checkbox-group.md +118 -0
- package/dist/skills/createui/reference/checkbox.md +79 -0
- package/dist/skills/createui/reference/chip.md +115 -0
- package/dist/skills/createui/reference/close-button.md +83 -0
- package/dist/skills/createui/reference/command.md +69 -0
- package/dist/skills/createui/reference/country-flag.md +109 -0
- package/dist/skills/createui/reference/credit-card-input.md +76 -0
- package/dist/skills/createui/reference/date-input.md +71 -0
- package/dist/skills/createui/reference/dropdown-menu.md +164 -0
- package/dist/skills/createui/reference/field.md +186 -0
- package/dist/skills/createui/reference/info-tooltip.md +110 -0
- package/dist/skills/createui/reference/inline-alert.md +146 -0
- package/dist/skills/createui/reference/input-group.md +171 -0
- package/dist/skills/createui/reference/input-otp.md +130 -0
- package/dist/skills/createui/reference/input-stepper.md +120 -0
- package/dist/skills/createui/reference/input.md +118 -0
- package/dist/skills/createui/reference/label.md +121 -0
- package/dist/skills/createui/reference/pagination.md +157 -0
- package/dist/skills/createui/reference/password-strength.md +70 -0
- package/dist/skills/createui/reference/phone-input.md +77 -0
- package/dist/skills/createui/reference/progress.md +158 -0
- package/dist/skills/createui/reference/radio-group.md +133 -0
- package/dist/skills/createui/reference/radio.md +79 -0
- package/dist/skills/createui/reference/scroll-area.md +212 -0
- package/dist/skills/createui/reference/segmented-control.md +146 -0
- package/dist/skills/createui/reference/select.md +204 -0
- package/dist/skills/createui/reference/separator.md +99 -0
- package/dist/skills/createui/reference/social-login-button.md +130 -0
- package/dist/skills/createui/reference/spinner.md +68 -0
- package/dist/skills/createui/reference/status-badge.md +89 -0
- package/dist/skills/createui/reference/switch-group.md +122 -0
- package/dist/skills/createui/reference/switch.md +75 -0
- package/dist/skills/createui/reference/tab-menu.md +165 -0
- package/dist/skills/createui/reference/text-link.md +84 -0
- package/dist/skills/createui/reference/textarea.md +50 -0
- package/dist/skills/createui/reference/toast.md +162 -0
- package/dist/skills/createui/reference/tooltip.md +63 -0
- package/dist/skills/createui/rules/composition.md +41 -25
- package/dist/skills/createui/rules/design.md +266 -0
- package/dist/skills/createui/rules/forms.md +44 -15
- package/dist/skills/createui/rules/icons.md +64 -18
- package/dist/skills/createui/rules/styling.md +53 -14
- package/dist/utils/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-M5DYT2NE.js +0 -64
- package/dist/chunk-M5DYT2NE.js.map +0 -1
- 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`
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
|
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.
|
|
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"
|
|
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
|
|
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
|
|
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
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
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
|
-
- [
|
|
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`**
|
|
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
|
|
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"`
|
|
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
|
-
##
|
|
40
|
+
## Icon props across components
|
|
39
41
|
|
|
40
|
-
|
|
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
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</
|
|
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
|
-
<
|
|
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
|
|
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"
|
|
123
|
+
<Button iconOnly aria-label="Search">
|
|
124
|
+
<RiSearchLine />
|
|
125
|
+
</Button>
|
|
82
126
|
|
|
83
|
-
<Button iconOnly aria-label="More options" appearance="ghost"
|
|
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
|
|
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
|
|