@zerotal/arch 1.7.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 (160) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +67 -0
  4. package/api-surface.md +283 -0
  5. package/docs/about.md +676 -0
  6. package/docs/admin/actions.md +183 -0
  7. package/docs/admin/auth.md +89 -0
  8. package/docs/admin/dashboard.md +155 -0
  9. package/docs/admin/extending-ui.md +229 -0
  10. package/docs/admin/extending.md +205 -0
  11. package/docs/admin/forms.md +155 -0
  12. package/docs/admin/index.md +144 -0
  13. package/docs/admin/operations.md +224 -0
  14. package/docs/admin/references.md +174 -0
  15. package/docs/admin/resources.md +132 -0
  16. package/docs/admin/structure.md +158 -0
  17. package/docs/admin/tables.md +185 -0
  18. package/docs/admin/testing.md +138 -0
  19. package/docs/ai.md +373 -0
  20. package/docs/application.md +435 -0
  21. package/docs/arch.md +263 -0
  22. package/docs/assets.md +755 -0
  23. package/docs/audit.md +367 -0
  24. package/docs/authentication.md +1000 -0
  25. package/docs/authorization.md +529 -0
  26. package/docs/broadcasting/channels.md +170 -0
  27. package/docs/broadcasting/client.md +108 -0
  28. package/docs/broadcasting/events.md +190 -0
  29. package/docs/broadcasting/index.md +163 -0
  30. package/docs/broadcasting/references.md +84 -0
  31. package/docs/broadcasting/testing.md +132 -0
  32. package/docs/cache.md +387 -0
  33. package/docs/carbon.md +830 -0
  34. package/docs/changelog.md +465 -0
  35. package/docs/client/auth.md +113 -0
  36. package/docs/client/errors.md +139 -0
  37. package/docs/client/files.md +118 -0
  38. package/docs/client/index.md +128 -0
  39. package/docs/client/references.md +58 -0
  40. package/docs/client/requests.md +131 -0
  41. package/docs/client/resilience.md +141 -0
  42. package/docs/client/testing.md +146 -0
  43. package/docs/commands.md +450 -0
  44. package/docs/components.md +2427 -0
  45. package/docs/config-system.md +258 -0
  46. package/docs/container.md +596 -0
  47. package/docs/context.md +579 -0
  48. package/docs/contributing.md +164 -0
  49. package/docs/controllers.md +354 -0
  50. package/docs/conventions.md +466 -0
  51. package/docs/cookies.md +101 -0
  52. package/docs/csrf.md +206 -0
  53. package/docs/database.md +476 -0
  54. package/docs/deployment.md +434 -0
  55. package/docs/devtools.md +1013 -0
  56. package/docs/email-verification.md +320 -0
  57. package/docs/encryption.md +380 -0
  58. package/docs/errors.md +317 -0
  59. package/docs/events.md +420 -0
  60. package/docs/flow/components.md +846 -0
  61. package/docs/flow/decorators.md +458 -0
  62. package/docs/flow/events.md +420 -0
  63. package/docs/flow/forms.md +551 -0
  64. package/docs/flow/index.md +568 -0
  65. package/docs/flow/layouts.md +793 -0
  66. package/docs/flow/lifecycle.md +336 -0
  67. package/docs/flow/pagination.md +402 -0
  68. package/docs/flow/performance.md +262 -0
  69. package/docs/flow/references.md +317 -0
  70. package/docs/flow/routing.md +550 -0
  71. package/docs/flow/testing.md +447 -0
  72. package/docs/getting-started.md +286 -0
  73. package/docs/health.md +269 -0
  74. package/docs/helpers.md +423 -0
  75. package/docs/i18n.md +414 -0
  76. package/docs/index.md +65 -0
  77. package/docs/inertia/build.md +95 -0
  78. package/docs/inertia/devtools.md +197 -0
  79. package/docs/inertia/index.md +267 -0
  80. package/docs/inertia/middleware.md +81 -0
  81. package/docs/inertia/props.md +507 -0
  82. package/docs/inertia/references.md +144 -0
  83. package/docs/inertia/rendering.md +189 -0
  84. package/docs/inertia/ssr.md +102 -0
  85. package/docs/inspirations.md +257 -0
  86. package/docs/lifecycle.md +331 -0
  87. package/docs/lock.md +454 -0
  88. package/docs/logger.md +517 -0
  89. package/docs/media.md +582 -0
  90. package/docs/middleware.md +509 -0
  91. package/docs/migrations.md +529 -0
  92. package/docs/monitor.md +377 -0
  93. package/docs/notifications.md +848 -0
  94. package/docs/orm/casts.md +365 -0
  95. package/docs/orm/factories.md +231 -0
  96. package/docs/orm/index.md +663 -0
  97. package/docs/orm/lifecycle.md +421 -0
  98. package/docs/orm/queries.md +692 -0
  99. package/docs/orm/relationships.md +440 -0
  100. package/docs/orm/serialization.md +251 -0
  101. package/docs/package-development.md +536 -0
  102. package/docs/pagination.md +340 -0
  103. package/docs/password-reset.md +335 -0
  104. package/docs/providers.md +447 -0
  105. package/docs/query-builder.md +465 -0
  106. package/docs/queue.md +549 -0
  107. package/docs/rate-limiting.md +253 -0
  108. package/docs/responses.md +336 -0
  109. package/docs/roles-and-2fa.md +418 -0
  110. package/docs/routing.md +1008 -0
  111. package/docs/scaffolding.md +335 -0
  112. package/docs/scheduler.md +533 -0
  113. package/docs/seeding.md +229 -0
  114. package/docs/session.md +451 -0
  115. package/docs/social.md +524 -0
  116. package/docs/storage.md +622 -0
  117. package/docs/structure.md +230 -0
  118. package/docs/support-policy.md +114 -0
  119. package/docs/telemetry.md +505 -0
  120. package/docs/tenancy.md +608 -0
  121. package/docs/testing/browser.md +153 -0
  122. package/docs/testing/console.md +130 -0
  123. package/docs/testing/database.md +238 -0
  124. package/docs/testing/flow-browser.md +216 -0
  125. package/docs/testing/http.md +441 -0
  126. package/docs/testing/index.md +240 -0
  127. package/docs/testing/mocking.md +410 -0
  128. package/docs/upgrade.md +192 -0
  129. package/docs/validator.md +549 -0
  130. package/docs/view.md +453 -0
  131. package/package.json +60 -0
  132. package/src/bin/mcp.ts +73 -0
  133. package/src/config.ts +63 -0
  134. package/src/errors.ts +27 -0
  135. package/src/index.ts +56 -0
  136. package/src/install/ArchInstallCommand.ts +189 -0
  137. package/src/install/detect.ts +98 -0
  138. package/src/install/guidelines.ts +290 -0
  139. package/src/install/markers.ts +77 -0
  140. package/src/install/mcpConfig.ts +81 -0
  141. package/src/mcp/index.ts +27 -0
  142. package/src/mcp/jsonrpc.ts +133 -0
  143. package/src/mcp/server.ts +307 -0
  144. package/src/mcp/stdio.ts +111 -0
  145. package/src/mcp/types.ts +146 -0
  146. package/src/probe/ArchProbeCommand.ts +48 -0
  147. package/src/probe/sentinel.ts +13 -0
  148. package/src/probe/topics.ts +291 -0
  149. package/src/provider/ArchProvider.ts +101 -0
  150. package/src/tools/_probe.ts +164 -0
  151. package/src/tools/apiSurface.ts +241 -0
  152. package/src/tools/appInfo.ts +88 -0
  153. package/src/tools/baselines.ts +210 -0
  154. package/src/tools/context.ts +37 -0
  155. package/src/tools/doctor.ts +93 -0
  156. package/src/tools/index.ts +50 -0
  157. package/src/tools/logs.ts +265 -0
  158. package/src/tools/routes.ts +133 -0
  159. package/src/tools/schema.ts +119 -0
  160. package/src/tools/searchDocs.ts +345 -0
@@ -0,0 +1,2427 @@
1
+ ---
2
+ title: Components
3
+ description: Themeable UI components for Flow that you install via the CLI or import from the package.
4
+ ---
5
+
6
+ # Components
7
+
8
+ `@zerotal/flow-ui` ships 53 themeable components for [Flow](/docs/flow). They are
9
+ built on accessible headless primitives and design tokens, so they follow your theme
10
+ (light / dark) out of the box. Add them with the CLI (copy the source into your app) or
11
+ import them straight from the package.
12
+
13
+ The catalogue covers what an application actually needs rather than only the obvious
14
+ primitives:
15
+
16
+ | Group | Components |
17
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | Forms | `Field`, `Input`, `InputGroup`, `InputOTP`, `Textarea`, `Select`, `Combobox`, `Checkbox`, `RadioGroup`, `Switch`, `Slider`, `Toggle`, `ToggleGroup`, `Label`, `Calendar`, `DatePicker` |
19
+ | Buttons & actions | `Button`, `ButtonGroup`, `DropdownMenu`, `ContextMenu`, `Menubar` |
20
+ | Overlays | `Dialog`, `AlertDialog`, `Sheet`, `Popover`, `HoverCard`, `Tooltip`, `Command` |
21
+ | Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Pagination`, `Tabs` |
22
+ | Data | `Table`, `Chart`, `Item`, `Avatar`, `Badge` |
23
+ | Feedback | `Toaster`, `Alert`, `Progress`, `Spinner`, `Skeleton`, `Empty` |
24
+ | Layout & content | `Card`, `Accordion`, `Collapsible`, `ScrollArea`, `Resizable`, `Carousel`, `AspectRatio`, `Separator`, `Prose`, `Kbd` |
25
+
26
+ ## Where the work happens
27
+
28
+ The components follow one rule, and it is worth stating before the API: **the
29
+ server renders, the client reacts.**
30
+
31
+ Anything that answers to a pointer or a keystroke — dragging a slider, paging a
32
+ calendar, filtering a palette, pressing a toggle, hovering a chart — runs entirely
33
+ in the browser through Alpine. Those interactions have to land inside a frame, and
34
+ a network round-trip cannot make that promise. Putting them on the server produces
35
+ the specific ugliness of a control that lags its own input: a slider whose readout
36
+ does not move while you drag it, a segmented button that stays unpressed until the
37
+ reply arrives.
38
+
39
+ The server's job is the first paint and the data. It renders the initial state so
40
+ the page is correct and complete before any script runs, and it hears about a
41
+ change once — when there is something to persist. A date picker syncs the day you
42
+ chose, not the six months you browsed to find it.
43
+
44
+ In practice that means most components bind rather than call:
45
+
46
+ ```tsx
47
+ <Slider bind={this.volume} showValue /> {/* live while dragging, synced on release */}
48
+ <ToggleGroup bind={this.view} options={…} /> {/* presses instantly, syncs after */}
49
+ <DatePicker bind={this.due} /> {/* months page client-side, day syncs once */}
50
+ ```
51
+
52
+ Where a component genuinely needs the server — a calendar laying out records, a
53
+ table of rows — it says so, and paging is a real navigation because the next page
54
+ means different data.
55
+
56
+ ## Getting Started
57
+
58
+ ```bash
59
+ # in your project root
60
+ bun add @zerotal/flow-ui
61
+ ```
62
+
63
+ ## Register the provider
64
+
65
+ `FlowUiProvider` is a scaffolding-only provider: it registers the `flow:*` CLI commands. The components themselves are plain functions with no runtime service, so you only need the provider to use the CLI. Add it to the providers array in `bootstrap/providers.ts`:
66
+
67
+ ```ts
68
+ // bootstrap/providers.ts
69
+ import { FlowUiProvider } from "@zerotal/flow-ui";
70
+
71
+ const providers = [
72
+ // …your other providers
73
+ FlowUiProvider,
74
+ ];
75
+
76
+ export default providers;
77
+ ```
78
+
79
+ Registering the provider switches on the following:
80
+
81
+ - `onBooted` — registers the `flow:list`, `flow:add`, and `flow:init` console commands (only when the `commands` binding is present, so web/test boots stay unaffected).
82
+
83
+ ## Set up the theme
84
+
85
+ Run `flow:init` once per app. It drops the shared `cn` / `gva` utilities into `app/flow/components/ui/lib/` and wires the design-token theme into your Tailwind entry CSS:
86
+
87
+ ```bash
88
+ # in your project root
89
+ bun zt flow:init
90
+ ```
91
+
92
+ > **Note** — `flow:init` is idempotent: it skips files that already exist and only adds the `@import "@zerotal/flow-ui/theme.css";` line if it is missing. Pass `--css <path>` to target a Tailwind entry other than `resources/css/app.css`.
93
+
94
+ ### Without a build step
95
+
96
+ Components are written against design tokens — `bg-card`, `text-muted-foreground`, `border-input`, `bg-primary` — so _defining_ those tokens themes the whole kit at once. `theme.css` does that for a real Tailwind build. When you have no build step, `flowUiHead()` does the same job as a `<head>` payload: it loads the Tailwind Play CDN, configures it with the identical token mapping, and emits the palette inline.
97
+
98
+ ```ts
99
+ import { Layout, flowUiHead } from "@zerotal/flow-ui";
100
+
101
+ export class AppLayout extends Layout {
102
+ static override get head() {
103
+ return flowUiHead("Acme");
104
+ }
105
+ }
106
+ ```
107
+
108
+ Re-brand by passing `tokensCss`; it is appended after the defaults, so overriding one variable recolours everything that reads it:
109
+
110
+ ```ts
111
+ flowUiHead("Acme", {
112
+ tokensCss: `
113
+ :root { --primary: 21 90% 48%; --ring: 21 90% 48%; }
114
+ .dark { --primary: 25 95% 58%; --ring: 25 95% 58%; }
115
+ `,
116
+ });
117
+ ```
118
+
119
+ This is the path both panels Zerotal ships take. The [admin](/docs/admin) uses it through `adminHead()`, and the [monitor](/docs/monitor) uses it directly with an orange `--primary` — which is the whole reason the two look like the same product rather than two.
120
+
121
+ ### The token set
122
+
123
+ Alongside the base tokens (`background`, `card`, `primary`, `muted`, `accent`, `destructive`, `border`, `input`, `ring`) the theme adds:
124
+
125
+ | Token | For |
126
+ | ------------------------------------------- | ------------------------------------------------------------------ |
127
+ | `success` / `warning` | The states `destructive` doesn't cover — "healthy" and "degraded". |
128
+ | `flow-toast-success` / `flow-toast-warning` | Status accents the toast host reads at runtime. |
129
+ | `chart-1` … `chart-7` | A categorical series palette for charts. |
130
+
131
+ The chart tokens are deliberately _not_ semantic: "the second line" isn't good or bad, it just has to stay distinguishable from the first. Because SVG `stroke` and `fill` can't take Tailwind classes, reference them as `hsl(var(--chart-2))`.
132
+
133
+ ## Adding components
134
+
135
+ `flow:add` copies component source into `app/flow/components/ui/`, so you own the code
136
+ outright. The shared `cn` / `gva` utils land in `ui/lib/`, and util imports are rewritten
137
+ to the local `./lib/*` paths on the way in.
138
+
139
+ A component built from others brings them along. Asking for `date-picker` also copies
140
+ `popover` and `calendar`, because a file importing a `Calendar` that was never copied is
141
+ not a working component.
142
+
143
+ ```bash
144
+ # in your project root
145
+ bun zt flow:add button # one component
146
+ bun zt flow:add button,card,dialog # several, comma-separated
147
+ bun zt flow:add --all # everything
148
+ bun zt flow:add button --force # overwrite if it exists
149
+ ```
150
+
151
+ Browse everything available with `flow:list`:
152
+
153
+ ```bash
154
+ # in your project root
155
+ bun zt flow:list
156
+ ```
157
+
158
+ > **Tip** — The copied `cn` util needs `clsx` and `tailwind-merge` in your app. `flow:add` and `flow:init` warn you with the exact `bun add` command if they are missing.
159
+
160
+ ## Copy-in vs import — which should I use?
161
+
162
+ Both give you the same components; they differ in ownership:
163
+
164
+ - **Copy in with `flow:add`** when you want to _own and tweak_ the source — change variants, restyle, or extend a component. The code lives in your repo and won't change under you.
165
+ - **Import from `@zerotal/flow-ui`** when you just want the component as-is and prefer to track upstream updates. Every component is exported from the package root:
166
+
167
+ ```tsx
168
+ // in a Flow component
169
+ import { Button, Card, Dialog } from "@zerotal/flow-ui";
170
+ ```
171
+
172
+ The component API is identical either way — the sections below apply to both.
173
+
174
+ Props not listed for a component pass straight through to the underlying element —
175
+ `onClick`, `type`, `disabled`, and any `flow:*` directive behave exactly as they would
176
+ on the raw tag, so a component never gets in the way of Flow's own bindings.
177
+
178
+ <!-- BEGIN GENERATED COMPONENTS — edit the spec in packages/flow-ui/src/docs/, not this block. -->
179
+
180
+ ### All 53 components
181
+
182
+ - [Button](#components-button) — Clickable button with variants + sizes
183
+ - [Badge](#components-badge) — Small status pill with variants
184
+ - [Card](#components-card) — Surface container (+ header/title/content/footer)
185
+ - [Input](#components-input) — Themed text input (two-way bindable)
186
+ - [Textarea](#components-textarea) — Themed multi-line input
187
+ - [Label](#components-label) — Form label (wraps the headless Label)
188
+ - [Separator](#components-separator) — Horizontal/vertical divider
189
+ - [Skeleton](#components-skeleton) — Pulsing loading placeholder
190
+ - [Avatar](#components-avatar) — Circular avatar with image + fallback
191
+ - [Switch](#components-switch) — On/off toggle bound to a boolean
192
+ - [Checkbox](#components-checkbox) — Checkbox bound to a boolean
193
+ - [Select](#components-select) — Native select bound to a value
194
+ - [RadioGroup](#components-radio-group) — Segmented radio set bound to a value
195
+ - [Dialog](#components-dialog) — Modal dialog (focus-trapped)
196
+ - [Sheet](#components-sheet) — Edge-anchored slide-over panel
197
+ - [DropdownMenu](#components-dropdown-menu) — Keyboard-navigable menu (+ item/label/separator)
198
+ - [Tabs](#components-tabs) — Tabbed panels with a pill tablist
199
+ - [Alert](#components-alert) — Inline alert (+ title/description)
200
+ - [Tooltip](#components-tooltip) — Hover/focus tooltip
201
+ - [Table](#components-table) — URL-sortable data table
202
+ - [Popover](#components-popover) — Floating panel anchored to a trigger
203
+ - [HoverCard](#components-hover-card) — Preview panel shown on hover
204
+ - [AlertDialog](#components-alert-dialog) — Confirm before something irreversible
205
+ - [Command](#components-command) — Searchable command menu (⌘K palette)
206
+ - [ContextMenu](#components-context-menu) — Right-click menu
207
+ - [Menubar](#components-menubar) — Application menu bar
208
+ - [NavigationMenu](#components-navigation-menu) — Site nav with dropdown panels
209
+ - [Sidebar](#components-sidebar) — App shell nav rail with a mobile drawer
210
+ - [Breadcrumb](#components-breadcrumb) — Trail showing where a page sits
211
+ - [Pagination](#components-pagination) — Page links with a windowed number range
212
+ - [Field](#components-field) — Label + control + description + error
213
+ - [InputGroup](#components-input-group) — Input with affixes or addons
214
+ - [InputOTP](#components-input-otp) — One-time-code input
215
+ - [Combobox](#components-combobox) — Autocomplete over many options
216
+ - [Slider](#components-slider) — Value chosen from a range
217
+ - [Toggle](#components-toggle) — Pressed-state button (+ toggle group)
218
+ - [ButtonGroup](#components-button-group) — Buttons joined into one control
219
+ - [Calendar](#components-calendar) — Month grid for picking or laying out dates
220
+ - [DatePicker](#components-date-picker) — Calendar in a popover
221
+ - [Toaster](#components-toast) — Host for transient flash messages
222
+ - [Progress](#components-progress) — Determinate progress bar
223
+ - [Spinner](#components-spinner) — Indeterminate loading indicator
224
+ - [Empty](#components-empty) — Empty-state block with an action
225
+ - [Kbd](#components-kbd) — Keyboard key (+ platform modifier)
226
+ - [Accordion](#components-accordion) — Stacked collapsible sections
227
+ - [Collapsible](#components-collapsible) — One section that opens and closes
228
+ - [ScrollArea](#components-scroll-area) — Scrollable region with a styled bar
229
+ - [Resizable](#components-resizable) — Two panes with a draggable handle
230
+ - [Carousel](#components-carousel) — Snap-scrolling strip with controls
231
+ - [AspectRatio](#components-aspect-ratio) — Fixed width-to-height box
232
+ - [Item](#components-item) — Icon + title + description + action row
233
+ - [Chart](#components-chart) — SVG line, area, bar and donut charts
234
+ - [Prose](#components-typography) — Prose wrapper + heading helpers
235
+
236
+ <a id="components-button"></a>
237
+
238
+ ## Button
239
+
240
+ Clickable button with variants + sizes.
241
+
242
+ ### Button installation
243
+
244
+ ```sh
245
+ bun zt flow:add button
246
+ ```
247
+
248
+ Or import directly from the package: `import { Button } from "@zerotal/flow-ui";`
249
+
250
+ ### Button preview
251
+
252
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
253
+ <div class="flex flex-wrap items-center gap-3"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-9 px-4 py-2">Default</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-secondary text-secondary-foreground hover:bg-secondary/80 h-9 px-4 py-2">Secondary</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-destructive text-destructive-foreground hover:bg-destructive/90 h-9 px-4 py-2">Destructive</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Outline</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Ghost</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 text-primary underline-offset-4 hover:underline h-9 px-4 py-2">Link</button></div>
254
+ </div>
255
+
256
+ ### Button usage
257
+
258
+ ```tsx
259
+ <Button onClick={this.save}>Save</Button>
260
+ <Button variant="secondary">Secondary</Button>
261
+ <Button variant="destructive">Delete</Button>
262
+ <Button variant="outline">Outline</Button>
263
+ <Button variant="ghost">Ghost</Button>
264
+ <Button variant="link">Link</Button>
265
+ ```
266
+
267
+ ### Button props
268
+
269
+ <table>
270
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
271
+ <tbody>
272
+ <tr><td><code>variant</code></td><td><code>"default" | "secondary" | "destructive" | "outline" | "ghost" | "link"</code></td><td><code>"default"</code></td><td>Visual style.</td></tr>
273
+ <tr><td><code>size</code></td><td><code>"default" | "sm" | "lg" | "icon"</code></td><td><code>"default"</code></td><td>Sizing.</td></tr>
274
+ <tr><td><code>onClick</code></td><td><code>handler</code></td><td>—</td><td>Server action or client expression (standard Flow).</td></tr>
275
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
276
+ </tbody>
277
+ </table>
278
+
279
+ <a id="components-badge"></a>
280
+
281
+ ## Badge
282
+
283
+ Small status pill with variants.
284
+
285
+ ### Badge installation
286
+
287
+ ```sh
288
+ bun zt flow:add badge
289
+ ```
290
+
291
+ Or import directly from the package: `import { Badge } from "@zerotal/flow-ui";`
292
+
293
+ ### Badge preview
294
+
295
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
296
+ <div class="flex flex-wrap items-center gap-3"><span class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground hover:bg-primary/80">Default</span><span class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">Secondary</span><span class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80">Destructive</span><span class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground">Outline</span></div>
297
+ </div>
298
+
299
+ ### Badge usage
300
+
301
+ ```tsx
302
+ <Badge>New</Badge>
303
+ <Badge variant="secondary">Beta</Badge>
304
+ <Badge variant="destructive">Overdue</Badge>
305
+ <Badge variant="outline">Draft</Badge>
306
+ ```
307
+
308
+ ### Badge props
309
+
310
+ <table>
311
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
312
+ <tbody>
313
+ <tr><td><code>variant</code></td><td><code>"default" | "secondary" | "destructive" | "outline"</code></td><td><code>"default"</code></td><td>Visual style.</td></tr>
314
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
315
+ </tbody>
316
+ </table>
317
+
318
+ <a id="components-card"></a>
319
+
320
+ ## Card
321
+
322
+ Surface container (+ header/title/content/footer).
323
+
324
+ ### Card installation
325
+
326
+ ```sh
327
+ bun zt flow:add card
328
+ ```
329
+
330
+ Or import directly from the package: `import { Card } from "@zerotal/flow-ui";`
331
+
332
+ ### Card preview
333
+
334
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
335
+ <div class="rounded-xl border bg-card text-card-foreground shadow w-80"><div class="flex flex-col space-y-1.5 p-6"><h3 class="font-semibold leading-none tracking-tight">Create project</h3><p class="text-sm text-muted-foreground">Deploy your new project in one click.</p></div><div class="p-6 pt-0"><p class="text-sm text-muted-foreground">Project settings go here.</p></div><div class="flex items-center p-6 pt-0"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-9 px-4 py-2">Deploy</button></div></div>
336
+ </div>
337
+
338
+ ### Card usage
339
+
340
+ ```tsx
341
+ <Card>
342
+ <CardHeader>
343
+ <CardTitle>Create project</CardTitle>
344
+ <CardDescription>Deploy your new project in one click.</CardDescription>
345
+ </CardHeader>
346
+ <CardContent>…</CardContent>
347
+ <CardFooter>
348
+ <Button>Deploy</Button>
349
+ </CardFooter>
350
+ </Card>
351
+ ```
352
+
353
+ ### Card props
354
+
355
+ <table>
356
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
357
+ <tbody>
358
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes on the surface.</td></tr>
359
+ <tr><td><code>children</code></td><td><code>node</code></td><td>—</td><td>Compose with CardHeader / CardTitle / CardDescription / CardContent / CardFooter.</td></tr>
360
+ </tbody>
361
+ </table>
362
+
363
+ <a id="components-input"></a>
364
+
365
+ ## Input
366
+
367
+ Themed text input (two-way bindable).
368
+
369
+ ### Input installation
370
+
371
+ ```sh
372
+ bun zt flow:add input
373
+ ```
374
+
375
+ Or import directly from the package: `import { Input } from "@zerotal/flow-ui";`
376
+
377
+ ### Input preview
378
+
379
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
380
+ <div class="flex w-72 flex-col gap-1.5"><label class="flow-label text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">Email</label><input type="text" data-slot="input" class="flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" placeholder="you@example.com"></div>
381
+ </div>
382
+
383
+ ### Input usage
384
+
385
+ ```tsx
386
+ <Field label="Email">
387
+ <Input value={this.form.email} placeholder="you@example.com" />
388
+ </Field>
389
+ ```
390
+
391
+ ### Input props
392
+
393
+ <table>
394
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
395
+ <tbody>
396
+ <tr><td><code>value</code></td><td><code>bound state</code></td><td>—</td><td>Two-way bind to an @expose / form field (emits flow:model).</td></tr>
397
+ <tr><td><code>type</code></td><td><code>string</code></td><td><code>"text"</code></td><td>Native input type.</td></tr>
398
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
399
+ </tbody>
400
+ </table>
401
+
402
+ <a id="components-textarea"></a>
403
+
404
+ ## Textarea
405
+
406
+ Themed multi-line input.
407
+
408
+ ### Textarea installation
409
+
410
+ ```sh
411
+ bun zt flow:add textarea
412
+ ```
413
+
414
+ Or import directly from the package: `import { Textarea } from "@zerotal/flow-ui";`
415
+
416
+ ### Textarea preview
417
+
418
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
419
+ <div class="w-72"><textarea class="flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" placeholder="Tell us about yourself" rows="4"></textarea></div>
420
+ </div>
421
+
422
+ ### Textarea usage
423
+
424
+ ```tsx
425
+ <Textarea value={this.form.bio} placeholder="Tell us about yourself" rows={4} />
426
+ ```
427
+
428
+ ### Textarea props
429
+
430
+ <table>
431
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
432
+ <tbody>
433
+ <tr><td><code>value</code></td><td><code>bound state</code></td><td>—</td><td>Two-way bind to an @expose / form field.</td></tr>
434
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
435
+ </tbody>
436
+ </table>
437
+
438
+ <a id="components-label"></a>
439
+
440
+ ## Label
441
+
442
+ Form label (wraps the headless Label).
443
+
444
+ ### Label installation
445
+
446
+ ```sh
447
+ bun zt flow:add label
448
+ ```
449
+
450
+ Or import directly from the package: `import { Label } from "@zerotal/flow-ui";`
451
+
452
+ ### Label preview
453
+
454
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
455
+ <div class="flex flex-col gap-1.5"><label for="email" class="flow-label text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">Email address</label><input type="text" data-slot="input" class="flex h-9 min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 w-64" id="email" placeholder="you@example.com"></div>
456
+ </div>
457
+
458
+ ### Label usage
459
+
460
+ ```tsx
461
+ <Label for="email">Email</Label>
462
+ ```
463
+
464
+ ### Label props
465
+
466
+ <table>
467
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
468
+ <tbody>
469
+ <tr><td><code>for</code></td><td><code>string</code></td><td>—</td><td>Associated control id.</td></tr>
470
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
471
+ </tbody>
472
+ </table>
473
+
474
+ <a id="components-separator"></a>
475
+
476
+ ## Separator
477
+
478
+ Horizontal/vertical divider.
479
+
480
+ ### Separator installation
481
+
482
+ ```sh
483
+ bun zt flow:add separator
484
+ ```
485
+
486
+ Or import directly from the package: `import { Separator } from "@zerotal/flow-ui";`
487
+
488
+ ### Separator preview
489
+
490
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
491
+ <div class="w-72"><p class="text-sm font-medium text-foreground">Radix Primitives</p><p class="text-sm text-muted-foreground">An open-source UI component library.</p><div role="none" class="shrink-0 bg-border h-px w-full my-3"></div><div class="flex h-5 items-center gap-3 text-sm"><span>Blog</span><div role="none" class="shrink-0 bg-border h-full w-px"></div><span>Docs</span><div role="none" class="shrink-0 bg-border h-full w-px"></div><span>Source</span></div></div>
492
+ </div>
493
+
494
+ ### Separator usage
495
+
496
+ ```tsx
497
+ <Separator />
498
+ <Separator orientation="vertical" class="h-6" />
499
+ ```
500
+
501
+ ### Separator props
502
+
503
+ <table>
504
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
505
+ <tbody>
506
+ <tr><td><code>orientation</code></td><td><code>"horizontal" | "vertical"</code></td><td><code>"horizontal"</code></td><td>Divider direction.</td></tr>
507
+ <tr><td><code>decorative</code></td><td><code>boolean</code></td><td><code>true</code></td><td>ARIA-hidden when decorative; semantic separator otherwise.</td></tr>
508
+ </tbody>
509
+ </table>
510
+
511
+ <a id="components-skeleton"></a>
512
+
513
+ ## Skeleton
514
+
515
+ Pulsing loading placeholder.
516
+
517
+ ### Skeleton installation
518
+
519
+ ```sh
520
+ bun zt flow:add skeleton
521
+ ```
522
+
523
+ Or import directly from the package: `import { Skeleton } from "@zerotal/flow-ui";`
524
+
525
+ ### Skeleton preview
526
+
527
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
528
+ <div class="flex items-center gap-3"><div class="animate-pulse bg-muted h-12 w-12 rounded-full"></div><div class="flex flex-col gap-2"><div class="animate-pulse rounded-md bg-muted h-4 w-40"></div><div class="animate-pulse rounded-md bg-muted h-4 w-28"></div></div></div>
529
+ </div>
530
+
531
+ ### Skeleton usage
532
+
533
+ ```tsx
534
+ <Skeleton class="h-12 w-12 rounded-full" />
535
+ <Skeleton class="h-4 w-48" />
536
+ ```
537
+
538
+ ### Skeleton props
539
+
540
+ <table>
541
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
542
+ <tbody>
543
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Size + shape via utilities.</td></tr>
544
+ </tbody>
545
+ </table>
546
+
547
+ <a id="components-avatar"></a>
548
+
549
+ ## Avatar
550
+
551
+ Circular avatar with image + fallback.
552
+
553
+ ### Avatar installation
554
+
555
+ ```sh
556
+ bun zt flow:add avatar
557
+ ```
558
+
559
+ Or import directly from the package: `import { Avatar } from "@zerotal/flow-ui";`
560
+
561
+ ### Avatar preview
562
+
563
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
564
+ <div class="flex flex-wrap items-center gap-3"><span class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full"><img src="https://i.pravatar.cc/64?img=11" alt="Ada Mokoena" class="aspect-square h-full w-full object-cover"></span><span class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full"><span class="flex h-full w-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">AL</span></span><span class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full bg-primary text-primary-foreground"><span class="flex h-full w-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">GH</span></span></div>
565
+ </div>
566
+
567
+ ### Avatar usage
568
+
569
+ ```tsx
570
+ <Avatar src={user.avatarUrl} alt={user.name} fallback="AL" />
571
+ <Avatar fallback="GH" />
572
+ ```
573
+
574
+ ### Avatar props
575
+
576
+ <table>
577
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
578
+ <tbody>
579
+ <tr><td><code>src</code></td><td><code>string | null</code></td><td>—</td><td>Image URL; falls back to `fallback` when absent.</td></tr>
580
+ <tr><td><code>fallback</code></td><td><code>node</code></td><td>—</td><td>Shown when there's no image (e.g. initials).</td></tr>
581
+ <tr><td><code>alt</code></td><td><code>string</code></td><td>—</td><td>Image alt text.</td></tr>
582
+ </tbody>
583
+ </table>
584
+
585
+ <a id="components-switch"></a>
586
+
587
+ ## Switch
588
+
589
+ On/off toggle bound to a boolean.
590
+
591
+ ### Switch installation
592
+
593
+ ```sh
594
+ bun zt flow:add switch
595
+ ```
596
+
597
+ Or import directly from the package: `import { Switch } from "@zerotal/flow-ui";`
598
+
599
+ ### Switch preview
600
+
601
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
602
+ <label class="flex items-center gap-2 text-sm"><button type="button" role="switch" tabindex="0" aria-checked="true" class="flow-switch group inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background bg-input data-checked:bg-primary" data-checked=""><span class="pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform translate-x-0 group-data-checked:translate-x-4"></span></button>Airplane mode</label>
603
+ </div>
604
+
605
+ ### Switch usage
606
+
607
+ ```tsx
608
+ <Switch bind={this.notifications} />
609
+ ```
610
+
611
+ ### Switch props
612
+
613
+ <table>
614
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
615
+ <tbody>
616
+ <tr><td><code>bind</code></td><td><code>@expose boolean</code></td><td>—</td><td>Two-way bound boolean (server-synced).</td></tr>
617
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes on the track.</td></tr>
618
+ </tbody>
619
+ </table>
620
+
621
+ <a id="components-checkbox"></a>
622
+
623
+ ## Checkbox
624
+
625
+ Checkbox bound to a boolean.
626
+
627
+ ### Checkbox installation
628
+
629
+ ```sh
630
+ bun zt flow:add checkbox
631
+ ```
632
+
633
+ Or import directly from the package: `import { Checkbox } from "@zerotal/flow-ui";`
634
+
635
+ ### Checkbox preview
636
+
637
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
638
+ <label class="flex items-center gap-2 text-sm"><button type="button" role="checkbox" tabindex="0" aria-checked="true" class="flow-checkbox grid h-4 w-4 shrink-0 place-items-center rounded-sm border border-primary text-transparent shadow outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-checked:bg-primary data-checked:text-primary-foreground" data-checked=""><svg viewBox="0 0 16 16" fill="none" class="h-3.5 w-3.5" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.5l3 3 6-7"></path></svg></button>Accept terms and conditions</label>
639
+ </div>
640
+
641
+ ### Checkbox usage
642
+
643
+ ```tsx
644
+ <Checkbox bind={this.agree} />
645
+ ```
646
+
647
+ ### Checkbox props
648
+
649
+ <table>
650
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
651
+ <tbody>
652
+ <tr><td><code>bind</code></td><td><code>@expose boolean</code></td><td>—</td><td>Two-way bound boolean.</td></tr>
653
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
654
+ </tbody>
655
+ </table>
656
+
657
+ <a id="components-select"></a>
658
+
659
+ ## Select
660
+
661
+ Native select bound to a value.
662
+
663
+ ### Select installation
664
+
665
+ ```sh
666
+ bun zt flow:add select
667
+ ```
668
+
669
+ Or import directly from the package: `import { Select } from "@zerotal/flow-ui";`
670
+
671
+ ### Select preview
672
+
673
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
674
+ <div class="w-56"><select class="flow-select flex h-9 w-full items-center rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"><option value="ca" selected>Canada</option><option value="br">Brazil</option><option value="jp">Japan</option></select></div>
675
+ </div>
676
+
677
+ ### Select usage
678
+
679
+ ```tsx
680
+ <Select bind={this.country} options={[{ label: "Canada", value: "ca" }]} />
681
+ ```
682
+
683
+ ### Select props
684
+
685
+ <table>
686
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
687
+ <tbody>
688
+ <tr><td><code>bind</code></td><td><code>@expose value</code></td><td>—</td><td>Two-way bound value (flow:model).</td></tr>
689
+ <tr><td><code>options</code></td><td><code>{ label, value }[]</code></td><td>—</td><td>Option list.</td></tr>
690
+ <tr><td><code>placeholder</code></td><td><code>string</code></td><td>—</td><td>Optional empty first option.</td></tr>
691
+ </tbody>
692
+ </table>
693
+
694
+ <a id="components-radio-group"></a>
695
+
696
+ ## RadioGroup
697
+
698
+ Segmented radio set bound to a value.
699
+
700
+ ### RadioGroup installation
701
+
702
+ ```sh
703
+ bun zt flow:add radio-group
704
+ ```
705
+
706
+ Or import directly from the package: `import { RadioGroup } from "@zerotal/flow-ui";`
707
+
708
+ ### RadioGroup preview
709
+
710
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
711
+ <div role="radiogroup" x-data="{}" class="flow-radiogroup grid gap-2 w-56"><div role="radio" aria-checked="false" class="flow-radio flex cursor-pointer items-center justify-center rounded-md border border-input bg-background px-3 py-2 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring data-[checked]:border-primary data-[checked]:bg-primary data-[checked]:text-primary-foreground" tabindex="-1">Free</div><div role="radio" aria-checked="true" class="flow-radio flex cursor-pointer items-center justify-center rounded-md border border-input bg-background px-3 py-2 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring data-[checked]:border-primary data-[checked]:bg-primary data-[checked]:text-primary-foreground" data-checked="" tabindex="0">Pro</div><div role="radio" aria-checked="false" class="flow-radio flex cursor-pointer items-center justify-center rounded-md border border-input bg-background px-3 py-2 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring data-[checked]:border-primary data-[checked]:bg-primary data-[checked]:text-primary-foreground" tabindex="-1">Enterprise</div></div>
712
+ </div>
713
+
714
+ ### RadioGroup usage
715
+
716
+ ```tsx
717
+ <RadioGroup bind={this.plan} options={[{ label: "Pro", value: "pro" }]} />
718
+ ```
719
+
720
+ ### RadioGroup props
721
+
722
+ <table>
723
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
724
+ <tbody>
725
+ <tr><td><code>bind</code></td><td><code>@expose value</code></td><td>—</td><td>Two-way bound value.</td></tr>
726
+ <tr><td><code>options</code></td><td><code>{ label, value }[]</code></td><td>—</td><td>Option list.</td></tr>
727
+ <tr><td><code>optionClass</code></td><td><code>string</code></td><td>—</td><td>Per-option classes.</td></tr>
728
+ </tbody>
729
+ </table>
730
+
731
+ <a id="components-dialog"></a>
732
+
733
+ ## Dialog
734
+
735
+ Modal dialog (focus-trapped).
736
+
737
+ ### Dialog installation
738
+
739
+ ```sh
740
+ bun zt flow:add dialog
741
+ ```
742
+
743
+ Or import directly from the package: `import { Dialog } from "@zerotal/flow-ui";`
744
+
745
+ ### Dialog preview
746
+
747
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
748
+ <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Edit profile</button>
749
+ </div>
750
+
751
+ ### Dialog usage
752
+
753
+ ```tsx
754
+ <Button onClick={() => (this.open = true)}>Edit profile</Button>
755
+
756
+ <Dialog show={this.open} title="Edit profile" description="Make changes here.">
757
+ <form onSubmit={this.save} class="flex flex-col gap-3">
758
+ <Field label="Name"><Input value={this.form.name} /></Field>
759
+ <Button type="submit">Save</Button>
760
+ </form>
761
+ </Dialog>
762
+ ```
763
+
764
+ ### Dialog props
765
+
766
+ <table>
767
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
768
+ <tbody>
769
+ <tr><td><code>show</code></td><td><code>@expose boolean</code></td><td>—</td><td>Visibility (focus-trapped while open).</td></tr>
770
+ <tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>Dialog title (wires aria-labelledby).</td></tr>
771
+ <tr><td><code>description</code></td><td><code>node</code></td><td>—</td><td>Supporting text (aria-describedby).</td></tr>
772
+ <tr><td><code>closable</code></td><td><code>boolean</code></td><td><code>true</code></td><td>Show the × + allow backdrop/Escape close.</td></tr>
773
+ </tbody>
774
+ </table>
775
+
776
+ <a id="components-sheet"></a>
777
+
778
+ ## Sheet
779
+
780
+ Edge-anchored slide-over panel.
781
+
782
+ ### Sheet installation
783
+
784
+ ```sh
785
+ bun zt flow:add sheet
786
+ ```
787
+
788
+ Or import directly from the package: `import { Sheet } from "@zerotal/flow-ui";`
789
+
790
+ ### Sheet preview
791
+
792
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
793
+ <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Open sheet</button>
794
+ </div>
795
+
796
+ ### Sheet usage
797
+
798
+ ```tsx
799
+ <Button onClick={() => (this.open = true)}>Open</Button>
800
+
801
+ <Sheet show={this.open} side="right" title="Edit profile">…</Sheet>
802
+ ```
803
+
804
+ ### Sheet props
805
+
806
+ <table>
807
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
808
+ <tbody>
809
+ <tr><td><code>show</code></td><td><code>@expose boolean</code></td><td>—</td><td>Visibility (focus-trapped while open).</td></tr>
810
+ <tr><td><code>side</code></td><td><code>"left" | "right" | "top" | "bottom"</code></td><td><code>"right"</code></td><td>Edge to slide from.</td></tr>
811
+ <tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>Header title.</td></tr>
812
+ </tbody>
813
+ </table>
814
+
815
+ <a id="components-dropdown-menu"></a>
816
+
817
+ ## DropdownMenu
818
+
819
+ Keyboard-navigable menu (+ item/label/separator).
820
+
821
+ ### DropdownMenu installation
822
+
823
+ ```sh
824
+ bun zt flow:add dropdown-menu
825
+ ```
826
+
827
+ Or import directly from the package: `import { DropdownMenu } from "@zerotal/flow-ui";`
828
+
829
+ ### DropdownMenu preview
830
+
831
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
832
+ <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Options ▾</button>
833
+ </div>
834
+
835
+ ### DropdownMenu usage
836
+
837
+ ```tsx
838
+ <DropdownMenu label="Options">
839
+ <DropdownMenuLabel>My account</DropdownMenuLabel>
840
+ <DropdownMenuItem onClick={this.profile}>Profile</DropdownMenuItem>
841
+ <DropdownMenuSeparator />
842
+ <DropdownMenuItem variant="destructive" onClick={this.signOut}>
843
+ Sign out
844
+ </DropdownMenuItem>
845
+ </DropdownMenu>
846
+ ```
847
+
848
+ ### DropdownMenu props
849
+
850
+ <table>
851
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
852
+ <tbody>
853
+ <tr><td><code>label</code></td><td><code>node</code></td><td>—</td><td>Default trigger label (or pass `trigger`).</td></tr>
854
+ <tr><td><code>align</code></td><td><code>"left" | "right"</code></td><td><code>"left"</code></td><td>Panel alignment.</td></tr>
855
+ </tbody>
856
+ </table>
857
+
858
+ <a id="components-tabs"></a>
859
+
860
+ ## Tabs
861
+
862
+ Tabbed panels with a pill tablist.
863
+
864
+ ### Tabs installation
865
+
866
+ ```sh
867
+ bun zt flow:add tabs
868
+ ```
869
+
870
+ Or import directly from the package: `import { Tabs } from "@zerotal/flow-ui";`
871
+
872
+ ### Tabs preview
873
+
874
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
875
+ <div class="w-80"><div x-data="flowTabs({ tab: &quot;0&quot; })" class="flex flex-col"><div role="tablist" x-on:keydown="onKey($event)" class="inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground"><button type="button" role="tab" id="flow-tab-0" aria-controls="flow-tabpanel-0" :aria-selected="tab === &quot;0&quot;" :tabindex="tab === &quot;0&quot; ? 0 : -1" x-on:click="tab = &quot;0&quot;" :class="tab === &quot;0&quot; ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground'" class="inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium outline-none transition-all focus-visible:ring-2 focus-visible:ring-ring">Account</button><button type="button" role="tab" id="flow-tab-1" aria-controls="flow-tabpanel-1" :aria-selected="tab === &quot;1&quot;" :tabindex="tab === &quot;1&quot; ? 0 : -1" x-on:click="tab = &quot;1&quot;" :class="tab === &quot;1&quot; ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground'" class="inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium outline-none transition-all focus-visible:ring-2 focus-visible:ring-ring">Password</button></div><div role="tabpanel" id="flow-tabpanel-0" aria-labelledby="flow-tab-0" tabindex="0" x-show="tab === &quot;0&quot;" x-cloak class="mt-2 outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-md"><p class="text-sm text-muted-foreground">Account settings.</p></div><div role="tabpanel" id="flow-tabpanel-1" aria-labelledby="flow-tab-1" tabindex="0" x-show="tab === &quot;1&quot;" x-cloak class="mt-2 outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-md"><p class="text-sm text-muted-foreground">Change your password.</p></div></div></div>
876
+ </div>
877
+
878
+ ### Tabs usage
879
+
880
+ ```tsx
881
+ <Tabs
882
+ items={[
883
+ { label: "Account", content: <AccountForm /> },
884
+ { label: "Password", content: <PasswordForm /> },
885
+ ]}
886
+ />
887
+ ```
888
+
889
+ ### Tabs props
890
+
891
+ <table>
892
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
893
+ <tbody>
894
+ <tr><td><code>items</code></td><td><code>{ label, content, name? }[]</code></td><td>—</td><td>Tabs + their panels.</td></tr>
895
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
896
+ </tbody>
897
+ </table>
898
+
899
+ <a id="components-alert"></a>
900
+
901
+ ## Alert
902
+
903
+ Inline alert (+ title/description).
904
+
905
+ ### Alert installation
906
+
907
+ ```sh
908
+ bun zt flow:add alert
909
+ ```
910
+
911
+ Or import directly from the package: `import { Alert } from "@zerotal/flow-ui";`
912
+
913
+ ### Alert preview
914
+
915
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
916
+ <div class="flex w-96 flex-col gap-3"><div role="status" x-data="{ shown: true }" x-show="shown" class="relative w-full rounded-lg border px-4 py-3 text-sm bg-background text-foreground flex items-start gap-3"><div class="min-w-0 flex-1"><div class="mb-1 font-medium leading-none tracking-tight">Heads up!</div><div class="text-sm [&amp;_p]:leading-relaxed">You can add components to your app using the CLI.</div></div></div><div role="alert" x-data="{ shown: true }" x-show="shown" class="relative w-full rounded-lg border px-4 py-3 text-sm border-destructive/50 text-destructive flex items-start gap-3"><div class="min-w-0 flex-1"><div class="mb-1 font-medium leading-none tracking-tight">Error</div><div class="text-sm [&amp;_p]:leading-relaxed">Your session has expired.</div></div></div></div>
917
+ </div>
918
+
919
+ ### Alert usage
920
+
921
+ ```tsx
922
+ <Alert title="Heads up!">You can add components to your app.</Alert>
923
+ <Alert variant="destructive" title="Error">Something went wrong.</Alert>
924
+ ```
925
+
926
+ ### Alert props
927
+
928
+ <table>
929
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
930
+ <tbody>
931
+ <tr><td><code>variant</code></td><td><code>"default" | "destructive"</code></td><td><code>"default"</code></td><td>Visual style + ARIA role.</td></tr>
932
+ <tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>Bold title line.</td></tr>
933
+ <tr><td><code>dismissible</code></td><td><code>boolean</code></td><td><code>false</code></td><td>Show a client-only dismiss button.</td></tr>
934
+ </tbody>
935
+ </table>
936
+
937
+ <a id="components-tooltip"></a>
938
+
939
+ ## Tooltip
940
+
941
+ Hover/focus tooltip.
942
+
943
+ ### Tooltip installation
944
+
945
+ ```sh
946
+ bun zt flow:add tooltip
947
+ ```
948
+
949
+ Or import directly from the package: `import { Tooltip } from "@zerotal/flow-ui";`
950
+
951
+ ### Tooltip preview
952
+
953
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
954
+ <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 w-9">+</button>
955
+ </div>
956
+
957
+ ### Tooltip usage
958
+
959
+ ```tsx
960
+ <Tooltip content="Add to library">
961
+ <Button size="icon">+</Button>
962
+ </Tooltip>
963
+ ```
964
+
965
+ ### Tooltip props
966
+
967
+ <table>
968
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
969
+ <tbody>
970
+ <tr><td><code>content</code></td><td><code>node</code></td><td>—</td><td>Tooltip text.</td></tr>
971
+ <tr><td><code>placement</code></td><td><code>"top" | "bottom"</code></td><td><code>"top"</code></td><td>Bubble position.</td></tr>
972
+ </tbody>
973
+ </table>
974
+
975
+ <a id="components-table"></a>
976
+
977
+ ## Table
978
+
979
+ URL-sortable data table.
980
+
981
+ ### Table installation
982
+
983
+ ```sh
984
+ bun zt flow:add table
985
+ ```
986
+
987
+ Or import directly from the package: `import { Table } from "@zerotal/flow-ui";`
988
+
989
+ ### Table preview
990
+
991
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
992
+ <div class="w-96"><table class="w-full caption-bottom border-collapse text-sm"><thead class="[&amp;_tr]:border-b [&amp;_tr]:border-border"><tr><th scope="col" class="h-10 px-2 text-left align-middle font-medium text-muted-foreground">Name</th><th scope="col" class="h-10 px-2 text-left align-middle font-medium text-muted-foreground">Role</th></tr></thead><tbody><tr flow:key="Ada Lovelace" class="border-b border-border transition-colors hover:bg-muted/50"><td class="p-2 align-middle">Ada Lovelace</td><td class="p-2 align-middle">Engineer</td></tr><tr flow:key="Alan Turing" class="border-b border-border transition-colors hover:bg-muted/50"><td class="p-2 align-middle">Alan Turing</td><td class="p-2 align-middle">Researcher</td></tr></tbody></table></div>
993
+ </div>
994
+
995
+ ### Table usage
996
+
997
+ ```tsx
998
+ <Table
999
+ columns={[
1000
+ { key: "name", label: "Name", sortable: true },
1001
+ { key: "role", label: "Role" },
1002
+ ]}
1003
+ rows={people}
1004
+ sortBy={this.sortBy}
1005
+ sortDir={this.sortDir}
1006
+ hover
1007
+ />
1008
+ ```
1009
+
1010
+ ### Table props
1011
+
1012
+ <table>
1013
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1014
+ <tbody>
1015
+ <tr><td><code>columns</code></td><td><code>TableColumn[]</code></td><td>—</td><td>Column defs (key, label, sortable?, render?).</td></tr>
1016
+ <tr><td><code>rows</code></td><td><code>T[]</code></td><td>—</td><td>Row data.</td></tr>
1017
+ <tr><td><code>sortBy / sortDir</code></td><td><code>@url state</code></td><td>—</td><td>Bind to URL sort state for sortable headers.</td></tr>
1018
+ </tbody>
1019
+ </table>
1020
+
1021
+ <a id="components-popover"></a>
1022
+
1023
+ ## Popover
1024
+
1025
+ Floating panel anchored to a trigger.
1026
+
1027
+ ### Popover installation
1028
+
1029
+ ```sh
1030
+ bun zt flow:add popover
1031
+ ```
1032
+
1033
+ Or import directly from the package: `import { Popover } from "@zerotal/flow-ui";`
1034
+
1035
+ ### Popover preview
1036
+
1037
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1038
+ <div x-data="{ open: false }" class="flow-popover relative inline-block"><span x-on:click="open = !open" :aria-expanded="open" :data-open="open ? '' : null" role="button" tabindex="0" class="flow-popover-button cursor-pointer outline-none"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Options</button></span><div x-show="open" x-cloak x-transition x-on:click.outside="open = false" x-on:keydown.escape.window="open = false" :data-open="open ? '' : null" class="flow-popover-panel absolute top-full mt-2 left-0 z-50 min-w-[8rem] rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none"><p class="text-sm">Anything at all.</p></div></div>
1039
+ </div>
1040
+
1041
+ ### Popover usage
1042
+
1043
+ ```tsx
1044
+ <Popover trigger={<Button variant="outline">Options</Button>}>
1045
+ <p class="text-sm">Anything at all.</p>
1046
+ </Popover>
1047
+ ```
1048
+
1049
+ ### Popover props
1050
+
1051
+ <table>
1052
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1053
+ <tbody>
1054
+ <tr><td><code>trigger</code></td><td><code>node</code></td><td>—</td><td>The element that opens the panel.</td></tr>
1055
+ <tr><td><code>side</code></td><td><code>"top" | "right" | "bottom" | "left"</code></td><td><code>"bottom"</code></td><td>Which edge the panel sits on.</td></tr>
1056
+ <tr><td><code>align</code></td><td><code>"start" | "center" | "end"</code></td><td><code>"start"</code></td><td>How the panel lines up along that edge.</td></tr>
1057
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
1058
+ </tbody>
1059
+ </table>
1060
+
1061
+ <a id="components-hover-card"></a>
1062
+
1063
+ ## HoverCard
1064
+
1065
+ Preview panel shown on hover.
1066
+
1067
+ ### HoverCard installation
1068
+
1069
+ ```sh
1070
+ bun zt flow:add hover-card
1071
+ ```
1072
+
1073
+ Or import directly from the package: `import { HoverCard } from "@zerotal/flow-ui";`
1074
+
1075
+ ### HoverCard preview
1076
+
1077
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1078
+ <div x-data="{
1079
+ open: false,
1080
+ t: null,
1081
+ show() { clearTimeout(this.t); this.t = setTimeout(() =&gt; (this.open = true), 300); },
1082
+ hide() { clearTimeout(this.t); this.t = setTimeout(() =&gt; (this.open = false), 150); }
1083
+ }" class="relative inline-block" x-on:mouseenter="show()" x-on:mouseleave="hide()"><span tabindex="0" x-on:focus="open = true" x-on:blur="hide()" class="cursor-default outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:rounded-sm"><span class="underline">@ada</span></span><div x-show="open" x-cloak x-transition class="absolute z-50 w-64 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md top-full mt-2 left-0"><p class="text-sm font-medium">Ada Mokoena</p><p class="text-xs text-muted-foreground">Joined in 2024</p></div></div>
1084
+ </div>
1085
+
1086
+ ### HoverCard usage
1087
+
1088
+ ```tsx
1089
+ <HoverCard trigger={<a href="/users/1">@ada</a>}>
1090
+ <p class="text-sm font-medium">Ada Mokoena</p>
1091
+ </HoverCard>
1092
+ ```
1093
+
1094
+ ### HoverCard props
1095
+
1096
+ <table>
1097
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1098
+ <tbody>
1099
+ <tr><td><code>trigger</code></td><td><code>node</code></td><td>—</td><td>What is hovered.</td></tr>
1100
+ <tr><td><code>openDelay</code></td><td><code>number</code></td><td><code>300</code></td><td>Milliseconds before it opens.</td></tr>
1101
+ <tr><td><code>closeDelay</code></td><td><code>number</code></td><td><code>150</code></td><td>Grace period so the pointer can reach the panel.</td></tr>
1102
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
1103
+ </tbody>
1104
+ </table>
1105
+
1106
+ <a id="components-alert-dialog"></a>
1107
+
1108
+ ## AlertDialog
1109
+
1110
+ Confirm before something irreversible.
1111
+
1112
+ ### AlertDialog installation
1113
+
1114
+ ```sh
1115
+ bun zt flow:add alert-dialog
1116
+ ```
1117
+
1118
+ Or import directly from the package: `import { AlertDialog } from "@zerotal/flow-ui";`
1119
+
1120
+ ### AlertDialog preview
1121
+
1122
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1123
+ <div role="dialog" aria-modal="true" class="fixed inset-0 z-50 flex items-center justify-center p-4"><div class="absolute inset-0 bg-black/50"></div><div class="relative z-10 w-full max-w-lg rounded-lg border border-border bg-background p-6 text-foreground shadow-lg" flow:transition><div class="mb-4 flex flex-col gap-1.5 pr-6"><h2 class="text-lg font-semibold leading-none tracking-tight">Delete this product?</h2><p class="text-sm text-muted-foreground">It will be removed from every order. This cannot be undone.</p></div><div class="mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Cancel</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-destructive text-destructive-foreground hover:bg-destructive/90 h-9 px-4 py-2">Delete</button></div></div></div>
1124
+ </div>
1125
+
1126
+ ### AlertDialog usage
1127
+
1128
+ ```tsx
1129
+ <AlertDialog
1130
+ show={this.confirming}
1131
+ title="Delete this product?"
1132
+ description="It will be removed from every order. This cannot be undone."
1133
+ confirmLabel="Delete"
1134
+ onConfirm={this.destroy}
1135
+ />
1136
+ ```
1137
+
1138
+ ### AlertDialog props
1139
+
1140
+ <table>
1141
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1142
+ <tbody>
1143
+ <tr><td><code>show</code></td><td><code>boolean</code></td><td>—</td><td>Bound @expose boolean controlling visibility.</td></tr>
1144
+ <tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>The question.</td></tr>
1145
+ <tr><td><code>description</code></td><td><code>node</code></td><td>—</td><td>What will happen. Worth a full sentence.</td></tr>
1146
+ <tr><td><code>onConfirm</code></td><td><code>handler</code></td><td>—</td><td>Server action for the confirming choice.</td></tr>
1147
+ <tr><td><code>destructive</code></td><td><code>boolean</code></td><td><code>true</code></td><td>Style the confirm button as destructive.</td></tr>
1148
+ </tbody>
1149
+ </table>
1150
+
1151
+ <a id="components-command"></a>
1152
+
1153
+ ## Command
1154
+
1155
+ Searchable command menu (⌘K palette).
1156
+
1157
+ ### Command installation
1158
+
1159
+ ```sh
1160
+ bun zt flow:add command
1161
+ ```
1162
+
1163
+ Or import directly from the package: `import { Command } from "@zerotal/flow-ui";`
1164
+
1165
+ ### Command preview
1166
+
1167
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1168
+ <p class="text-sm text-muted-foreground">Mounted hidden; press <kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground" x-data="{ mac: false }" x-init="mac = /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent)" x-text="mac ? '⌘' : 'Ctrl'">Ctrl</kbd> <kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">K</kbd> to open it.</p><div x-data="flowCommand({&quot;items&quot;:[{&quot;label&quot;:&quot;Products&quot;,&quot;href&quot;:&quot;#&quot;,&quot;group&quot;:&quot;Go to&quot;},{&quot;label&quot;:&quot;Orders&quot;,&quot;href&quot;:&quot;#&quot;,&quot;group&quot;:&quot;Go to&quot;},{&quot;label&quot;:&quot;New order&quot;,&quot;href&quot;:&quot;#&quot;,&quot;group&quot;:&quot;Create&quot;}],&quot;hotkey&quot;:&quot;k&quot;})" x-show="open" x-cloak x-transition.opacity x-on:click="if ($event.target === $el) hide()" x-on:keydown.escape.window="hide()" class="fixed inset-0 z-50 flex items-start justify-center bg-black/50 p-4 pt-[12vh]"><div role="dialog" aria-modal="true" aria-label="Command menu" class="w-full max-w-lg overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-2xl"><div class="flex items-center gap-2 border-b border-border px-3"><svg class="h-4 w-4 shrink-0 text-muted-foreground" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg><input x-ref="field" type="text" role="combobox" aria-expanded="true" aria-controls="flow-command-list" autocomplete="off" placeholder="Search…" x-model="query" x-on:input="active = 0" x-on:keydown.arrow-down.prevent="move(1)" x-on:keydown.arrow-up.prevent="move(-1)" x-on:keydown.enter.prevent="choose()" class="h-12 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground"></div><div id="flow-command-list" role="listbox" class="max-h-80 overflow-y-auto p-2"><template x-for="(item, i) in results()" :key="item.label + i"><div><div x-show="startsGroup(i)" x-text="item.group" class="px-2 pb-1 pt-3 text-[11px] font-medium uppercase tracking-wide text-muted-foreground"></div><div role="option" :aria-selected="active === i" x-on:click="choose(i)" x-on:mousemove="active = i" :class="active === i &amp;&amp; 'bg-accent text-accent-foreground'" class="flex cursor-pointer items-center gap-2 rounded-md px-2 py-2 text-sm"><span class="flex-1 truncate" x-text="item.label"></span><span x-show="item.shortcut" x-text="item.shortcut" class="text-[11px] text-muted-foreground"></span></div></div></template><p x-show="results().length === 0" class="px-4 py-8 text-center text-sm text-muted-foreground">Nothing found.</p></div></div></div>
1169
+ </div>
1170
+
1171
+ ### Command usage
1172
+
1173
+ ```tsx
1174
+ <Command
1175
+ items={[
1176
+ { label: "Products", href: "/admin/products", group: "Go to" },
1177
+ { label: "New order", href: "/admin/orders/create", group: "Create" },
1178
+ ]}
1179
+ />
1180
+ ```
1181
+
1182
+ ### Command props
1183
+
1184
+ <table>
1185
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1186
+ <tbody>
1187
+ <tr><td><code>items</code></td><td><code>CommandItem[]</code></td><td>—</td><td>Destinations and actions, each with an optional group.</td></tr>
1188
+ <tr><td><code>hotkey</code></td><td><code>string | null</code></td><td><code>"k"</code></td><td>Key that opens it with the platform modifier.</td></tr>
1189
+ <tr><td><code>placeholder</code></td><td><code>string</code></td><td><code>"Search…"</code></td><td>Placeholder in the search box.</td></tr>
1190
+ <tr><td><code>emptyMessage</code></td><td><code>string</code></td><td><code>"Nothing found."</code></td><td>Shown when nothing matches.</td></tr>
1191
+ </tbody>
1192
+ </table>
1193
+
1194
+ <a id="components-context-menu"></a>
1195
+
1196
+ ## ContextMenu
1197
+
1198
+ Right-click menu.
1199
+
1200
+ ### ContextMenu installation
1201
+
1202
+ ```sh
1203
+ bun zt flow:add context-menu
1204
+ ```
1205
+
1206
+ Or import directly from the package: `import { ContextMenu } from "@zerotal/flow-ui";`
1207
+
1208
+ ### ContextMenu preview
1209
+
1210
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1211
+ <div x-data="{ open: false, x: 0, y: 0 }" x-on:contextmenu=" $event.preventDefault(); open = true; $nextTick(() =&gt; { const menu = $refs.menu; const w = menu.offsetWidth, h = menu.offsetHeight; x = Math.min($event.clientX, window.innerWidth - w - 8); y = Math.min($event.clientY, window.innerHeight - h - 8); }); " class="contents"><div class="rounded-md border border-dashed border-border px-4 py-6 text-center text-sm text-muted-foreground">Right-click me</div><div x-ref="menu" x-show="open" x-cloak x-on:click.outside="open = false" x-on:keydown.escape.window="open = false" x-bind:style="`position:fixed;left:${x}px;top:${y}px`" role="menu" class="z-50 min-w-44 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"><button type="button" role="menuitem" class="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Open</span><span class="text-xs text-muted-foreground">↵</span></button><div role="separator" class="-mx-1 my-1 h-px bg-border"></div><button type="button" role="menuitem" class="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 text-destructive hover:bg-destructive/10"><span class="flex-1">Delete</span></button></div></div>
1212
+ </div>
1213
+
1214
+ ### ContextMenu usage
1215
+
1216
+ ```tsx
1217
+ <ContextMenu
1218
+ items={[
1219
+ { label: "Open", action: "$flow.open(id)" },
1220
+ { separator: true },
1221
+ { label: "Delete", action: "$flow.remove(id)", danger: true },
1222
+ ]}
1223
+ >
1224
+ <div>Right-click me</div>
1225
+ </ContextMenu>
1226
+ ```
1227
+
1228
+ ### ContextMenu props
1229
+
1230
+ <table>
1231
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1232
+ <tbody>
1233
+ <tr><td><code>items</code></td><td><code>ContextMenuItem[]</code></td><td>—</td><td>Entries, dividers and destructive actions.</td></tr>
1234
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
1235
+ </tbody>
1236
+ </table>
1237
+
1238
+ <a id="components-menubar"></a>
1239
+
1240
+ ## Menubar
1241
+
1242
+ Application menu bar.
1243
+
1244
+ ### Menubar installation
1245
+
1246
+ ```sh
1247
+ bun zt flow:add menubar
1248
+ ```
1249
+
1250
+ Or import directly from the package: `import { Menubar } from "@zerotal/flow-ui";`
1251
+
1252
+ ### Menubar preview
1253
+
1254
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1255
+ <div x-data="{ open: -1 }" x-on:click.outside="open = -1" x-on:keydown.escape.window="open = -1" role="menubar" class="flex items-center gap-0.5 rounded-md border border-border bg-card p-1"><div class="relative"><button type="button" role="menuitem" x-on:click="open = open === 0 ? -1 : 0" x-on:mouseenter="if (open !== -1) open = 0" x-bind:aria-expanded="open === 0" x-bind:class="open === 0 &amp;&amp; 'bg-accent text-accent-foreground'" class="rounded-sm px-3 py-1 text-sm font-medium outline-none transition-colors hover:bg-accent hover:text-accent-foreground">File</button><div x-show="open === 0" x-cloak x-transition role="menu" class="absolute left-0 top-full z-50 mt-1 min-w-48 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">New</span><span class="text-xs text-muted-foreground">⌘N</span></button><div role="separator" class="-mx-1 my-1 h-px bg-border"></div><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Export</span></button></div></div><div class="relative"><button type="button" role="menuitem" x-on:click="open = open === 1 ? -1 : 1" x-on:mouseenter="if (open !== -1) open = 1" x-bind:aria-expanded="open === 1" x-bind:class="open === 1 &amp;&amp; 'bg-accent text-accent-foreground'" class="rounded-sm px-3 py-1 text-sm font-medium outline-none transition-colors hover:bg-accent hover:text-accent-foreground">Edit</button><div x-show="open === 1" x-cloak x-transition role="menu" class="absolute left-0 top-full z-50 mt-1 min-w-48 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Undo</span><span class="text-xs text-muted-foreground">⌘Z</span></button><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Redo</span><span class="text-xs text-muted-foreground">⇧⌘Z</span></button></div></div><div class="relative"><button type="button" role="menuitem" x-on:click="open = open === 2 ? -1 : 2" x-on:mouseenter="if (open !== -1) open = 2" x-bind:aria-expanded="open === 2" x-bind:class="open === 2 &amp;&amp; 'bg-accent text-accent-foreground'" class="rounded-sm px-3 py-1 text-sm font-medium outline-none transition-colors hover:bg-accent hover:text-accent-foreground">View</button><div x-show="open === 2" x-cloak x-transition role="menu" class="absolute left-0 top-full z-50 mt-1 min-w-48 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Zoom in</span></button><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Zoom out</span></button></div></div></div>
1256
+ </div>
1257
+
1258
+ ### Menubar usage
1259
+
1260
+ ```tsx
1261
+ <Menubar
1262
+ menus={[
1263
+ { label: "File", items: [{ label: "New", shortcut: "⌘N" }] },
1264
+ { label: "Edit", items: [{ label: "Undo", shortcut: "⌘Z" }] },
1265
+ ]}
1266
+ />
1267
+ ```
1268
+
1269
+ ### Menubar props
1270
+
1271
+ <table>
1272
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1273
+ <tbody>
1274
+ <tr><td><code>menus</code></td><td><code>MenubarMenu[]</code></td><td>—</td><td>Top-level menus, each with its own items.</td></tr>
1275
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
1276
+ </tbody>
1277
+ </table>
1278
+
1279
+ <a id="components-navigation-menu"></a>
1280
+
1281
+ ## NavigationMenu
1282
+
1283
+ Site nav with dropdown panels.
1284
+
1285
+ ### NavigationMenu installation
1286
+
1287
+ ```sh
1288
+ bun zt flow:add navigation-menu
1289
+ ```
1290
+
1291
+ Or import directly from the package: `import { NavigationMenu } from "@zerotal/flow-ui";`
1292
+
1293
+ ### NavigationMenu preview
1294
+
1295
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1296
+ <nav class="relative flex items-center gap-1"><a href="#" flow:navigate class="inline-flex h-9 items-center gap-1 rounded-md px-3 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring">Docs</a><div x-data="{ open: false }" x-on:mouseenter="open = true" x-on:mouseleave="open = false" x-on:focusin="open = true" x-on:focusout="open = false" class="relative"><button type="button" x-bind:aria-expanded="open" class="inline-flex h-9 items-center gap-1 rounded-md px-3 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring">Products<svg class="h-3 w-3 transition-transform" x-bind:class="open &amp;&amp; 'rotate-180'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></button><div x-show="open" x-cloak x-transition class="absolute left-0 top-full z-50 mt-1.5 rounded-lg border border-border bg-popover p-2 text-popover-foreground shadow-lg w-72 space-y-1"><a href="#" flow:navigate class="flex gap-3 rounded-md p-3 transition-colors hover:bg-accent hover:text-accent-foreground"><span class="min-w-0"><span class="block text-sm font-medium">Admin</span><span class="mt-0.5 block text-xs leading-snug text-muted-foreground">Declarative back office</span></span></a><a href="#" flow:navigate class="flex gap-3 rounded-md p-3 transition-colors hover:bg-accent hover:text-accent-foreground"><span class="min-w-0"><span class="block text-sm font-medium">Flow</span><span class="mt-0.5 block text-xs leading-snug text-muted-foreground">Server-driven reactivity</span></span></a></div></div></nav>
1297
+ </div>
1298
+
1299
+ ### NavigationMenu usage
1300
+
1301
+ ```tsx
1302
+ <NavigationMenu
1303
+ items={[
1304
+ { label: "Docs", href: "/docs" },
1305
+ { label: "Products", panel: [{ label: "Admin", href: "/admin", description: "Back office" }] },
1306
+ ]}
1307
+ />
1308
+ ```
1309
+
1310
+ ### NavigationMenu props
1311
+
1312
+ <table>
1313
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1314
+ <tbody>
1315
+ <tr><td><code>items</code></td><td><code>NavigationMenuItem[]</code></td><td>—</td><td>Links, some of which open a panel.</td></tr>
1316
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
1317
+ </tbody>
1318
+ </table>
1319
+
1320
+ <a id="components-sidebar"></a>
1321
+
1322
+ ## Sidebar
1323
+
1324
+ App shell nav rail with a mobile drawer.
1325
+
1326
+ ### Sidebar installation
1327
+
1328
+ ```sh
1329
+ bun zt flow:add sidebar
1330
+ ```
1331
+
1332
+ Or import directly from the package: `import { Sidebar } from "@zerotal/flow-ui";`
1333
+
1334
+ ### Sidebar preview
1335
+
1336
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1337
+ <div class="h-64 overflow-hidden rounded-lg border border-border"><aside class="flex w-64 flex-col border-r border-border bg-card"><a href="/" flow:navigate class="flex h-14 shrink-0 items-center gap-2 border-b border-border px-4"><span class="min-w-0"><span class="block truncate text-sm font-semibold">Zerotal</span><span class="block truncate text-[11px] text-muted-foreground">Back office</span></span></a><nav class="flex-1 space-y-4 overflow-y-auto p-3"><div class="space-y-1"><p class="px-2.5 pb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/70">Shop</p><a href="/admin/products" flow:navigate aria-current="page" class="group flex items-center gap-2.5 rounded-md px-2.5 py-2 text-sm font-medium transition-colors hover:bg-accent/50 hover:text-foreground [&amp;[aria-current]]:bg-accent [&amp;[aria-current]]:text-accent-foreground bg-accent text-accent-foreground"><span class="truncate">Products</span><span class="ml-auto inline-flex min-w-5 items-center justify-center rounded-full px-1.5 py-0.5 text-[10px] font-semibold leading-none bg-muted text-muted-foreground">12</span></a><a href="/admin/orders" flow:navigate class="group flex items-center gap-2.5 rounded-md px-2.5 py-2 text-sm font-medium transition-colors text-muted-foreground hover:bg-accent/50 hover:text-foreground [&amp;[aria-current]]:bg-accent [&amp;[aria-current]]:text-accent-foreground"><span class="truncate">Orders</span></a></div><div class="space-y-1"><p class="px-2.5 pb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/70">System</p><a href="/admin/settings" flow:navigate class="group flex items-center gap-2.5 rounded-md px-2.5 py-2 text-sm font-medium transition-colors text-muted-foreground hover:bg-accent/50 hover:text-foreground [&amp;[aria-current]]:bg-accent [&amp;[aria-current]]:text-accent-foreground"><span class="truncate">Settings</span></a></div></nav></aside></div>
1338
+ </div>
1339
+
1340
+ ### Sidebar usage
1341
+
1342
+ ```tsx
1343
+ <Sidebar
1344
+ brand="Zerotal"
1345
+ tagline="Back office"
1346
+ current={path}
1347
+ groups={[{ label: "Shop", items: [{ label: "Products", href: "/admin/products", badge: 12 }] }]}
1348
+ />
1349
+ ```
1350
+
1351
+ ### Sidebar props
1352
+
1353
+ <table>
1354
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1355
+ <tbody>
1356
+ <tr><td><code>groups</code></td><td><code>SidebarGroup[]</code></td><td>—</td><td>Nav tree, optionally nested one level.</td></tr>
1357
+ <tr><td><code>current</code></td><td><code>string</code></td><td>—</td><td>Current path, for marking the active item.</td></tr>
1358
+ <tr><td><code>collapsible</code></td><td><code>boolean</code></td><td><code>true</code></td><td>Render the mobile drawer toggle.</td></tr>
1359
+ <tr><td><code>footer</code></td><td><code>node</code></td><td>—</td><td>Pinned to the bottom — a user menu, a version.</td></tr>
1360
+ </tbody>
1361
+ </table>
1362
+
1363
+ <a id="components-breadcrumb"></a>
1364
+
1365
+ ## Breadcrumb
1366
+
1367
+ Trail showing where a page sits.
1368
+
1369
+ ### Breadcrumb installation
1370
+
1371
+ ```sh
1372
+ bun zt flow:add breadcrumb
1373
+ ```
1374
+
1375
+ Or import directly from the package: `import { Breadcrumb } from "@zerotal/flow-ui";`
1376
+
1377
+ ### Breadcrumb preview
1378
+
1379
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1380
+ <nav aria-label="Breadcrumb" class="text-xs text-muted-foreground"><ol class="flex flex-wrap items-center gap-1.5"><li class="inline-flex items-center gap-1.5"><a href="#" flow:navigate class="inline-flex items-center gap-1 transition-colors hover:text-foreground">Dashboard</a></li><li class="inline-flex items-center gap-1.5"><svg class="h-3.5 w-3.5 shrink-0 text-muted-foreground/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg><a href="#" flow:navigate class="inline-flex items-center gap-1 transition-colors hover:text-foreground">Products</a></li><li class="inline-flex items-center gap-1.5"><svg class="h-3.5 w-3.5 shrink-0 text-muted-foreground/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg><span aria-current="page" class="inline-flex items-center gap-1 text-foreground">Desk Lamp</span></li></ol></nav>
1381
+ </div>
1382
+
1383
+ ### Breadcrumb usage
1384
+
1385
+ ```tsx
1386
+ <Breadcrumb
1387
+ items={[
1388
+ { label: "Dashboard", href: "/admin" },
1389
+ { label: "Products", href: "/admin/products" },
1390
+ { label: "Desk Lamp" },
1391
+ ]}
1392
+ />
1393
+ ```
1394
+
1395
+ ### Breadcrumb props
1396
+
1397
+ <table>
1398
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1399
+ <tbody>
1400
+ <tr><td><code>items</code></td><td><code>BreadcrumbItem[]</code></td><td>—</td><td>The trail. The last item renders as the current page.</td></tr>
1401
+ <tr><td><code>maxItems</code></td><td><code>number</code></td><td>—</td><td>Collapse a longer trail to first + last few.</td></tr>
1402
+ <tr><td><code>separator</code></td><td><code>node</code></td><td>—</td><td>What sits between items.</td></tr>
1403
+ </tbody>
1404
+ </table>
1405
+
1406
+ <a id="components-pagination"></a>
1407
+
1408
+ ## Pagination
1409
+
1410
+ Page links with a windowed number range.
1411
+
1412
+ ### Pagination installation
1413
+
1414
+ ```sh
1415
+ bun zt flow:add pagination
1416
+ ```
1417
+
1418
+ Or import directly from the package: `import { Pagination } from "@zerotal/flow-ui";`
1419
+
1420
+ ### Pagination preview
1421
+
1422
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1423
+ <nav aria-label="Pagination" class="flex flex-wrap items-center justify-between gap-3"><p class="text-sm text-muted-foreground">61–80 of 231</p><div class="flex items-center gap-1"><a href="#" flow:navigate aria-label="Previous page" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"></path></svg></a><a href="#" flow:navigate aria-label="Page 1" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">1</a><a href="#" flow:navigate aria-label="Page 2" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">2</a><a href="#" flow:navigate aria-label="Page 3" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">3</a><a href="#" flow:navigate aria-current="page" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors bg-primary text-primary-foreground">4</a><a href="#" flow:navigate aria-label="Page 5" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">5</a><a href="#" flow:navigate aria-label="Page 6" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">6</a><span class="px-1 text-sm text-muted-foreground" aria-hidden="true">…</span><a href="#" flow:navigate aria-label="Page 12" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">12</a><a href="#" flow:navigate aria-label="Next page" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg></a></div></nav>
1424
+ </div>
1425
+
1426
+ ### Pagination usage
1427
+
1428
+ ```tsx
1429
+ <Pagination
1430
+ page={p.page}
1431
+ lastPage={p.lastPage}
1432
+ total={p.total}
1433
+ perPage={p.perPage}
1434
+ href={(n) => `?page=${n}`}
1435
+ />
1436
+ ```
1437
+
1438
+ ### Pagination props
1439
+
1440
+ <table>
1441
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1442
+ <tbody>
1443
+ <tr><td><code>page</code></td><td><code>number</code></td><td>—</td><td>Current page, 1-based.</td></tr>
1444
+ <tr><td><code>lastPage</code></td><td><code>number</code></td><td>—</td><td>How many pages there are.</td></tr>
1445
+ <tr><td><code>href</code></td><td><code>(page: number) =&gt; string</code></td><td>—</td><td>Builds each page's URL, keeping your other params.</td></tr>
1446
+ <tr><td><code>total</code></td><td><code>number</code></td><td>—</td><td>Row count, shown as “1–20 of 231”.</td></tr>
1447
+ <tr><td><code>siblings</code></td><td><code>number</code></td><td><code>5</code></td><td>Numbered links around the current page.</td></tr>
1448
+ </tbody>
1449
+ </table>
1450
+
1451
+ <a id="components-field"></a>
1452
+
1453
+ ## Field
1454
+
1455
+ Label + control + description + error.
1456
+
1457
+ ### Field installation
1458
+
1459
+ ```sh
1460
+ bun zt flow:add field
1461
+ ```
1462
+
1463
+ Or import directly from the package: `import { Field } from "@zerotal/flow-ui";`
1464
+
1465
+ ### Field preview
1466
+
1467
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1468
+ <div class="w-full max-w-sm space-y-4"><div class="space-y-1.5"><label for="flow-field-1" class="block text-sm font-medium leading-none text-foreground">Email<span class="ml-0.5 text-destructive" aria-hidden="true">*</span></label><div class="space-y-1.5"><input type="email" data-slot="input" class="flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" placeholder="ada@example.com" id="flow-field-1" aria-describedby="flow-field-1-description" aria-required="true"><p id="flow-field-1-description" class="text-xs text-muted-foreground">We never share it.</p></div></div><div class="space-y-1.5"><label for="flow-field-2" class="block text-sm font-medium leading-none text-foreground">Password</label><div class="space-y-1.5"><input type="password" data-slot="input" class="flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" id="flow-field-2" aria-describedby="flow-field-2-error" aria-invalid="true"><p id="flow-field-2-error" role="alert" class="text-xs font-medium text-destructive">Must be at least 8 characters.</p></div></div></div>
1469
+ </div>
1470
+
1471
+ ### Field usage
1472
+
1473
+ ```tsx
1474
+ <Field label="Email" description="We never share it." error={errors.email} required>
1475
+ <Input type="email" flow:model="form.email" />
1476
+ </Field>
1477
+ ```
1478
+
1479
+ ### Field props
1480
+
1481
+ <table>
1482
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1483
+ <tbody>
1484
+ <tr><td><code>label</code></td><td><code>node</code></td><td>—</td><td>Associated with the control by a generated id.</td></tr>
1485
+ <tr><td><code>description</code></td><td><code>node</code></td><td>—</td><td>Helper text, linked by aria-describedby.</td></tr>
1486
+ <tr><td><code>error</code></td><td><code>node</code></td><td>—</td><td>Its presence marks the field invalid and announces it.</td></tr>
1487
+ <tr><td><code>required</code></td><td><code>boolean</code></td><td>—</td><td>Shows the marker and sets aria-required.</td></tr>
1488
+ <tr><td><code>orientation</code></td><td><code>"vertical" | "horizontal"</code></td><td><code>"vertical"</code></td><td>Label above or beside.</td></tr>
1489
+ </tbody>
1490
+ </table>
1491
+
1492
+ <a id="components-input-group"></a>
1493
+
1494
+ ## InputGroup
1495
+
1496
+ Input with affixes or addons.
1497
+
1498
+ ### InputGroup installation
1499
+
1500
+ ```sh
1501
+ bun zt flow:add input-group
1502
+ ```
1503
+
1504
+ Or import directly from the package: `import { InputGroup } from "@zerotal/flow-ui";`
1505
+
1506
+ ### InputGroup preview
1507
+
1508
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1509
+ <div class="w-full max-w-sm space-y-3"><div class="flex w-full items-stretch"><div class="relative flex-1"><span class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-sm text-muted-foreground">R</span><div class="[&amp;&gt;input]:w-full [&amp;&gt;input]:pl-8"><input type="text" data-slot="input" class="flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" placeholder="0.00"></div></div></div><div class="flex w-full items-stretch"><div class="relative flex-1"><div class="[&amp;&gt;input]:w-full [&amp;&gt;input]:rounded-r-none"><input type="text" data-slot="input" class="flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" value="kln_live_8f3a"></div></div><span class="inline-flex shrink-0 items-center [&amp;&gt;button]:rounded-l-none"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Copy</button></span></div></div>
1510
+ </div>
1511
+
1512
+ ### InputGroup usage
1513
+
1514
+ ```tsx
1515
+ <InputGroup prefix="R"><Input flow:model="form.price" /></InputGroup>
1516
+ <InputGroup addonAfter={<Button>Copy</Button>}><Input value={key} /></InputGroup>
1517
+ ```
1518
+
1519
+ ### InputGroup props
1520
+
1521
+ <table>
1522
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1523
+ <tbody>
1524
+ <tr><td><code>prefix</code></td><td><code>node</code></td><td>—</td><td>Inside the border, before the text.</td></tr>
1525
+ <tr><td><code>suffix</code></td><td><code>node</code></td><td>—</td><td>Inside the border, after the text.</td></tr>
1526
+ <tr><td><code>addonBefore</code></td><td><code>node</code></td><td>—</td><td>Outside the border, in its own cell.</td></tr>
1527
+ <tr><td><code>addonAfter</code></td><td><code>node</code></td><td>—</td><td>Outside the border — often a button.</td></tr>
1528
+ </tbody>
1529
+ </table>
1530
+
1531
+ <a id="components-input-otp"></a>
1532
+
1533
+ ## InputOTP
1534
+
1535
+ One-time-code input.
1536
+
1537
+ ### InputOTP installation
1538
+
1539
+ ```sh
1540
+ bun zt flow:add input-otp
1541
+ ```
1542
+
1543
+ Or import directly from the package: `import { InputOTP } from "@zerotal/flow-ui";`
1544
+
1545
+ ### InputOTP preview
1546
+
1547
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1548
+ <div x-data="{ value: '' }" class="relative inline-flex" x-on:click="$refs.field.focus()"><input x-ref="field" type="text" inputmode="numeric" autocomplete="one-time-code" maxlength="6" pattern="[0-9]*" x-model="value" class="absolute inset-0 z-10 h-full w-full cursor-default opacity-0"><div class="flex items-center gap-2"><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 0 &amp;&amp; 'ring-2 ring-ring border-ring'"><span x-text="value[0] || ''"></span></div><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 1 &amp;&amp; 'ring-2 ring-ring border-ring'"><span x-text="value[1] || ''"></span></div><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 2 &amp;&amp; 'ring-2 ring-ring border-ring'"><span x-text="value[2] || ''"></span></div><span class="w-2"></span><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 3 &amp;&amp; 'ring-2 ring-ring border-ring'"><span x-text="value[3] || ''"></span></div><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 4 &amp;&amp; 'ring-2 ring-ring border-ring'"><span x-text="value[4] || ''"></span></div><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 5 &amp;&amp; 'ring-2 ring-ring border-ring'"><span x-text="value[5] || ''"></span></div></div></div>
1549
+ </div>
1550
+
1551
+ ### InputOTP usage
1552
+
1553
+ ```tsx
1554
+ <InputOTP length={6} groupAfter={3} flow:model="form.code" />
1555
+ ```
1556
+
1557
+ ### InputOTP props
1558
+
1559
+ <table>
1560
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1561
+ <tbody>
1562
+ <tr><td><code>length</code></td><td><code>number</code></td><td><code>6</code></td><td>How many characters the code has.</td></tr>
1563
+ <tr><td><code>numeric</code></td><td><code>boolean</code></td><td><code>true</code></td><td>Restrict to digits.</td></tr>
1564
+ <tr><td><code>groupAfter</code></td><td><code>number</code></td><td>—</td><td>Insert a wider gap after this many boxes.</td></tr>
1565
+ </tbody>
1566
+ </table>
1567
+
1568
+ <a id="components-combobox"></a>
1569
+
1570
+ ## Combobox
1571
+
1572
+ Autocomplete over many options.
1573
+
1574
+ ### Combobox installation
1575
+
1576
+ ```sh
1577
+ bun zt flow:add combobox
1578
+ ```
1579
+
1580
+ Or import directly from the package: `import { Combobox } from "@zerotal/flow-ui";`
1581
+
1582
+ ### Combobox preview
1583
+
1584
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1585
+ <div class="w-full max-w-sm"><div x-data="{ open: false, active: -1, query: '' }" class="flow-combobox relative"><input type="text" role="combobox" autocomplete="off" aria-autocomplete="list" aria-controls="flow-combobox-list" :aria-expanded="open" placeholder="Search brands…" :value="query" x-on:input="onInput($event)" x-on:focus="openList()" x-on:click="openList()" x-on:keydown="onKey($event)" class="flow-combobox-input flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50"><ul id="flow-combobox-list" role="listbox" x-show="open" x-cloak x-on:click.outside="close()" :aria-activedescendant="null" class="flow-combobox-options absolute z-50 mt-1 max-h-60 w-full overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"><li role="option" data-value="&quot;1&quot;" data-label="Acme" aria-selected="false" :aria-selected="isSelected(&quot;1&quot;)" :data-selected="isSelected(&quot;1&quot;) ? '' : null" :data-active="active === 0 ? '' : null" x-on:click="selectEl($event.currentTarget)" x-on:mousemove="active = 0" class="flow-combobox-option cursor-pointer rounded-sm px-2 py-1.5 text-sm outline-none data-active:bg-accent data-active:text-accent-foreground data-selected:font-medium data-disabled:pointer-events-none data-disabled:opacity-50" x-show="!query || &quot;acme&quot;.includes(query.toLowerCase())">Acme</li><li role="option" data-value="&quot;2&quot;" data-label="Globex" aria-selected="false" :aria-selected="isSelected(&quot;2&quot;)" :data-selected="isSelected(&quot;2&quot;) ? '' : null" :data-active="active === 1 ? '' : null" x-on:click="selectEl($event.currentTarget)" x-on:mousemove="active = 1" class="flow-combobox-option cursor-pointer rounded-sm px-2 py-1.5 text-sm outline-none data-active:bg-accent data-active:text-accent-foreground data-selected:font-medium data-disabled:pointer-events-none data-disabled:opacity-50" x-show="!query || &quot;globex&quot;.includes(query.toLowerCase())">Globex</li><li role="option" data-value="&quot;3&quot;" data-label="Initech" aria-selected="false" :aria-selected="isSelected(&quot;3&quot;)" :data-selected="isSelected(&quot;3&quot;) ? '' : null" :data-active="active === 2 ? '' : null" x-on:click="selectEl($event.currentTarget)" x-on:mousemove="active = 2" class="flow-combobox-option cursor-pointer rounded-sm px-2 py-1.5 text-sm outline-none data-active:bg-accent data-active:text-accent-foreground data-selected:font-medium data-disabled:pointer-events-none data-disabled:opacity-50" x-show="!query || &quot;initech&quot;.includes(query.toLowerCase())">Initech</li></ul></div></div>
1586
+ </div>
1587
+
1588
+ ### Combobox usage
1589
+
1590
+ ```tsx
1591
+ <Combobox bind={this.brandId} options={brands} placeholder="Search brands…" />
1592
+ ```
1593
+
1594
+ ### Combobox props
1595
+
1596
+ <table>
1597
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1598
+ <tbody>
1599
+ <tr><td><code>bind</code></td><td><code>@expose value</code></td><td>—</td><td>The chosen value.</td></tr>
1600
+ <tr><td><code>options</code></td><td><code>ComboboxOption[]</code></td><td>—</td><td>Choices, filtered as you type.</td></tr>
1601
+ <tr><td><code>query</code></td><td><code>@expose value</code></td><td>—</td><td>Bind to filter on the server instead of the client.</td></tr>
1602
+ </tbody>
1603
+ </table>
1604
+
1605
+ <a id="components-slider"></a>
1606
+
1607
+ ## Slider
1608
+
1609
+ Value chosen from a range.
1610
+
1611
+ ### Slider installation
1612
+
1613
+ ```sh
1614
+ bun zt flow:add slider
1615
+ ```
1616
+
1617
+ Or import directly from the package: `import { Slider } from "@zerotal/flow-ui";`
1618
+
1619
+ ### Slider preview
1620
+
1621
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1622
+ <div class="w-full max-w-sm"><div value="64" x-data="flowSlider({&quot;name&quot;:null,&quot;min&quot;:0,&quot;max&quot;:100,&quot;step&quot;:1})" :data-dragging="dragging ? '' : null" class="flow-slider flex items-center gap-3"><input type="range" min="0" max="100" step="1" value="0" style="background:linear-gradient(to right, var(--primary) 0%, var(--secondary) 0%)" :style="`background:linear-gradient(to right, var(--primary) ${percent()}%, var(--secondary) ${percent()}%)`" x-model.number="value" x-on:input="onInput($event)" x-on:change="commit()" x-on:keyup.debounce.200ms="commit()" class="flow-slider-input h-2 w-full cursor-pointer appearance-none rounded-full outline-none disabled:cursor-not-allowed disabled:opacity-50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background [&amp;::-webkit-slider-thumb]:h-4 [&amp;::-webkit-slider-thumb]:w-4 [&amp;::-webkit-slider-thumb]:appearance-none [&amp;::-webkit-slider-thumb]:rounded-full [&amp;::-webkit-slider-thumb]:border-2 [&amp;::-webkit-slider-thumb]:border-primary [&amp;::-webkit-slider-thumb]:bg-background [&amp;::-webkit-slider-thumb]:shadow [&amp;::-webkit-slider-thumb]:transition-transform [&amp;::-webkit-slider-thumb]:hover:scale-110 [&amp;::-moz-range-thumb]:h-4 [&amp;::-moz-range-thumb]:w-4 [&amp;::-moz-range-thumb]:rounded-full [&amp;::-moz-range-thumb]:border-2 [&amp;::-moz-range-thumb]:border-primary [&amp;::-moz-range-thumb]:bg-background [&amp;::-moz-range-thumb]:shadow"><span class="w-14 shrink-0 text-right text-sm tabular-nums text-muted-foreground"><span x-text="value"></span></span></div></div>
1623
+ </div>
1624
+
1625
+ ### Slider usage
1626
+
1627
+ ```tsx
1628
+ <Slider value={this.volume} max={100} showValue />
1629
+ ```
1630
+
1631
+ ### Slider props
1632
+
1633
+ <table>
1634
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1635
+ <tbody>
1636
+ <tr><td><code>value</code></td><td><code>number</code></td><td>—</td><td>Current value.</td></tr>
1637
+ <tr><td><code>min</code></td><td><code>number</code></td><td><code>0</code></td><td>Lower bound.</td></tr>
1638
+ <tr><td><code>max</code></td><td><code>number</code></td><td><code>100</code></td><td>Upper bound.</td></tr>
1639
+ <tr><td><code>showValue</code></td><td><code>boolean</code></td><td>—</td><td>Show the value beside the track.</td></tr>
1640
+ </tbody>
1641
+ </table>
1642
+
1643
+ <a id="components-toggle"></a>
1644
+
1645
+ ## Toggle
1646
+
1647
+ Pressed-state button (+ toggle group).
1648
+
1649
+ ### Toggle installation
1650
+
1651
+ ```sh
1652
+ bun zt flow:add toggle
1653
+ ```
1654
+
1655
+ Or import directly from the package: `import { Toggle } from "@zerotal/flow-ui";`
1656
+
1657
+ ### Toggle preview
1658
+
1659
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1660
+ <div class="flex flex-wrap items-center gap-3"><button type="button" aria-pressed="true" class="inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-muted-foreground h-9 px-2.5 bg-accent text-accent-foreground">B</button><button type="button" aria-pressed="false" class="inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-muted-foreground border border-input bg-transparent h-9 px-2.5">I</button><div value="grid" role="radiogroup" x-data="flowToggleGroup({&quot;name&quot;:null,&quot;multiple&quot;:false})" class="flow-toggle-group inline-flex items-center gap-1 rounded-md"><button type="button" aria-pressed="false" :aria-pressed="isOn(&quot;list&quot;)" :data-pressed="isOn(&quot;list&quot;) ? '' : null" x-on:click="toggle(&quot;list&quot;)" class="flow-toggle inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-muted-foreground border border-input bg-transparent h-9 px-2.5 data-pressed:bg-accent data-pressed:text-accent-foreground">List</button><button type="button" aria-pressed="false" :aria-pressed="isOn(&quot;grid&quot;)" :data-pressed="isOn(&quot;grid&quot;) ? '' : null" x-on:click="toggle(&quot;grid&quot;)" class="flow-toggle inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-muted-foreground border border-input bg-transparent h-9 px-2.5 data-pressed:bg-accent data-pressed:text-accent-foreground">Grid</button></div></div>
1661
+ </div>
1662
+
1663
+ ### Toggle usage
1664
+
1665
+ ```tsx
1666
+ <Toggle pressed={this.bold}>B</Toggle>
1667
+ <ToggleGroup value={this.view} options={[
1668
+ { value: "list", label: "List" }, { value: "grid", label: "Grid" },
1669
+ ]} />
1670
+ ```
1671
+
1672
+ ### Toggle props
1673
+
1674
+ <table>
1675
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1676
+ <tbody>
1677
+ <tr><td><code>pressed</code></td><td><code>boolean</code></td><td>—</td><td>Whether the toggle is on (sets aria-pressed).</td></tr>
1678
+ <tr><td><code>variant</code></td><td><code>"default" | "outline"</code></td><td><code>"default"</code></td><td>Visual style.</td></tr>
1679
+ <tr><td><code>options</code></td><td><code>ToggleOption[]</code></td><td>—</td><td>ToggleGroup only — the members.</td></tr>
1680
+ <tr><td><code>type</code></td><td><code>"single" | "multiple"</code></td><td><code>"single"</code></td><td>ToggleGroup only — how many may be on.</td></tr>
1681
+ </tbody>
1682
+ </table>
1683
+
1684
+ <a id="components-button-group"></a>
1685
+
1686
+ ## ButtonGroup
1687
+
1688
+ Buttons joined into one control.
1689
+
1690
+ ### ButtonGroup installation
1691
+
1692
+ ```sh
1693
+ bun zt flow:add button-group
1694
+ ```
1695
+
1696
+ Or import directly from the package: `import { ButtonGroup } from "@zerotal/flow-ui";`
1697
+
1698
+ ### ButtonGroup preview
1699
+
1700
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1701
+ <div role="group" class="inline-flex flex-row [&amp;&gt;*:not(:first-child)]:rounded-l-none [&amp;&gt;*:not(:last-child)]:rounded-r-none [&amp;&gt;*:not(:first-child)]:-ml-px [&amp;&gt;*:focus-visible]:relative [&amp;&gt;*:focus-visible]:z-10"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Day</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Week</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Month</button></div>
1702
+ </div>
1703
+
1704
+ ### ButtonGroup usage
1705
+
1706
+ ```tsx
1707
+ <ButtonGroup>
1708
+ <Button variant="outline">Day</Button>
1709
+ <Button variant="outline">Week</Button>
1710
+ </ButtonGroup>
1711
+ ```
1712
+
1713
+ ### ButtonGroup props
1714
+
1715
+ <table>
1716
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1717
+ <tbody>
1718
+ <tr><td><code>orientation</code></td><td><code>"horizontal" | "vertical"</code></td><td><code>"horizontal"</code></td><td>Direction the members join in.</td></tr>
1719
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
1720
+ </tbody>
1721
+ </table>
1722
+
1723
+ <a id="components-calendar"></a>
1724
+
1725
+ ## Calendar
1726
+
1727
+ Month grid for picking or laying out dates.
1728
+
1729
+ ### Calendar installation
1730
+
1731
+ ```sh
1732
+ bun zt flow:add calendar
1733
+ ```
1734
+
1735
+ Or import directly from the package: `import { Calendar } from "@zerotal/flow-ui";`
1736
+
1737
+ ### Calendar preview
1738
+
1739
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1740
+ <div class="rounded-lg border border-border bg-card p-3"><div class="mb-2 flex items-center justify-between px-1"><span class="w-7"></span><p class="text-sm font-medium">July 2026</p><span class="w-7"></span></div><div class="grid grid-cols-7 gap-px"><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Mon</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Tue</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Wed</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Thu</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Fri</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Sat</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Sun</div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">29</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">30</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">1</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">2</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">3</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">4</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">5</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">6</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">7</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">8</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">9</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">10</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">11</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">12</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">13</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors bg-primary text-primary-foreground"><span class="">14</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">15</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">16</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">17</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">18</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">19</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">20</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">21</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">22</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">23</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">24</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">25</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">26</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">27</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">28</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">29</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">30</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">31</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">1</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">2</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">3</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">4</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">5</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">6</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">7</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">8</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">9</span></div></div></div>
1741
+ </div>
1742
+
1743
+ ### Calendar usage
1744
+
1745
+ ```tsx
1746
+ <Calendar value={this.due} onSelect={this.pick} />
1747
+ <Calendar month="2026-07" events={[{ date: "2026-07-14", label: "Launch" }]} />
1748
+ ```
1749
+
1750
+ ### Calendar props
1751
+
1752
+ <table>
1753
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1754
+ <tbody>
1755
+ <tr><td><code>month</code></td><td><code>string</code></td><td>—</td><td>`YYYY-MM` to display.</td></tr>
1756
+ <tr><td><code>value</code></td><td><code>string</code></td><td>—</td><td>Selected `YYYY-MM-DD`.</td></tr>
1757
+ <tr><td><code>onSelect</code></td><td><code>handler</code></td><td>—</td><td>Receives the clicked `YYYY-MM-DD`.</td></tr>
1758
+ <tr><td><code>events</code></td><td><code>CalendarEvent[]</code></td><td>—</td><td>Records to lay out across the month.</td></tr>
1759
+ <tr><td><code>min / max</code></td><td><code>string</code></td><td>—</td><td>Selectable range.</td></tr>
1760
+ </tbody>
1761
+ </table>
1762
+
1763
+ <a id="components-date-picker"></a>
1764
+
1765
+ ## DatePicker
1766
+
1767
+ Calendar in a popover.
1768
+
1769
+ ### DatePicker installation
1770
+
1771
+ ```sh
1772
+ bun zt flow:add date-picker
1773
+ ```
1774
+
1775
+ Or import directly from the package: `import { DatePicker } from "@zerotal/flow-ui";`
1776
+
1777
+ ### DatePicker preview
1778
+
1779
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1780
+ <div class="w-56"><div x-data="{ open: false }" class="flow-popover relative inline-block"><span x-on:click="open = !open" :aria-expanded="open" :data-open="open ? '' : null" role="button" tabindex="0" class="flow-popover-button cursor-pointer outline-none"><span class="inline-flex h-9 w-full items-center gap-2 rounded-md border border-input bg-background px-3 text-sm transition-colors hover:bg-accent hover:text-accent-foreground"><svg class="h-4 w-4 shrink-0 text-muted-foreground" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2"></rect><path d="M16 2v4M8 2v4M3 10h18"></path></svg><span>14 Jul 2026</span></span></span><div x-show="open" x-cloak x-transition x-on:click.outside="open = false" x-on:keydown.escape.window="open = false" :data-open="open ? '' : null" class="flow-popover-panel absolute top-full mt-2 left-0 z-50 min-w-[8rem] rounded-md border border-border bg-popover text-popover-foreground shadow-md outline-none p-0"><div class="rounded-lg border-border bg-card p-3 border-0"><div class="mb-2 flex items-center justify-between px-1"><span class="w-7"></span><p class="text-sm font-medium">July 2026</p><span class="w-7"></span></div><div class="grid grid-cols-7 gap-px"><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Mon</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Tue</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Wed</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Thu</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Fri</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Sat</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Sun</div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">29</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">30</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">1</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">2</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">3</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">4</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">5</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">6</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">7</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">8</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">9</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">10</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">11</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">12</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">13</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors bg-primary text-primary-foreground"><span class="">14</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">15</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">16</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">17</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">18</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">19</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">20</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">21</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">22</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">23</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">24</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">25</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">26</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">27</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">28</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">29</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">30</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">31</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">1</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">2</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">3</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">4</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">5</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">6</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">7</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">8</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">9</span></div></div></div></div></div></div>
1781
+ </div>
1782
+
1783
+ ### DatePicker usage
1784
+
1785
+ ```tsx
1786
+ <DatePicker value={this.due} onSelect={this.setDue} />
1787
+ ```
1788
+
1789
+ ### DatePicker props
1790
+
1791
+ <table>
1792
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1793
+ <tbody>
1794
+ <tr><td><code>value</code></td><td><code>string</code></td><td>—</td><td>Selected `YYYY-MM-DD`.</td></tr>
1795
+ <tr><td><code>onSelect</code></td><td><code>handler</code></td><td>—</td><td>Receives the clicked `YYYY-MM-DD`.</td></tr>
1796
+ <tr><td><code>placeholder</code></td><td><code>string</code></td><td><code>"Pick a date"</code></td><td>Shown when nothing is chosen.</td></tr>
1797
+ <tr><td><code>min / max</code></td><td><code>string</code></td><td>—</td><td>Selectable range.</td></tr>
1798
+ </tbody>
1799
+ </table>
1800
+
1801
+ <a id="components-toast"></a>
1802
+
1803
+ ## Toaster
1804
+
1805
+ Host for transient flash messages.
1806
+
1807
+ ### Toaster installation
1808
+
1809
+ ```sh
1810
+ bun zt flow:add toast
1811
+ ```
1812
+
1813
+ Or import directly from the package: `import { Toaster } from "@zerotal/flow-ui";`
1814
+
1815
+ ### Toaster preview
1816
+
1817
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1818
+ <p class="text-sm text-muted-foreground">Mounted once per layout; every <code class="font-mono text-xs">page.flash()</code> lands in it.</p>
1819
+ </div>
1820
+
1821
+ ### Toaster usage
1822
+
1823
+ ```tsx
1824
+ <Toaster position="bottom-right" />;
1825
+ // then anywhere on the server:
1826
+ page.flash("Saved.", "success");
1827
+ ```
1828
+
1829
+ ### Toaster props
1830
+
1831
+ <table>
1832
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1833
+ <tbody>
1834
+ <tr><td><code>position</code></td><td><code>"top-right" | "bottom-right" | …</code></td><td><code>"bottom-right"</code></td><td>Corner it stacks in.</td></tr>
1835
+ <tr><td><code>duration</code></td><td><code>number</code></td><td><code>4000</code></td><td>How long a toast stays, in ms.</td></tr>
1836
+ <tr><td><code>max</code></td><td><code>number</code></td><td><code>4</code></td><td>Most on screen at once.</td></tr>
1837
+ </tbody>
1838
+ </table>
1839
+
1840
+ <a id="components-progress"></a>
1841
+
1842
+ ## Progress
1843
+
1844
+ Determinate progress bar.
1845
+
1846
+ ### Progress installation
1847
+
1848
+ ```sh
1849
+ bun zt flow:add progress
1850
+ ```
1851
+
1852
+ Or import directly from the package: `import { Progress } from "@zerotal/flow-ui";`
1853
+
1854
+ ### Progress preview
1855
+
1856
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1857
+ <div class="w-full max-w-sm space-y-3"><div class="flex items-center gap-3"><div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="62" class="relative h-2 w-full overflow-hidden rounded-full bg-secondary"><div class="h-full rounded-full bg-primary transition-[width] duration-300 ease-out" style="width:62%"></div></div><span class="shrink-0 text-xs tabular-nums text-muted-foreground">62%</span></div><div class="flex items-center gap-3"><div role="progressbar" aria-valuemin="0" aria-valuemax="100" class="relative h-2 w-full overflow-hidden rounded-full bg-secondary"><div class="h-full rounded-full bg-primary transition-[width] duration-300 ease-out w-1/3 animate-[flow-progress_1.2s_ease-in-out_infinite]"></div></div></div></div>
1858
+ </div>
1859
+
1860
+ ### Progress usage
1861
+
1862
+ ```tsx
1863
+ <Progress value={imported} max={total} showValue />
1864
+ <Progress /> {/* indeterminate */}
1865
+ ```
1866
+
1867
+ ### Progress props
1868
+
1869
+ <table>
1870
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1871
+ <tbody>
1872
+ <tr><td><code>value</code></td><td><code>number</code></td><td>—</td><td>Completed amount. Omit for the indeterminate bar.</td></tr>
1873
+ <tr><td><code>max</code></td><td><code>number</code></td><td><code>100</code></td><td>The total.</td></tr>
1874
+ <tr><td><code>showValue</code></td><td><code>boolean</code></td><td>—</td><td>Show the percentage beside the bar.</td></tr>
1875
+ <tr><td><code>label</code></td><td><code>string</code></td><td>—</td><td>Describes what is progressing, for screen readers.</td></tr>
1876
+ </tbody>
1877
+ </table>
1878
+
1879
+ <a id="components-spinner"></a>
1880
+
1881
+ ## Spinner
1882
+
1883
+ Indeterminate loading indicator.
1884
+
1885
+ ### Spinner installation
1886
+
1887
+ ```sh
1888
+ bun zt flow:add spinner
1889
+ ```
1890
+
1891
+ Or import directly from the package: `import { Spinner } from "@zerotal/flow-ui";`
1892
+
1893
+ ### Spinner preview
1894
+
1895
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1896
+ <div class="flex flex-wrap items-center gap-3"><span role="status" class="inline-flex items-center"><svg class="animate-spin text-current h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.2"></circle><path d="M22 12a10 10 0 0 1-10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round"></path></svg><span class="sr-only">Loading</span></span><span role="status" class="inline-flex items-center"><svg class="animate-spin text-current h-5 w-5" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.2"></circle><path d="M22 12a10 10 0 0 1-10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round"></path></svg><span class="sr-only">Loading</span></span><span role="status" class="inline-flex items-center"><svg class="animate-spin text-current h-8 w-8" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.2"></circle><path d="M22 12a10 10 0 0 1-10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round"></path></svg><span class="sr-only">Loading</span></span></div>
1897
+ </div>
1898
+
1899
+ ### Spinner usage
1900
+
1901
+ ```tsx
1902
+ <Spinner />
1903
+ <Button disabled><Spinner size="sm" /> Saving…</Button>
1904
+ ```
1905
+
1906
+ ### Spinner props
1907
+
1908
+ <table>
1909
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1910
+ <tbody>
1911
+ <tr><td><code>size</code></td><td><code>"sm" | "default" | "lg"</code></td><td><code>"default"</code></td><td>Sizing.</td></tr>
1912
+ <tr><td><code>label</code></td><td><code>string | null</code></td><td><code>"Loading"</code></td><td>Announced to screen readers.</td></tr>
1913
+ </tbody>
1914
+ </table>
1915
+
1916
+ <a id="components-empty"></a>
1917
+
1918
+ ## Empty
1919
+
1920
+ Empty-state block with an action.
1921
+
1922
+ ### Empty installation
1923
+
1924
+ ```sh
1925
+ bun zt flow:add empty
1926
+ ```
1927
+
1928
+ Or import directly from the package: `import { Empty } from "@zerotal/flow-ui";`
1929
+
1930
+ ### Empty preview
1931
+
1932
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1933
+ <div class="flex flex-col items-center justify-center px-6 py-12 text-center rounded-lg border border-dashed border-border"><p class="text-sm font-medium text-foreground">No orders yet</p><p class="mt-1 max-w-sm text-sm text-muted-foreground">Orders appear here as customers place them.</p><div class="mt-4 flex items-center gap-2"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-8 rounded-md px-3 text-xs">New order</button></div></div>
1934
+ </div>
1935
+
1936
+ ### Empty usage
1937
+
1938
+ ```tsx
1939
+ <Empty
1940
+ icon={icon}
1941
+ title="No orders yet"
1942
+ description="Orders appear here as customers place them."
1943
+ action={<Button>New order</Button>}
1944
+ />
1945
+ ```
1946
+
1947
+ ### Empty props
1948
+
1949
+ <table>
1950
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1951
+ <tbody>
1952
+ <tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>What is missing.</td></tr>
1953
+ <tr><td><code>description</code></td><td><code>node</code></td><td>—</td><td>Why, or what to do about it.</td></tr>
1954
+ <tr><td><code>action</code></td><td><code>node</code></td><td>—</td><td>The next step — usually a button.</td></tr>
1955
+ <tr><td><code>bare</code></td><td><code>boolean</code></td><td>—</td><td>Drop the dashed border, inside a card that draws its own.</td></tr>
1956
+ </tbody>
1957
+ </table>
1958
+
1959
+ <a id="components-kbd"></a>
1960
+
1961
+ ## Kbd
1962
+
1963
+ Keyboard key (+ platform modifier).
1964
+
1965
+ ### Kbd installation
1966
+
1967
+ ```sh
1968
+ bun zt flow:add kbd
1969
+ ```
1970
+
1971
+ Or import directly from the package: `import { Kbd } from "@zerotal/flow-ui";`
1972
+
1973
+ ### Kbd preview
1974
+
1975
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
1976
+ <div class="flex flex-wrap items-center gap-3"><span class="inline-flex items-center gap-1"><kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground" x-data="{ mac: false }" x-init="mac = /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent)" x-text="mac ? '⌘' : 'Ctrl'">Ctrl</kbd><kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">K</kbd></span><span class="inline-flex items-center gap-1"><kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">⇧</kbd><kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">↵</kbd></span></div>
1977
+ </div>
1978
+
1979
+ ### Kbd usage
1980
+
1981
+ ```tsx
1982
+ <KbdMod /> <Kbd>K</Kbd>
1983
+ ```
1984
+
1985
+ ### Kbd props
1986
+
1987
+ <table>
1988
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
1989
+ <tbody>
1990
+ <tr><td><code>children</code></td><td><code>node</code></td><td>—</td><td>The key. `&lt;KbdMod /&gt;` renders ⌘ or Ctrl per platform.</td></tr>
1991
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
1992
+ </tbody>
1993
+ </table>
1994
+
1995
+ <a id="components-accordion"></a>
1996
+
1997
+ ## Accordion
1998
+
1999
+ Stacked collapsible sections.
2000
+
2001
+ ### Accordion installation
2002
+
2003
+ ```sh
2004
+ bun zt flow:add accordion
2005
+ ```
2006
+
2007
+ Or import directly from the package: `import { Accordion } from "@zerotal/flow-ui";`
2008
+
2009
+ ### Accordion preview
2010
+
2011
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
2012
+ <div class="w-full max-w-md"><div x-data="{ active: 0 }" class="flow-accordion divide-y divide-border border-y border-border"><div class="flow-accordion-item" x-id="['flow-accordion']"><button type="button" x-on:click="active = active === 0 ? -1 : 0" :aria-expanded="active === 0" :data-open="active === 0 ? '' : null" :aria-controls="$id('flow-accordion')" class="flow-accordion-button flex w-full items-center justify-between gap-4 py-4 text-left text-sm font-medium transition-colors hover:underline outline-none focus-visible:ring-2 focus-visible:ring-ring [&amp;[data-open]&gt;svg]:rotate-180">Shipping</button><div :id="$id('flow-accordion')" x-show="active === 0" x-cloak :data-open="active === 0 ? '' : null" class="flow-accordion-panel pb-4 text-sm text-muted-foreground"><p>Ships in 2–3 days.</p></div></div><div class="flow-accordion-item" x-id="['flow-accordion']"><button type="button" x-on:click="active = active === 1 ? -1 : 1" :aria-expanded="active === 1" :data-open="active === 1 ? '' : null" :aria-controls="$id('flow-accordion')" class="flow-accordion-button flex w-full items-center justify-between gap-4 py-4 text-left text-sm font-medium transition-colors hover:underline outline-none focus-visible:ring-2 focus-visible:ring-ring [&amp;[data-open]&gt;svg]:rotate-180">Returns</button><div :id="$id('flow-accordion')" x-show="active === 1" x-cloak :data-open="active === 1 ? '' : null" class="flow-accordion-panel pb-4 text-sm text-muted-foreground"><p>30 days, no questions.</p></div></div></div></div>
2013
+ </div>
2014
+
2015
+ ### Accordion usage
2016
+
2017
+ ```tsx
2018
+ <Accordion
2019
+ items={[
2020
+ { label: "Shipping", content: <p>Ships in 2–3 days.</p> },
2021
+ { label: "Returns", content: <p>30 days, no questions.</p> },
2022
+ ]}
2023
+ />
2024
+ ```
2025
+
2026
+ ### Accordion props
2027
+
2028
+ <table>
2029
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
2030
+ <tbody>
2031
+ <tr><td><code>items</code></td><td><code>AccordionItem[]</code></td><td>—</td><td>Each with a label and its content.</td></tr>
2032
+ <tr><td><code>multiple</code></td><td><code>boolean</code></td><td>—</td><td>Allow several open at once.</td></tr>
2033
+ <tr><td><code>defaultIndex</code></td><td><code>number</code></td><td><code>-1</code></td><td>Which starts open.</td></tr>
2034
+ </tbody>
2035
+ </table>
2036
+
2037
+ <a id="components-collapsible"></a>
2038
+
2039
+ ## Collapsible
2040
+
2041
+ One section that opens and closes.
2042
+
2043
+ ### Collapsible installation
2044
+
2045
+ ```sh
2046
+ bun zt flow:add collapsible
2047
+ ```
2048
+
2049
+ Or import directly from the package: `import { Collapsible } from "@zerotal/flow-ui";`
2050
+
2051
+ ### Collapsible preview
2052
+
2053
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
2054
+ <div class="w-full max-w-md"><div x-data="{ open: false }" x-id="['flow-disclosure']" class="flow-disclosure"><button type="button" x-on:click="open = !open" :aria-expanded="open" :data-open="open ? '' : null" :aria-controls="$id('flow-disclosure')" class="flow-disclosure-button inline-flex items-center gap-1.5 rounded-md text-sm font-medium text-foreground transition-colors hover:text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring [&amp;[data-open]&gt;svg]:rotate-180">Advanced</button><div :id="$id('flow-disclosure')" x-show="open" x-cloak :data-open="open ? '' : null" class="flow-disclosure-panel pt-2"><p class="text-sm text-muted-foreground">Rarely-changed settings live here.</p></div></div></div>
2055
+ </div>
2056
+
2057
+ ### Collapsible usage
2058
+
2059
+ ```tsx
2060
+ <Collapsible label="Advanced">
2061
+ <Field label="Timeout">
2062
+ <Input />
2063
+ </Field>
2064
+ </Collapsible>
2065
+ ```
2066
+
2067
+ ### Collapsible props
2068
+
2069
+ <table>
2070
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
2071
+ <tbody>
2072
+ <tr><td><code>label</code></td><td><code>node</code></td><td>—</td><td>Text for the default trigger.</td></tr>
2073
+ <tr><td><code>trigger</code></td><td><code>node</code></td><td>—</td><td>A custom trigger instead.</td></tr>
2074
+ <tr><td><code>defaultOpen</code></td><td><code>boolean</code></td><td>—</td><td>Start open.</td></tr>
2075
+ </tbody>
2076
+ </table>
2077
+
2078
+ <a id="components-scroll-area"></a>
2079
+
2080
+ ## ScrollArea
2081
+
2082
+ Scrollable region with a styled bar.
2083
+
2084
+ ### ScrollArea installation
2085
+
2086
+ ```sh
2087
+ bun zt flow:add scroll-area
2088
+ ```
2089
+
2090
+ Or import directly from the package: `import { ScrollArea } from "@zerotal/flow-ui";`
2091
+
2092
+ ### ScrollArea preview
2093
+
2094
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
2095
+ <div class="relative overflow-y-auto overflow-x-hidden [scrollbar-width:thin] [scrollbar-color:var(--border)_transparent] [&amp;::-webkit-scrollbar]:h-2 [&amp;::-webkit-scrollbar]:w-2 [&amp;::-webkit-scrollbar-track]:bg-transparent [&amp;::-webkit-scrollbar-thumb]:rounded-full [&amp;::-webkit-scrollbar-thumb]:bg-border [&amp;::-webkit-scrollbar-thumb:hover]:bg-muted-foreground/40 h-32 w-full max-w-sm rounded-md border border-border p-3"><div class="space-y-2 text-sm"><p>Row 1</p><p>Row 2</p><p>Row 3</p><p>Row 4</p><p>Row 5</p><p>Row 6</p><p>Row 7</p><p>Row 8</p><p>Row 9</p><p>Row 10</p><p>Row 11</p><p>Row 12</p></div></div>
2096
+ </div>
2097
+
2098
+ ### ScrollArea usage
2099
+
2100
+ ```tsx
2101
+ <ScrollArea class="h-72">…long list…</ScrollArea>
2102
+ ```
2103
+
2104
+ ### ScrollArea props
2105
+
2106
+ <table>
2107
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
2108
+ <tbody>
2109
+ <tr><td><code>orientation</code></td><td><code>"vertical" | "horizontal" | "both"</code></td><td><code>"vertical"</code></td><td>Which way it scrolls.</td></tr>
2110
+ <tr><td><code>fade</code></td><td><code>boolean</code></td><td>—</td><td>Fade the content at the scrollable edges.</td></tr>
2111
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
2112
+ </tbody>
2113
+ </table>
2114
+
2115
+ <a id="components-resizable"></a>
2116
+
2117
+ ## Resizable
2118
+
2119
+ Two panes with a draggable handle.
2120
+
2121
+ ### Resizable installation
2122
+
2123
+ ```sh
2124
+ bun zt flow:add resizable
2125
+ ```
2126
+
2127
+ Or import directly from the package: `import { Resizable } from "@zerotal/flow-ui";`
2128
+
2129
+ ### Resizable preview
2130
+
2131
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
2132
+ <div x-data="{ size: 35, dragging: false, start(e) { this.dragging = true; e.target.setPointerCapture(e.pointerId); }, move(e) { if (!this.dragging) return; const box = $el.getBoundingClientRect(); const raw = ((e.clientX - box.left) / box.width) * 100; this.size = Math.min(90, Math.max(10, raw)); }, stop(e) { this.dragging = false; if (e.pointerId != null) e.target.releasePointerCapture?.(e.pointerId); }, nudge(by) { this.size = Math.min(90, Math.max(10, this.size + by)); } }" x-on:pointermove="move($event)" x-on:pointerup="stop($event)" class="flex w-full flex-row h-32 rounded-md border border-border"><div x-bind:style="`flex-basis:${size}%`" class="min-w-0 overflow-hidden"><div class="h-full bg-muted/40 p-3 text-sm">Sidebar</div></div><div role="separator" tabindex="0" aria-orientation="vertical" x-bind:aria-valuenow="Math.round(size)" aria-valuemin="10" aria-valuemax="90" aria-label="Resize panes" x-on:pointerdown="start($event)" x-on:keydown="if ($event.key === 'ArrowLeft') { $event.preventDefault(); nudge(-2) } if ($event.key === 'ArrowRight') { $event.preventDefault(); nudge(2) }" x-bind:class="dragging &amp;&amp; 'bg-primary'" class="group relative shrink-0 bg-border transition-colors hover:bg-primary/50 outline-none focus-visible:bg-primary w-px cursor-col-resize"><span class="absolute -inset-x-1.5 inset-y-0"></span></div><div class="min-w-0 flex-1 overflow-hidden"><div class="h-full p-3 text-sm">Content</div></div></div>
2133
+ </div>
2134
+
2135
+ ### Resizable usage
2136
+
2137
+ ```tsx
2138
+ <Resizable start={<Tree />} end={<Editor />} defaultSize={30} />
2139
+ ```
2140
+
2141
+ ### Resizable props
2142
+
2143
+ <table>
2144
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
2145
+ <tbody>
2146
+ <tr><td><code>start / end</code></td><td><code>node</code></td><td>—</td><td>The two panes.</td></tr>
2147
+ <tr><td><code>defaultSize</code></td><td><code>number</code></td><td><code>50</code></td><td>First pane's starting size, as a percentage.</td></tr>
2148
+ <tr><td><code>min</code></td><td><code>number</code></td><td><code>10</code></td><td>Smallest either pane may become.</td></tr>
2149
+ <tr><td><code>orientation</code></td><td><code>"horizontal" | "vertical"</code></td><td><code>"horizontal"</code></td><td>Split direction.</td></tr>
2150
+ </tbody>
2151
+ </table>
2152
+
2153
+ <a id="components-carousel"></a>
2154
+
2155
+ ## Carousel
2156
+
2157
+ Snap-scrolling strip with controls.
2158
+
2159
+ ### Carousel installation
2160
+
2161
+ ```sh
2162
+ bun zt flow:add carousel
2163
+ ```
2164
+
2165
+ Or import directly from the package: `import { Carousel } from "@zerotal/flow-ui";`
2166
+
2167
+ ### Carousel preview
2168
+
2169
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
2170
+ <div x-data="{ atStart: true, atEnd: false, sync() { const el = $refs.track; this.atStart = el.scrollLeft &lt;= 1; this.atEnd = el.scrollLeft + el.clientWidth &gt;= el.scrollWidth - 1; }, page(dir) { $refs.track.scrollBy({ left: dir * $refs.track.clientWidth * 0.9, behavior: 'smooth' }); } }" x-init="sync()" role="region" aria-roledescription="carousel" aria-label="Carousel" class="relative"><div x-ref="track" x-on:scroll.debounce.50ms="sync()" class="flex snap-x snap-mandatory gap-4 overflow-x-auto scroll-smooth pb-2 [scrollbar-width:none] [&amp;::-webkit-scrollbar]:hidden"><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 1</div></div><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 2</div></div><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 3</div></div><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 4</div></div><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 5</div></div><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 6</div></div></div><button type="button" aria-label="Previous" x-on:click="page(-1)" x-bind:disabled="atStart" class="absolute top-1/2 z-10 inline-flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full border border-border bg-background shadow-sm transition-opacity hover:bg-accent disabled:pointer-events-none disabled:opacity-0 left-0 -translate-x-1/2"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"></path></svg></button><button type="button" aria-label="Next" x-on:click="page(1)" x-bind:disabled="atEnd" class="absolute top-1/2 z-10 inline-flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full border border-border bg-background shadow-sm transition-opacity hover:bg-accent disabled:pointer-events-none disabled:opacity-0 right-0 translate-x-1/2"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg></button></div>
2171
+ </div>
2172
+
2173
+ ### Carousel usage
2174
+
2175
+ ```tsx
2176
+ <Carousel
2177
+ items={products.map((p) => (
2178
+ <ProductCard product={p} />
2179
+ ))}
2180
+ />
2181
+ ```
2182
+
2183
+ ### Carousel props
2184
+
2185
+ <table>
2186
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
2187
+ <tbody>
2188
+ <tr><td><code>items</code></td><td><code>node[]</code></td><td>—</td><td>The slides.</td></tr>
2189
+ <tr><td><code>itemClass</code></td><td><code>string</code></td><td><code>"w-64 sm:w-72"</code></td><td>Width of each slide.</td></tr>
2190
+ <tr><td><code>hideControls</code></td><td><code>boolean</code></td><td>—</td><td>Swipe and scroll only.</td></tr>
2191
+ </tbody>
2192
+ </table>
2193
+
2194
+ <a id="components-aspect-ratio"></a>
2195
+
2196
+ ## AspectRatio
2197
+
2198
+ Fixed width-to-height box.
2199
+
2200
+ ### AspectRatio installation
2201
+
2202
+ ```sh
2203
+ bun zt flow:add aspect-ratio
2204
+ ```
2205
+
2206
+ Or import directly from the package: `import { AspectRatio } from "@zerotal/flow-ui";`
2207
+
2208
+ ### AspectRatio preview
2209
+
2210
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
2211
+ <div class="w-64"><div style="aspect-ratio:1.7777777777777777" class="relative w-full overflow-hidden rounded-lg border border-border bg-muted/40"><div class="flex h-full items-center justify-center text-sm text-muted-foreground">16 / 9</div></div></div>
2212
+ </div>
2213
+
2214
+ ### AspectRatio usage
2215
+
2216
+ ```tsx
2217
+ <AspectRatio ratio={16 / 9}>
2218
+ <img src={cover} class="h-full w-full object-cover" />
2219
+ </AspectRatio>
2220
+ ```
2221
+
2222
+ ### AspectRatio props
2223
+
2224
+ <table>
2225
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
2226
+ <tbody>
2227
+ <tr><td><code>ratio</code></td><td><code>number</code></td><td><code>1</code></td><td>Width ÷ height.</td></tr>
2228
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
2229
+ </tbody>
2230
+ </table>
2231
+
2232
+ <a id="components-item"></a>
2233
+
2234
+ ## Item
2235
+
2236
+ Icon + title + description + action row.
2237
+
2238
+ ### Item installation
2239
+
2240
+ ```sh
2241
+ bun zt flow:add item
2242
+ ```
2243
+
2244
+ Or import directly from the package: `import { Item } from "@zerotal/flow-ui";`
2245
+
2246
+ ### Item preview
2247
+
2248
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
2249
+ <div class="w-full max-w-md rounded-lg border border-border p-1"><div class="flex w-full items-center gap-3 rounded-md px-3 py-2 text-left"><span class="min-w-0 flex-1"><span class="block truncate text-sm font-medium">Team</span><span class="block truncate text-xs text-muted-foreground">4 members</span></span><span class="flex shrink-0 items-center gap-2"><span class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">Owner</span></span></div><div class="flex w-full items-center gap-3 rounded-md px-3 py-2 text-left transition-colors hover:bg-accent hover:text-accent-foreground"><span class="min-w-0 flex-1"><span class="block truncate text-sm font-medium">Billing</span><span class="block truncate text-xs text-muted-foreground">Visa ending 4242</span></span></div></div>
2250
+ </div>
2251
+
2252
+ ### Item usage
2253
+
2254
+ ```tsx
2255
+ <Item title="Team" description="4 members" action={<Button size="sm">Manage</Button>} />
2256
+ ```
2257
+
2258
+ ### Item props
2259
+
2260
+ <table>
2261
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
2262
+ <tbody>
2263
+ <tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>The primary line.</td></tr>
2264
+ <tr><td><code>description</code></td><td><code>node</code></td><td>—</td><td>The supporting line.</td></tr>
2265
+ <tr><td><code>action</code></td><td><code>node</code></td><td>—</td><td>Trailing content.</td></tr>
2266
+ <tr><td><code>href</code></td><td><code>string</code></td><td>—</td><td>Makes the whole row a link.</td></tr>
2267
+ </tbody>
2268
+ </table>
2269
+
2270
+ <a id="components-chart"></a>
2271
+
2272
+ ## Chart
2273
+
2274
+ SVG line, area, bar and donut charts.
2275
+
2276
+ ### Chart installation
2277
+
2278
+ ```sh
2279
+ bun zt flow:add chart
2280
+ ```
2281
+
2282
+ Or import directly from the package: `import { Chart } from "@zerotal/flow-ui";`
2283
+
2284
+ ### Chart preview
2285
+
2286
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
2287
+ <div class="w-full space-y-6"><figure class="relative w-full" x-data="flowChart({&quot;points&quot;:[{&quot;x&quot;:0.07333333333333333,&quot;label&quot;:&quot;Mon&quot;,&quot;values&quot;:[{&quot;label&quot;:&quot;Orders&quot;,&quot;value&quot;:&quot;12&quot;,&quot;color&quot;:&quot;var(--primary)&quot;}]},{&quot;x&quot;:0.2277777777777778,&quot;label&quot;:&quot;Tue&quot;,&quot;values&quot;:[{&quot;label&quot;:&quot;Orders&quot;,&quot;value&quot;:&quot;19&quot;,&quot;color&quot;:&quot;var(--primary)&quot;}]},{&quot;x&quot;:0.38222222222222224,&quot;label&quot;:&quot;Wed&quot;,&quot;values&quot;:[{&quot;label&quot;:&quot;Orders&quot;,&quot;value&quot;:&quot;14&quot;,&quot;color&quot;:&quot;var(--primary)&quot;}]},{&quot;x&quot;:0.5366666666666666,&quot;label&quot;:&quot;Thu&quot;,&quot;values&quot;:[{&quot;label&quot;:&quot;Orders&quot;,&quot;value&quot;:&quot;27&quot;,&quot;color&quot;:&quot;var(--primary)&quot;}]},{&quot;x&quot;:0.6911111111111111,&quot;label&quot;:&quot;Fri&quot;,&quot;values&quot;:[{&quot;label&quot;:&quot;Orders&quot;,&quot;value&quot;:&quot;22&quot;,&quot;color&quot;:&quot;var(--primary)&quot;}]},{&quot;x&quot;:0.8455555555555556,&quot;label&quot;:&quot;Sat&quot;,&quot;values&quot;:[{&quot;label&quot;:&quot;Orders&quot;,&quot;value&quot;:&quot;31&quot;,&quot;color&quot;:&quot;var(--primary)&quot;}]},{&quot;x&quot;:1,&quot;label&quot;:&quot;Sun&quot;,&quot;values&quot;:[{&quot;label&quot;:&quot;Orders&quot;,&quot;value&quot;:&quot;25&quot;,&quot;color&quot;:&quot;var(--primary)&quot;}]}]})" x-on:pointermove="onMove($event)" x-on:pointerleave="onLeave()"><svg viewBox="0 0 600 160" width="100%" height="160" preserveAspectRatio="none" role="img" aria-label="Orders: 12, 19, 14, 27, 22, 31, 25" class="overflow-visible"><line x1="44" y1="138" x2="600" y2="138" stroke="var(--border)" stroke-width="1"></line><text x="36" y="142" text-anchor="end" font-size="11" fill="var(--muted-foreground)">0</text><line x1="44" y1="103.5" x2="600" y2="103.5" stroke="var(--border)" stroke-width="1"></line><text x="36" y="107.5" text-anchor="end" font-size="11" fill="var(--muted-foreground)">13</text><line x1="44" y1="69" x2="600" y2="69" stroke="var(--border)" stroke-width="1"></line><text x="36" y="73" text-anchor="end" font-size="11" fill="var(--muted-foreground)">25</text><line x1="44" y1="34.5" x2="600" y2="34.5" stroke="var(--border)" stroke-width="1"></line><text x="36" y="38.5" text-anchor="end" font-size="11" fill="var(--muted-foreground)">38</text><line x1="44" y1="0" x2="600" y2="0" stroke="var(--border)" stroke-width="1"></line><text x="36" y="4" text-anchor="end" font-size="11" fill="var(--muted-foreground)">50</text><g transform="translate(44,0)"><path d="M0.00,104.88 L92.67,85.56 L185.33,99.36 L278.00,63.48 L370.67,77.28 L463.33,52.44 L556.00,69.00 L556,138 L0,138 Z" fill="var(--primary)" opacity="0.12"></path><path d="M0.00,104.88 L92.67,85.56 L185.33,99.36 L278.00,63.48 L370.67,77.28 L463.33,52.44 L556.00,69.00" fill="none" stroke="var(--primary)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" vector-effect="non-scaling-stroke" x-bind:style="`stroke-dasharray:2000;stroke-dashoffset:${ready ? 0 : 2000};transition:stroke-dashoffset .7s ease-out`"></path></g><text x="44" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Mon</text><text x="136.66666666666669" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Tue</text><text x="229.33333333333334" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Wed</text><text x="322" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Thu</text><text x="414.6666666666667" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Fri</text><text x="507.33333333333337" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Sat</text><text x="600" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Sun</text></svg><div x-show="hover &gt;= 0" x-cloak class="pointer-events-none absolute inset-y-0 w-px bg-border" x-bind:style="`left:${tipX}px`"></div><div x-show="hover &gt;= 0" x-cloak x-transition.opacity class="pointer-events-none absolute top-2 z-10 min-w-32 -translate-x-1/2 rounded-md border border-border bg-popover px-2.5 py-2 text-xs shadow-md" x-bind:style="`left:${tipX}px`"><p class="mb-1 font-medium" x-text="tip()?.label"></p><template x-for="row in (tip()?.values ?? [])" :key="row.label"><span class="flex items-center gap-1.5"><span class="h-2 w-2 shrink-0 rounded-sm" x-bind:style="`background:${row.color}`"></span><span class="text-muted-foreground" x-text="row.label"></span><span class="ml-auto font-medium tabular-nums" x-text="row.value"></span></span></template></div></figure><figure class="flex items-center gap-6"><svg viewBox="0 0 140 140" width="140" height="140" role="img" aria-label="Series: 82, 24, 6"><path d="M70,2 A68,68 0 1 1 2.4275697272595096,77.61358437502494 L28.1050932309009,74.72042231251547 A42.16,42.16 0 1 0 70,27.840000000000003 Z" fill="var(--primary)"></path><path d="M2.4275697272595096,77.61358437502494 A68,68 0 0 1 47.54102378704863,5.815933539031008 L56.07543474797015,30.20587879419923 A42.16,42.16 0 0 0 28.1050932309009,74.72042231251547 Z" fill="var(--flow-toast-success, #16a34a)"></path><path d="M47.54102378704863,5.815933539031008 A68,68 0 0 1 69.99999999999999,2 L69.99999999999999,27.840000000000003 A42.16,42.16 0 0 0 56.07543474797015,30.20587879419923 Z" fill="var(--flow-toast-warning, #d97706)"></path></svg><figcaption class="space-y-1.5 text-sm"><span class="flex items-center gap-2"><span class="h-2.5 w-2.5 shrink-0 rounded-sm" style="background:var(--primary)"></span><span class="text-muted-foreground">Paid</span><span class="ml-auto font-medium tabular-nums">82</span></span><span class="flex items-center gap-2"><span class="h-2.5 w-2.5 shrink-0 rounded-sm" style="background:var(--flow-toast-success, #16a34a)"></span><span class="text-muted-foreground">Pending</span><span class="ml-auto font-medium tabular-nums">24</span></span><span class="flex items-center gap-2"><span class="h-2.5 w-2.5 shrink-0 rounded-sm" style="background:var(--flow-toast-warning, #d97706)"></span><span class="text-muted-foreground">Refunded</span><span class="ml-auto font-medium tabular-nums">6</span></span></figcaption></figure></div>
2288
+ </div>
2289
+
2290
+ ### Chart usage
2291
+
2292
+ ```tsx
2293
+ <Chart type="line" labels={days} datasets={[{ label: "Orders", data: counts }]} />
2294
+ <Chart type="donut" labels={["Paid","Pending"]} datasets={[{ data: [82, 18] }]} />
2295
+ ```
2296
+
2297
+ ### Chart props
2298
+
2299
+ <table>
2300
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
2301
+ <tbody>
2302
+ <tr><td><code>type</code></td><td><code>"line" | "area" | "bar" | "donut"</code></td><td><code>"line"</code></td><td>Chart kind.</td></tr>
2303
+ <tr><td><code>labels</code></td><td><code>string[]</code></td><td>—</td><td>Axis or legend labels.</td></tr>
2304
+ <tr><td><code>datasets</code></td><td><code>ChartDataset[]</code></td><td>—</td><td>One or more series.</td></tr>
2305
+ <tr><td><code>height</code></td><td><code>number</code></td><td><code>220</code></td><td>Drawing height; width is fluid.</td></tr>
2306
+ <tr><td><code>format</code></td><td><code>(n: number) =&gt; string</code></td><td>—</td><td>Formats axis values and the accessible summary.</td></tr>
2307
+ </tbody>
2308
+ </table>
2309
+
2310
+ <a id="components-typography"></a>
2311
+
2312
+ ## Prose
2313
+
2314
+ Prose wrapper + heading helpers.
2315
+
2316
+ ### Prose installation
2317
+
2318
+ ```sh
2319
+ bun zt flow:add typography
2320
+ ```
2321
+
2322
+ Or import directly from the package: `import { Prose } from "@zerotal/flow-ui";`
2323
+
2324
+ ### Prose preview
2325
+
2326
+ <div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
2327
+ <div class="text-foreground [&amp;_h1]:mt-8 [&amp;_h1]:scroll-m-20 [&amp;_h1]:text-3xl [&amp;_h1]:font-semibold [&amp;_h1]:tracking-tight [&amp;_h2]:mt-8 [&amp;_h2]:scroll-m-20 [&amp;_h2]:border-b [&amp;_h2]:border-border [&amp;_h2]:pb-2 [&amp;_h2]:text-2xl [&amp;_h2]:font-semibold [&amp;_h3]:mt-6 [&amp;_h3]:scroll-m-20 [&amp;_h3]:text-xl [&amp;_h3]:font-semibold [&amp;_p]:leading-7 [&amp;_p:not(:first-child)]:mt-4 [&amp;_a]:font-medium [&amp;_a]:text-primary [&amp;_a]:underline [&amp;_a]:underline-offset-4 [&amp;_ul]:my-4 [&amp;_ul]:ml-6 [&amp;_ul]:list-disc [&amp;_ol]:my-4 [&amp;_ol]:ml-6 [&amp;_ol]:list-decimal [&amp;_li]:mt-2 [&amp;_blockquote]:mt-4 [&amp;_blockquote]:border-l-2 [&amp;_blockquote]:border-border [&amp;_blockquote]:pl-4 [&amp;_blockquote]:italic [&amp;_code]:rounded [&amp;_code]:bg-muted [&amp;_code]:px-[0.3rem] [&amp;_code]:py-[0.2rem] [&amp;_code]:font-mono [&amp;_code]:text-sm [&amp;_pre]:mt-4 [&amp;_pre]:overflow-x-auto [&amp;_pre]:rounded-lg [&amp;_pre]:border [&amp;_pre]:border-border [&amp;_pre]:bg-muted [&amp;_pre]:p-4 [&amp;_pre_code]:bg-transparent [&amp;_pre_code]:p-0 [&amp;_hr]:my-8 [&amp;_hr]:border-border [&amp;_table]:w-full [&amp;_table]:text-sm [&amp;_th]:border-b [&amp;_th]:border-border [&amp;_th]:px-3 [&amp;_th]:py-2 [&amp;_th]:text-left [&amp;_td]:border-b [&amp;_td]:border-border [&amp;_td]:px-3 [&amp;_td]:py-2 [&amp;_img]:rounded-lg max-w-md"><h2>A rendered document</h2><p>Prose styles its descendants, for content that arrives as a blob — Markdown, a CMS field, a rich-text column.</p><ul><li>Headings, lists and quotes</li><li>Inline <code>code</code> and code blocks</li></ul></div>
2328
+ </div>
2329
+
2330
+ ### Prose usage
2331
+
2332
+ ```tsx
2333
+ <Prose dangerouslySetInnerHTML={{ __html: rendered }} />
2334
+ <H1>Page title</H1>
2335
+ <Muted>Last updated yesterday</Muted>
2336
+ ```
2337
+
2338
+ ### Prose props
2339
+
2340
+ <table>
2341
+ <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
2342
+ <tbody>
2343
+ <tr><td><code>children</code></td><td><code>node</code></td><td>—</td><td>Markup you did not author — Prose styles its descendants.</td></tr>
2344
+ <tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
2345
+ </tbody>
2346
+ </table>
2347
+
2348
+ <!-- END GENERATED COMPONENTS -->
2349
+
2350
+ ## Testing
2351
+
2352
+ Set your suite up once as described in [Testing](/docs/testing). A `flow-ui`
2353
+ component is a plain function returning a node, so it tests like any other
2354
+ [view](/docs/view#testing) — render it and assert on the string.
2355
+
2356
+ ```typescript
2357
+ // tests/components/Button.test.ts
2358
+ import { test, expect } from "bun:test";
2359
+ import { Button } from "@zerotal/flow-ui";
2360
+
2361
+ test("renders the variant's classes", () => {
2362
+ const html = String(Button({ variant: "destructive", children: "Delete" }));
2363
+
2364
+ expect(html).toContain("Delete");
2365
+ expect(html).toContain("destructive");
2366
+ });
2367
+ ```
2368
+
2369
+ **Test your own components, not the library's.** The twenty components ship with
2370
+ their own suite; a test asserting that `Button` renders a `<button>` re-tests
2371
+ someone else's work and breaks when they restyle. What earns a test is the
2372
+ component _you_ composed from them, and the props you pass it.
2373
+
2374
+ **Copy-in components are yours the moment you run `flow:add`.** Once the source
2375
+ lives in your repo, it is application code — it changes when you edit it, and
2376
+ nothing upstream will catch a regression you introduce:
2377
+
2378
+ ```typescript
2379
+ // tests/components/StatusBadge.test.ts
2380
+ import { test, expect } from "bun:test";
2381
+ import { StatusBadge } from "../../resources/components/StatusBadge.tsx";
2382
+
2383
+ test("an overdue invoice is flagged", () => {
2384
+ const html = String(StatusBadge({ status: "overdue" }));
2385
+
2386
+ expect(html).toContain("Overdue");
2387
+ expect(html).toContain("bg-red"); // whatever your theme maps it to
2388
+ });
2389
+ ```
2390
+
2391
+ **Assert behaviour, not class strings, wherever you can.** A test pinned to
2392
+ `bg-red-500` fails on a palette change that broke nothing. Prefer the visible
2393
+ text, an `aria-` attribute, or a `data-` hook you control.
2394
+
2395
+ > **Note** — Interactive behaviour — a dropdown opening, a dialog trapping focus
2396
+ > — needs a real browser. See [Browser Tests](/docs/testing/browser); rendering
2397
+ > assertions stop at the markup the server produced.
2398
+
2399
+ ## References
2400
+
2401
+ The full export surface of `@zerotal/flow-ui`:
2402
+
2403
+ | Export | Kind | Description |
2404
+ | --------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------- |
2405
+ | `FlowUiProvider` | provider | Registers the `flow:list` / `flow:add` / `flow:init` commands. |
2406
+ | `COMPONENTS`, `UTILS`, `findComponent` | registry | The manifest behind `flow:add`. |
2407
+ | `cn(...classes)` | util | Merge class strings (clsx + tailwind-merge). |
2408
+ | `gva(base, config)` | util | Build token-backed variant class functions. |
2409
+ | `Button`, `Badge`, `Card` (+ parts), `Input`, `Textarea`, `Label` | components | Styled leaf and composite components. |
2410
+ | `Separator`, `Skeleton`, `Avatar` | components | Styled leaf components. |
2411
+ | `Switch`, `Checkbox`, `Select`, `RadioGroup` | components | Themed wrappers over Flow's headless primitives. |
2412
+ | `Dialog`, `Sheet`, `DropdownMenu` (+ parts), `Tabs`, `Alert`, `Tooltip`, `Table` | components | Themed interactive components. |
2413
+ | `Disclosure`, `Accordion`, `Popover`, `Listbox`, `Combobox`, `Field`, `Fieldset`, `Legend`, `Description` | re-exports | Headless [Flow](/docs/flow) primitives, re-exported so flow-ui stays a single import. |
2414
+
2415
+ CLI commands (registered by `FlowUiProvider`):
2416
+
2417
+ | Command | Signature | Description |
2418
+ | ----------- | --------------------------------------------------------- | ----------------------------------------- |
2419
+ | `flow:init` | `flow:init [--dir <path>] [--css <path>]` | Set up the shared utils and theme import. |
2420
+ | `flow:add` | `flow:add <name[,name]> [--all] [--force] [--dir <path>]` | Copy component source into your app. |
2421
+ | `flow:list` | `flow:list` | List every component available to add. |
2422
+
2423
+ ## Next steps
2424
+
2425
+ - [Flow](/docs/flow) — the server-driven component framework these wrap.
2426
+ - [Validator](/docs/validator) — validate the form fields you bind with `Input` and `Select`.
2427
+ - [Assets](/docs/assets) — build the Tailwind CSS that powers the theme tokens.