@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,77 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/phone-input.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/phone-input.md -->
2
+
3
+ # phone-input
4
+
5
+ Phone number input with a country flag select and dial code prefix; onValueChange emits the E.164 value
6
+
7
+ Install: `npx @create-ui/cli add phone-input`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { PhoneInput } from "@/components/ui/phone-input"
13
+ ```
14
+
15
+ ## PhoneInput props
16
+
17
+ | Prop | Type | Default |
18
+ | --- | --- | --- |
19
+ | defaultValue | `string` (E.164 or national digits; the dial code is stripped only when it matches the active country, so pair it with the matching defaultCountry) | - |
20
+ | defaultCountry | `PhoneCountryCode` | `DE` |
21
+ | country | `PhoneCountryCode` | - |
22
+ | onCountryChange | `(country: PhoneCountryCode) => void` | - |
23
+ | onValueChange | `(value: string, details: PhoneInputDetails) => void` (Emits the full E.164 string ('' when empty) plus { country, dialCode, nationalNumber }; re-fires immediately when the country changes) | - |
24
+ | countries | `PhoneCountryConfig[]` (Subset or reorder the dropdown; build entries from PHONE_COUNTRIES exported by use-phone-input (default: every country, sorted by English name)) | - |
25
+ | size | `xs \| sm \| md` (Omit inside Field; InputGroup resolves it from Field context (falls back to sm)) | - |
26
+ | invalid | `boolean` | - |
27
+ | disabled | `boolean` | - |
28
+ | loading | `boolean` | - |
29
+ | showHelperIcon | `boolean` | `true` |
30
+ | helperIcon | `ReactNode` | - |
31
+ | helperTooltip | `ReactNode` (Pass null to keep the helper icon without a tooltip; hide the icon entirely with showHelperIcon={false}) | `Enter your phone number without the country code.` |
32
+ | className | `string` | - |
33
+ | ref | `React.Ref<HTMLInputElement>` | - |
34
+
35
+ Extends `React.ComponentProps<"input">`.
36
+
37
+ ## Examples
38
+
39
+ From `input-group-phone`:
40
+
41
+ ```tsx
42
+ "use client"
43
+
44
+ import { Field, FieldLabel } from "@/components/ui/field"
45
+ import { PhoneInput } from "@/components/ui/phone-input"
46
+
47
+ export default function InputGroupPhone() {
48
+ return (
49
+ <Field className="max-w-md">
50
+ <FieldLabel htmlFor="input-group-phone">Phone Number</FieldLabel>
51
+ <PhoneInput
52
+ id="input-group-phone"
53
+ defaultCountry="US"
54
+ onValueChange={(value, { country }) => console.log(value, country)}
55
+ />
56
+ </Field>
57
+ )
58
+ }
59
+ ```
60
+
61
+ More: `npx @create-ui/cli view phone-input` or MCP `get_item_examples_from_registries` with "phone-input-demo" / "phone-input-example".
62
+
63
+ ## When to use
64
+ Complete phone-number field: country flag select, display-only dial code prefix, auto-formatting national-digit input, and an E.164 value via onValueChange. Use it whenever a form collects a phone number; do not rebuild it from InputGroup + InputGroupSelect + CountryFlag, because it already is exactly that composition. For other prefix-plus-select combos (amount, currency) compose InputGroup directly, and for plain text use Input.
65
+
66
+ ## Gotchas
67
+ - It renders its own InputGroup; never nest it inside another InputGroup. Wrap it in Field for the label, and leave size/invalid/disabled/loading unset there so they cascade from Field context.
68
+ - The text value cannot be controlled: native value/onChange are stripped from the prop type. Seed with defaultValue and read through onValueChange. Only the country supports controlled mode (country + onCountryChange).
69
+ - The country is never auto-detected from the value (unlike react-phone-number-input). The dial code in defaultValue is stripped only when it matches the active country, so an E.164 defaultValue must ship with the matching defaultCountry or the dial digits leak into the national number:
70
+
71
+ ```tsx
72
+ <PhoneInput defaultCountry="US" defaultValue="+15551234567" />
73
+ ```
74
+
75
+ - The visible input holds national digits only, live-formatted per country by libphonenumber's AsYouType; the dial code is a display-only span. The E.164 string exists solely in the onValueChange payload, and an empty input emits "" rather than the bare dial code.
76
+ - Switching country keeps the typed digits and instantly re-fires onValueChange with the new dial code applied to the same national number.
77
+ - Non-digit keystrokes are stripped on input; do not layer your own masking or formatting on top.
@@ -0,0 +1,158 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/progress.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/progress.md -->
2
+
3
+ # progress
4
+
5
+ Determinate progress as a line or circle via the type prop; solid or gradient appearance, value/max with animated fill
6
+
7
+ Install: `npx @create-ui/cli add progress`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { Progress } from "@/components/ui/progress"
13
+ ```
14
+
15
+ Also exported: `progressLineVariants`
16
+
17
+ ## Progress props
18
+
19
+ | Prop | Type | Default |
20
+ | --- | --- | --- |
21
+ | type | `line \| circle` | `line` |
22
+ | variant | `primary \| info \| success \| warning \| danger \| away \| neutral \| neutral-static \| neutral-soft \| inverse \| inverse-static \| inverse-soft` | `primary` |
23
+ | appearance | `solid \| gradient` | `solid` |
24
+ | size | `xs \| sm \| md \| lg` (Line: bar thickness; circle: fixed outer diameter (12/16/20/24px) with matching stroke width) | `md` |
25
+ | shape | `sharp \| pill` (Visual effect on line only; the circle ring renders identically and merely emits data-shape) | `pill` |
26
+ | value | `number` | `0` |
27
+ | max | `number` (Values <= 0 silently fall back to 100; value is clamped to 0..max for both fill width and aria-valuenow) | `100` |
28
+ | duration | `number` (Line only; overrides the default 300ms ease-out width transition. type=circle discards it (the ring has no transition at all)) | - |
29
+
30
+ Extends `React.ComponentProps<"div">`.
31
+
32
+ ## Examples
33
+
34
+ From `progress-demo`:
35
+
36
+ ```tsx
37
+ import { Progress } from "@/components/ui/progress"
38
+
39
+ export default function ProgressDemo() {
40
+ return (
41
+ <div className="flex items-center gap-8">
42
+ <div className="w-64">
43
+ <Progress value={42} />
44
+ </div>
45
+ <Progress type="circle" size="lg" value={42} />
46
+ </div>
47
+ )
48
+ }
49
+ ```
50
+
51
+ From `progress-upload`:
52
+
53
+ ```tsx
54
+ "use client"
55
+
56
+ import * as React from "react"
57
+ import {
58
+ RiCheckLine,
59
+ RiRefreshLine,
60
+ RiUploadCloud2Line,
61
+ } from "@create-ui/assets/icons"
62
+
63
+ import { Button } from "@/components/ui/button"
64
+ import { Progress } from "@/components/ui/progress"
65
+
66
+ const DURATION_MS = 2000
67
+
68
+ type Status = "idle" | "uploading" | "done" | "error"
69
+
70
+ export default function ProgressUpload() {
71
+ const [attempt, setAttempt] = React.useState(0)
72
+ const [status, setStatus] = React.useState<Status>("idle")
73
+ const [progress, setProgress] = React.useState(0)
74
+
75
+ React.useEffect(() => {
76
+ if (status !== "uploading") return
77
+
78
+ // First attempt fails to demo the retry flow, later attempts succeed.
79
+ const willFail = attempt === 1
80
+ const target = willFail ? 60 : 100
81
+
82
+ const raf = requestAnimationFrame(() => setProgress(target))
83
+ const timeout = setTimeout(
84
+ () => setStatus(willFail ? "error" : "done"),
85
+ DURATION_MS
86
+ )
87
+
88
+ return () => {
89
+ cancelAnimationFrame(raf)
90
+ clearTimeout(timeout)
91
+ }
92
+ }, [status, attempt])
93
+
94
+ function startUpload() {
95
+ setProgress(0)
96
+ setAttempt((a) => a + 1)
97
+ setStatus("uploading")
98
+ }
99
+
100
+ const isUploading = status === "uploading"
101
+ const isDone = status === "done"
102
+ const isError = status === "error"
103
+
104
+ const variant = isDone ? "success" : isError ? "danger" : "primary"
105
+
106
+ return (
107
+ <div className="flex w-80 items-center gap-4">
108
+ <Progress
109
+ key={attempt}
110
+ value={progress}
111
+ duration={DURATION_MS}
112
+ variant={variant}
113
+ />
114
+ <Button
115
+ variant={variant}
116
+ loading={isUploading}
117
+ leadingIcon={
118
+ isDone ? (
119
+ <RiCheckLine />
120
+ ) : isError ? (
121
+ <RiRefreshLine />
122
+ ) : (
123
+ <RiUploadCloud2Line />
124
+ )
125
+ }
126
+ onClick={startUpload}
127
+ className="w-32 shrink-0"
128
+ >
129
+ {isDone
130
+ ? "Done"
131
+ : isError
132
+ ? "Retry"
133
+ : isUploading
134
+ ? "Uploading"
135
+ : "Upload"}
136
+ </Button>
137
+ </div>
138
+ )
139
+ }
140
+ ```
141
+
142
+ More: `npx @create-ui/cli view progress` or MCP `get_item_examples_from_registries` with "progress-demo" / "progress-example".
143
+
144
+ ## When to use
145
+ Determinate progress against a known total: uploads, multi-step flows, download bars, and compact dashboard stat rings (type="circle"). Do not use it when duration or total is unknown; use Spinner for indeterminate loading. For a passive status or count that does not track completion, use Badge or Status Badge.
146
+
147
+ ## Gotchas
148
+ - Single self-closing component built on plain divs, not Radix: there is no Root/Indicator subcomponent and `children` is type-omitted, so the shadcn pattern `<Progress><ProgressIndicator /></Progress>` will not compile. Render labels or percentage text outside the component.
149
+ - `type="line"` is `w-full` and stretches to fill its parent; always wrap it in a width-constrained container. `type="circle"` sets width/height via inline style from `size`, so className width utilities cannot resize the ring.
150
+ - Every `value` change animates the line width. To restart a fill from 0 (e.g. a retry), remount with a `key` and set the target value on the next frame, as progress-upload does; resetting value in place animates the bar backward first.
151
+
152
+ ```tsx
153
+ <Progress key={attempt} value={progress} duration={2000} variant={isError ? "danger" : "primary"} />
154
+ ```
155
+
156
+ - The root carries `role="progressbar"` and aria-valuemin/max/now automatically but has no accessible name; pass `aria-label` yourself (div props pass through).
157
+ - The circle is pure CSS (conic-gradient arc + radial mask), no SVG, so there are no stroke props. The arc paints with `currentColor`; a `text-*` class on the component recolors it (the inline-styled indicator cannot be themed via `data-slot` CSS).
158
+ - `inverse*` variants render in white/static-white tones for dark surfaces; the examples place them on a `bg-strongest` canvas.
@@ -0,0 +1,133 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/radio-group.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/radio-group.md -->
2
+
3
+ # radio-group
4
+
5
+ Radix radio group wrapped in a Field; cascades variant and size to Radio items, placement left or right
6
+
7
+ Install: `npx @create-ui/cli add radio-group`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { RadioGroup } from "@/components/ui/radio-group"
13
+ ```
14
+
15
+ Also exported: `radioGroupVariants`
16
+
17
+ ## RadioGroup props
18
+
19
+ | Prop | Type | Default |
20
+ | --- | --- | --- |
21
+ | variant | `primary \| neutral \| danger` | `primary` |
22
+ | size | `xs \| sm \| md` | `md` |
23
+ | placement | `left \| right` (right only sets flex-row-reverse on the single Field row; in a stacked flex-col layout add flex-row-reverse to each per-option wrapper div instead) | `left` |
24
+ | disabled | `boolean` (Styles the inner Field only; it is NOT forwarded to the Radix root, so also set disabled on each Radio to actually block interaction) | - |
25
+ | invalid | `boolean` (Forwarded to the inner Field; variant='danger' does not set it for you - pass both together for a full error state) | - |
26
+ | fieldClassName | `string` (Layout lives here: fieldClassName='flex-col items-stretch gap-4' is the stacked-list recipe; className only styles the outer Radix root) | - |
27
+
28
+ Extends `React.ComponentProps<typeof RadioGroupPrimitive.Root>`.
29
+
30
+ ## Examples
31
+
32
+ From `radio-group-demo`:
33
+
34
+ ```tsx
35
+ import { FieldContent } from "@/components/ui/field"
36
+ import { Label, LabelDescription, LabelMain } from "@/components/ui/label"
37
+ import { Radio } from "@/components/ui/radio"
38
+ import { RadioGroup } from "@/components/ui/radio-group"
39
+
40
+ const options = [
41
+ { value: "free", title: "Free", description: "For personal projects." },
42
+ {
43
+ value: "pro",
44
+ title: "Pro",
45
+ description: "For freelancers and small teams.",
46
+ },
47
+ {
48
+ value: "team",
49
+ title: "Team",
50
+ description: "For larger teams with shared workspaces.",
51
+ },
52
+ ]
53
+
54
+ export default function RadioGroupDemo() {
55
+ return (
56
+ <RadioGroup
57
+ defaultValue="pro"
58
+ className="w-[340px]"
59
+ fieldClassName="flex-col items-stretch gap-4"
60
+ >
61
+ {options.map((option) => (
62
+ <div key={option.value} className="flex items-start gap-2">
63
+ <Radio id={`plan-${option.value}`} value={option.value} />
64
+ <FieldContent>
65
+ <LabelMain>
66
+ <Label htmlFor={`plan-${option.value}`}>{option.title}</Label>
67
+ <LabelDescription>{option.description}</LabelDescription>
68
+ </LabelMain>
69
+ </FieldContent>
70
+ </div>
71
+ ))}
72
+ </RadioGroup>
73
+ )
74
+ }
75
+ ```
76
+
77
+ From `radio-group-controlled`:
78
+
79
+ ```tsx
80
+ "use client"
81
+
82
+ import * as React from "react"
83
+
84
+ import { FieldContent } from "@/components/ui/field"
85
+ import { Label, LabelMain } from "@/components/ui/label"
86
+ import { Radio } from "@/components/ui/radio"
87
+ import { RadioGroup } from "@/components/ui/radio-group"
88
+
89
+ const options = ["light", "dark", "system"] as const
90
+
91
+ export default function RadioGroupControlled() {
92
+ const [value, setValue] = React.useState<(typeof options)[number]>("system")
93
+
94
+ return (
95
+ <div className="flex flex-col gap-3">
96
+ <RadioGroup
97
+ value={value}
98
+ onValueChange={(next) => setValue(next as (typeof options)[number])}
99
+ className="w-[280px]"
100
+ fieldClassName="flex-col items-stretch gap-3"
101
+ >
102
+ {options.map((option) => (
103
+ <div key={option} className="flex items-start gap-2">
104
+ <Radio id={`theme-${option}`} value={option} />
105
+ <FieldContent>
106
+ <LabelMain>
107
+ <Label htmlFor={`theme-${option}`}>
108
+ {option.charAt(0).toUpperCase() + option.slice(1)}
109
+ </Label>
110
+ </LabelMain>
111
+ </FieldContent>
112
+ </div>
113
+ ))}
114
+ </RadioGroup>
115
+ <p className="text-placeholder text-ui-control-sm">
116
+ Selected: <span className="text-body font-medium">{value}</span>
117
+ </p>
118
+ </div>
119
+ )
120
+ }
121
+ ```
122
+
123
+ More: `npx @create-ui/cli view radio-group` or MCP `get_item_examples_from_registries` with "radio-group-demo" / "radio-group-example".
124
+
125
+ ## When to use
126
+ Single-select group where all options are visible at once (plan picker, theme, sort order). For multi-select use CheckboxGroup; for long collapsible lists use Select; for a lone on/off use Switch or Checkbox.
127
+ ## Gotchas
128
+ - There is no `RadioGroupItem` (shadcn divergence): the item is the separate `Radio` component, and each option is composed manually as a wrapper div with `Radio` plus `FieldContent > LabelMain > Label / LabelDescription` (see radio-group-demo).
129
+ - The component renders ONE `Field` wrapping all children, not a Field per option; `Label`/`LabelDescription` sizing and disabled/invalid styling all cascade from that single Field context.
130
+ - `variant` and `size` cascade to every child `Radio` via `RadioContext`; an explicit prop on a `Radio` wins (`variant ?? ctx ?? default`). Radio's `success`/`inverse` variants cannot be set at group level - pass them per `Radio`.
131
+ - `variant="danger"` recolors every descendant `[data-slot=field-footer]` to `text-error-base`, including per-option footers nested in `FieldContent`.
132
+ - The Radix root sets `orientation="horizontal"` and `display: flex`; a vertical list comes purely from `fieldClassName="flex-col items-stretch ..."`, not from any orientation or layout prop.
133
+ - Group `disabled` never reaches the Radix root, so radios stay interactive unless each `Radio` also gets `disabled` (the disabled demos in radio-group-example set both).
@@ -0,0 +1,79 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/radio.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/radio.md -->
2
+
3
+ # radio
4
+
5
+ Single radio item for use inside RadioGroup; five variants and xs/sm/md sizes inherit from group context
6
+
7
+ Install: `npx @create-ui/cli add radio`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { Radio } from "@/components/ui/radio"
13
+ ```
14
+
15
+ Also exported: `RadioContext`, `radioVariants`, `radioIndicatorVariants`
16
+
17
+ ## Radio props
18
+
19
+ | Prop | Type | Default |
20
+ | --- | --- | --- |
21
+ | variant | `primary \| neutral \| danger \| success \| inverse` (Own prop beats RadioGroup context; the only way to get success or inverse inside a group, since RadioGroup's own variant prop offers only primary/neutral/danger) | `primary` |
22
+ | size | `xs \| sm \| md` (Resolved as own prop ?? RadioGroup context ?? md; the CVA defaultVariants sm is dead code because a resolved value is always passed) | `md` |
23
+
24
+ Extends `React.ComponentProps<typeof RadioGroupPrimitive.Item>`.
25
+
26
+ ## Examples
27
+
28
+ From `radio-demo`:
29
+
30
+ ```tsx
31
+ import { Radio } from "@/components/ui/radio"
32
+ import { RadioGroup } from "@/components/ui/radio-group"
33
+
34
+ export default function RadioDemo() {
35
+ return (
36
+ <RadioGroup defaultValue="a">
37
+ <Radio value="a" aria-label="Option A" />
38
+ <Radio value="b" aria-label="Option B" />
39
+ </RadioGroup>
40
+ )
41
+ }
42
+ ```
43
+
44
+ From `radio-in-group`:
45
+
46
+ ```tsx
47
+ import { Radio } from "@/components/ui/radio"
48
+ import { RadioGroup } from "@/components/ui/radio-group"
49
+
50
+ export default function RadioInGroup() {
51
+ return (
52
+ <div className="flex flex-col gap-6">
53
+ <RadioGroup variant="neutral" size="md" defaultValue="b">
54
+ <Radio value="a" aria-label="Option A" />
55
+ <Radio value="b" aria-label="Option B" />
56
+ <Radio value="c" aria-label="Option C" />
57
+ </RadioGroup>
58
+ <p className="text-placeholder text-ui-control-sm">
59
+ Children inherit <code>variant</code> and <code>size</code> from the
60
+ group via context — pass them on the group, not each child.
61
+ </p>
62
+ </div>
63
+ )
64
+ }
65
+ ```
66
+
67
+ More: `npx @create-ui/cli view radio` or MCP `get_item_examples_from_registries` with "radio-demo" / "radio-example".
68
+
69
+ ## When to use
70
+ One item in a mutually-exclusive set of a few always-visible options, always rendered inside `RadioGroup` (it is a Radix RadioGroup.Item and cannot toggle on its own). For long option lists use `Select`; for a binary on/off setting use `Switch`; for standalone or multi-select choices use `Checkbox` / `CheckboxGroup`.
71
+
72
+ ## Gotchas
73
+ - `Radio` and `RadioGroup` are separate registry items with separate import paths (`@/components/ui/radio` and `@/components/ui/radio-group`). There is no `RadioGroupItem` export; a shadcn-style `import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"` fails.
74
+ - `Radio` renders its indicator dot internally and ignores children. Never nest a `RadioGroupPrimitive.Indicator`, a circle icon, or any content inside it.
75
+ - Styling cascades from the group: `RadioGroup` publishes `variant`/`size` through `RadioContext`, so set them once on the group, not per child. A per-Radio prop overrides the context when one item must differ.
76
+ - `RadioGroup` wraps its children in a horizontal `Field`, so labeled rows use `FieldLabel htmlFor` next to the radio (or `aria-label` when there is no visible label, as in `radio-demo`).
77
+ - `RadioGroup`'s `invalid` and `disabled` props are passed only to the `Field` wrapper, never to the Radix root or the items: `invalid` colors the field footer but does not set `aria-invalid`, and `disabled` styles labels (cursor, pointer-events) while the radios stay interactive. The red outline needs `aria-invalid` on the `Radio` itself, and disabling needs `disabled` on each `Radio`.
78
+ - `variant="danger"` only colors the checked/focus state; it does not mark the field invalid. The error outline comes solely from the item's `aria-invalid` attribute, on any variant.
79
+ - `inverse` is built for dark surfaces: unchecked border and checked dot use light tokens, and focus-visible fills the control with static-black. `radio-example` shows it on a `bg-strongest` panel; on a light surface it is near-invisible.
@@ -0,0 +1,212 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/scroll-area.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/scroll-area.md -->
2
+
3
+ # scroll-area
4
+
5
+ Custom-scrollbar viewport; vertical, horizontal or both, filled/ghost scrollbar styles and optional edge fade
6
+
7
+ Install: `npx @create-ui/cli add scroll-area`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
13
+ ```
14
+
15
+ Also exported: `scrollBarVariants`
16
+
17
+ ## ScrollArea props
18
+
19
+ | Prop | Type | Default |
20
+ | --- | --- | --- |
21
+ | appearance | `filled \| ghost` (filled adds a bg-weak track behind the thumb; ghost shows only the thumb; forwarded to all auto-rendered scrollbars) | `filled` |
22
+ | size | `sm \| md \| lg` (Set once on ScrollArea; forwarded to every auto-rendered scrollbar) | `md` |
23
+ | orientation | `vertical \| horizontal \| both` (Auto-renders the matching scrollbar(s); a Corner is always rendered, so 'both' needs no extra children) | `vertical` |
24
+ | fade | `boolean` (End-of-scroll gradient only (bottom / right edge); hides at scroll end or when content fits, re-checked on scroll, resize, and DOM mutations) | `false` |
25
+
26
+ Extends `React.ComponentProps<typeof ScrollAreaPrimitive.Root>`.
27
+
28
+ ## ScrollBar props
29
+
30
+ | Prop | Type | Default |
31
+ | --- | --- | --- |
32
+ | appearance | `filled \| ghost` (filled adds a bg-weak track behind the thumb; ghost shows only the thumb; forwarded to all auto-rendered scrollbars) | `filled` |
33
+ | size | `sm \| md \| lg` (Set once on ScrollArea; forwarded to every auto-rendered scrollbar) | `md` |
34
+
35
+ Extends `React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>`.
36
+
37
+ ## Examples
38
+
39
+ From `scroll-area-demo`:
40
+
41
+ ```tsx
42
+ import * as React from "react"
43
+
44
+ import { ScrollArea } from "@/components/ui/scroll-area"
45
+ import { Separator } from "@/components/ui/separator"
46
+
47
+ const components = [
48
+ "Accordion",
49
+ "Alert Banner",
50
+ "App Store Badge",
51
+ "Aspect Ratio",
52
+ "Avatar",
53
+ "Badge",
54
+ "Breadcrumb",
55
+ "Button",
56
+ "Button Group",
57
+ "Checkbox",
58
+ "Checkbox Group",
59
+ "Chip",
60
+ "Close Button",
61
+ "Command",
62
+ "Country Flag",
63
+ "Credit Card Input",
64
+ "Date Input",
65
+ "Dialog",
66
+ "Dropdown Menu",
67
+ "Field",
68
+ "Info Tooltip",
69
+ "Inline Alert",
70
+ "Input",
71
+ "Input Group",
72
+ "Input OTP",
73
+ "Input Stepper",
74
+ "Label",
75
+ "Modal",
76
+ "Pagination",
77
+ "Password Strength",
78
+ "Phone Input",
79
+ "Popover",
80
+ "Progress",
81
+ "Radio",
82
+ "Radio Group",
83
+ "Scroll Area",
84
+ "Segmented Control",
85
+ "Select",
86
+ "Separator",
87
+ "Social Login Button",
88
+ "Spinner",
89
+ "Status Badge",
90
+ "Switch",
91
+ "Switch Group",
92
+ "Tab Menu",
93
+ "Text Link",
94
+ "Textarea",
95
+ "Toast",
96
+ "Tooltip",
97
+ ]
98
+
99
+ export default function ScrollAreaDemo() {
100
+ return (
101
+ <ScrollArea className="bg-weakest text-strong h-72 w-48 rounded-md">
102
+ <div className="p-4">
103
+ <h4 className="mb-4 text-sm leading-none font-medium">Components</h4>
104
+ {components.map((component) => (
105
+ <React.Fragment key={component}>
106
+ <div className="text-sm">{component}</div>
107
+ <Separator className="my-2" />
108
+ </React.Fragment>
109
+ ))}
110
+ </div>
111
+ </ScrollArea>
112
+ )
113
+ }
114
+ ```
115
+
116
+ From `scroll-area-fade`:
117
+
118
+ ```tsx
119
+ import * as React from "react"
120
+
121
+ import { ScrollArea } from "@/components/ui/scroll-area"
122
+ import { Separator } from "@/components/ui/separator"
123
+
124
+ const components = [
125
+ "Accordion",
126
+ "Alert Banner",
127
+ "App Store Badge",
128
+ "Aspect Ratio",
129
+ "Avatar",
130
+ "Badge",
131
+ "Breadcrumb",
132
+ "Button",
133
+ "Button Group",
134
+ "Checkbox",
135
+ "Checkbox Group",
136
+ "Chip",
137
+ "Close Button",
138
+ "Command",
139
+ "Country Flag",
140
+ "Credit Card Input",
141
+ "Date Input",
142
+ "Dialog",
143
+ "Dropdown Menu",
144
+ "Field",
145
+ "Info Tooltip",
146
+ "Inline Alert",
147
+ "Input",
148
+ "Input Group",
149
+ "Input OTP",
150
+ "Input Stepper",
151
+ "Label",
152
+ "Modal",
153
+ "Pagination",
154
+ "Password Strength",
155
+ "Phone Input",
156
+ "Popover",
157
+ "Progress",
158
+ "Radio",
159
+ "Radio Group",
160
+ "Scroll Area",
161
+ "Segmented Control",
162
+ "Select",
163
+ "Separator",
164
+ "Social Login Button",
165
+ "Spinner",
166
+ "Status Badge",
167
+ "Switch",
168
+ "Switch Group",
169
+ "Tab Menu",
170
+ "Text Link",
171
+ "Textarea",
172
+ "Toast",
173
+ "Tooltip",
174
+ ]
175
+
176
+ export default function ScrollAreaFade() {
177
+ return (
178
+ <ScrollArea fade className="bg-weakest text-strong h-72 w-48 rounded-md">
179
+ <div className="p-4">
180
+ <h4 className="mb-4 text-sm leading-none font-medium">Components</h4>
181
+ {components.map((component) => (
182
+ <React.Fragment key={component}>
183
+ <div className="text-sm">{component}</div>
184
+ <Separator className="my-2" />
185
+ </React.Fragment>
186
+ ))}
187
+ </div>
188
+ </ScrollArea>
189
+ )
190
+ }
191
+ ```
192
+
193
+ More: `npx @create-ui/cli view scroll-area` or MCP `get_item_examples_from_registries` with "scroll-area-demo" / "scroll-area-example".
194
+
195
+ ## When to use
196
+ Styled overlay-scrollbar viewport for content that overflows a fixed-size surface: lists in panels, image rows, wide grids. Do not use it for full-page scrolling (let the browser handle that). DropdownMenu and Select content panels already scroll on their own; do not wrap them in ScrollArea.
197
+
198
+ ## Gotchas
199
+ - Unlike shadcn, you never add a `<ScrollBar>` child. `ScrollArea` renders viewport, scrollbar(s), and corner itself from `orientation`; horizontal scrolling is `orientation="horizontal"`, not an extra child. A `<ScrollBar>` child would land inside the scrolled viewport content; the export exists only for fully custom setups.
200
+ - Width/height constraints go on the root `className` (e.g. `h-72 w-48`); the internal viewport is `size-full` and inherits the root's radius. Without a fixed dimension on the enabled axis nothing scrolls.
201
+ - Horizontal content must actually overflow: a non-wrapping flex row with `shrink-0` children inside a width-constrained root, e.g.
202
+
203
+ ```tsx
204
+ <ScrollArea orientation="horizontal" className="w-full max-w-96 rounded-md p-4">
205
+ <div className="flex gap-4">{items /* each child shrink-0 */}</div>
206
+ </ScrollArea>
207
+ ```
208
+
209
+ - `fade` overlays mark only the scroll END of each enabled axis (bottom for vertical, right for horizontal); there is no top/left start fade. They are `aria-hidden` and `pointer-events-none`, and dynamic content is handled automatically (scroll listener + ResizeObserver + MutationObserver).
210
+ - The fade gradient ends in the `static-white` token (white in light mode, black in dark), not the container's own background. On a tinted container (e.g. `bg-weakest`) the fade edge will not exactly match the surface color.
211
+ - Scrollbars are `forceMount`ed and toggle via opacity (`data-[state=hidden/visible]`); show/hide timing comes from the Radix root's `type` and `scrollHideDelay` props, which pass through `ScrollArea` untouched.
212
+ - The `cn-scroll-area*` classes are intentional style-less hooks for consumer CSS targeting; do not strip them as dead code.