@dryui/ui 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,143 +1,109 @@
1
1
  # Composition
2
2
 
3
- ## Layout Components
3
+ ## Layout with CSS Grid
4
4
 
5
- DryUI provides five layout primitives. Use them instead of manual CSS.
5
+ All layout uses raw `display: grid` in scoped `<style>` blocks with `--dry-space-*` tokens. No flexbox, no inline styles, no layout components (Stack, Flex, Grid, Spacer).
6
6
 
7
- ### Stack (vertical layout)
8
-
9
- Stacks children vertically with consistent gap.
7
+ ### Vertical stack
10
8
 
11
9
  ```svelte
12
- <!-- Incorrect -->
13
- <div style="display: flex; flex-direction: column; gap: 1rem;">
14
- <p>First</p>
15
- <p>Second</p>
10
+ <div class="stack">
11
+ <p>First</p>
12
+ <p>Second</p>
16
13
  </div>
17
14
 
18
- <!-- Correct -->
19
- <Stack gap="md">
20
- <p>First</p>
21
- <p>Second</p>
22
- </Stack>
15
+ <style>
16
+ .stack { display: grid; gap: var(--dry-space-4); }
17
+ </style>
23
18
  ```
24
19
 
25
- Gap values: `"sm"`, `"md"`, `"lg"`, `"xl"`
26
-
27
- ### Flex (horizontal layout)
28
-
29
- Flexible row layout with alignment controls.
20
+ ### Horizontal row
30
21
 
31
22
  ```svelte
32
- <!-- Incorrect -->
33
- <div style="display: flex; justify-content: space-between; align-items: center;">
34
- <span>Label</span>
35
- <Button>Action</Button>
23
+ <div class="row">
24
+ <span>Label</span>
25
+ <Button>Action</Button>
36
26
  </div>
37
27
 
38
- <!-- Correct -->
39
- <Flex justify="between" align="center">
40
- <span>Label</span>
41
- <Button>Action</Button>
42
- </Flex>
28
+ <style>
29
+ .row {
30
+ display: grid;
31
+ grid-template-columns: 1fr auto;
32
+ align-items: center;
33
+ gap: var(--dry-space-4);
34
+ }
35
+ </style>
43
36
  ```
44
37
 
45
- Props: `gap`, `justify` (start, center, end, between, around, evenly), `align` (start, center, end, stretch, baseline), `wrap` (boolean)
46
-
47
- ### Grid (CSS grid)
48
-
49
- Grid layout configured entirely via `--dry-grid-*` CSS custom properties.
38
+ ### Responsive columns with @container
50
39
 
51
40
  ```svelte
52
- <!-- Incorrect -->
53
- <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;">
54
- <div>A</div>
55
- <div>B</div>
56
- <div>C</div>
41
+ <div class="grid-container">
42
+ <div class="grid">
43
+ <div>A</div>
44
+ <div>B</div>
45
+ <div>C</div>
46
+ </div>
57
47
  </div>
58
48
 
59
- <!-- Correct -->
60
- <Grid --dry-grid-columns="repeat(3, 1fr)" --dry-grid-gap="var(--dry-space-6)">
61
- <div>A</div>
62
- <div>B</div>
63
- <div>C</div>
64
- </Grid>
49
+ <style>
50
+ .grid-container { container-type: inline-size; }
51
+ .grid {
52
+ display: grid;
53
+ grid-template-columns: 1fr;
54
+ gap: var(--dry-space-6);
55
+ }
56
+ @container (min-width: 40rem) {
57
+ .grid { grid-template-columns: repeat(3, 1fr); }
58
+ }
59
+ </style>
65
60
  ```
66
61
 
67
- For 2D layouts with named areas, use Grid.Area:
62
+ ### Centered max-width content
68
63
 
69
- ```svelte
70
- <Grid
71
- --dry-grid-areas="'header header' 'nav content'"
72
- --dry-grid-columns="16rem 1fr"
73
- --dry-grid-rows="auto 1fr"
74
- --dry-grid-gap="var(--dry-space-4)"
75
- >
76
- <Grid.Area --dry-grid-area="header">...</Grid.Area>
77
- <Grid.Area --dry-grid-area="nav">...</Grid.Area>
78
- <Grid.Area --dry-grid-area="content">...</Grid.Area>
79
- </Grid>
80
- ```
81
-
82
- ### Container (centered max-width)
83
-
84
- Centers content with a max-width constraint.
64
+ Use `Container` (simple component, no `.Root`) for constrained content width:
85
65
 
86
66
  ```svelte
87
- <!-- Incorrect -->
88
- <div style="max-width: 1200px; margin: 0 auto; padding: 0 1rem;">
89
- <h1>Page Title</h1>
90
- </div>
91
-
92
- <!-- Correct -->
93
67
  <Container>
94
- <h1>Page Title</h1>
68
+ <h1>Page Title</h1>
95
69
  </Container>
96
70
  ```
97
71
 
98
- ### Spacer (flexible space)
99
-
100
- Fills available space between flex/stack items.
101
-
102
- ```svelte
103
- <Flex align="center">
104
- <h2>Title</h2>
105
- <Spacer />
106
- <Button>Action</Button>
107
- </Flex>
108
- ```
109
-
110
72
  ## Form Composition
111
73
 
112
74
  ### Basic form
113
75
 
114
- Wrap each input in Field.Root, stack them vertically, and put everything in a Card.
76
+ Wrap each input in Field.Root, stack them with grid, put everything in a Card.
115
77
 
116
78
  ```svelte
117
79
  <script>
118
- import '@dryui/ui/themes/default.css';
119
- import { Card, Stack, Field, Label, Input, Button } from '@dryui/ui';
80
+ import '@dryui/ui/themes/default.css';
81
+ import { Card, Field, Label, Input, Button } from '@dryui/ui';
120
82
 
121
- let name = $state('');
122
- let email = $state('');
83
+ let name = $state('');
84
+ let email = $state('');
123
85
  </script>
124
86
 
125
87
  <Card.Root>
126
- <Card.Header>Contact Info</Card.Header>
127
- <Card.Content>
128
- <Stack gap="md">
129
- <Field.Root>
130
- <Label>Name</Label>
131
- <Input bind:value={name} />
132
- </Field.Root>
133
- <Field.Root>
134
- <Label>Email</Label>
135
- <Input type="email" bind:value={email} />
136
- </Field.Root>
137
- <Button type="submit" variant="solid">Save</Button>
138
- </Stack>
139
- </Card.Content>
88
+ <Card.Header>Contact Info</Card.Header>
89
+ <Card.Content>
90
+ <form class="form-stack">
91
+ <Field.Root>
92
+ <Label>Name</Label>
93
+ <Input bind:value={name} />
94
+ </Field.Root>
95
+ <Field.Root>
96
+ <Label>Email</Label>
97
+ <Input type="email" bind:value={email} />
98
+ </Field.Root>
99
+ <Button type="submit" variant="solid">Save contact</Button>
100
+ </form>
101
+ </Card.Content>
140
102
  </Card.Root>
103
+
104
+ <style>
105
+ .form-stack { display: grid; gap: var(--dry-space-4); }
106
+ </style>
141
107
  ```
142
108
 
143
109
  ### Form with validation
@@ -146,16 +112,16 @@ Use Field.Error to show validation messages.
146
112
 
147
113
  ```svelte
148
114
  <script>
149
- let email = $state('');
150
- let error = $derived(email && !email.includes('@') ? 'Please enter a valid email' : '');
115
+ let email = $state('');
116
+ let error = $derived(email && !email.includes('@') ? 'Please enter a valid email' : '');
151
117
  </script>
152
118
 
153
119
  <Field.Root>
154
- <Label>Email</Label>
155
- <Input type="email" bind:value={email} />
156
- {#if error}
157
- <Field.Error>{error}</Field.Error>
158
- {/if}
120
+ <Label>Email</Label>
121
+ <Input type="email" bind:value={email} />
122
+ {#if error}
123
+ <Field.Error>{error}</Field.Error>
124
+ {/if}
159
125
  </Field.Root>
160
126
  ```
161
127
 
@@ -163,45 +129,55 @@ Use Field.Error to show validation messages.
163
129
 
164
130
  ```svelte
165
131
  <script>
166
- let name = $state('');
167
- let bio = $state('');
168
- let country = $state('');
169
- let agreed = $state(false);
132
+ let name = $state('');
133
+ let bio = $state('');
134
+ let country = $state('');
135
+ let agreed = $state(false);
170
136
  </script>
171
137
 
172
- <Stack gap="md">
173
- <Field.Root>
174
- <Label>Name</Label>
175
- <Input bind:value={name} />
176
- </Field.Root>
177
-
178
- <Field.Root>
179
- <Label>Bio</Label>
180
- <Textarea bind:value={bio} />
181
- </Field.Root>
182
-
183
- <Field.Root>
184
- <Label>Country</Label>
185
- <Select.Root bind:value={country}>
186
- <Select.Trigger>
187
- <Select.Value placeholder="Select country..." />
188
- </Select.Trigger>
189
- <Select.Content>
190
- <Select.Item value="us">United States</Select.Item>
191
- <Select.Item value="uk">United Kingdom</Select.Item>
192
- </Select.Content>
193
- </Select.Root>
194
- </Field.Root>
195
-
196
- <Field.Root>
197
- <Flex align="center" gap="sm">
198
- <Checkbox bind:checked={agreed} />
199
- <Label>I agree to the terms</Label>
200
- </Flex>
201
- </Field.Root>
202
-
203
- <Button type="submit" variant="solid">Submit</Button>
204
- </Stack>
138
+ <form class="form-stack">
139
+ <Field.Root>
140
+ <Label>Name</Label>
141
+ <Input bind:value={name} />
142
+ </Field.Root>
143
+
144
+ <Field.Root>
145
+ <Label>Bio</Label>
146
+ <Textarea bind:value={bio} />
147
+ </Field.Root>
148
+
149
+ <Field.Root>
150
+ <Label>Country</Label>
151
+ <Select.Root bind:value={country}>
152
+ <Select.Trigger>
153
+ <Select.Value placeholder="Select country..." />
154
+ </Select.Trigger>
155
+ <Select.Content>
156
+ <Select.Item value="us">United States</Select.Item>
157
+ <Select.Item value="uk">United Kingdom</Select.Item>
158
+ </Select.Content>
159
+ </Select.Root>
160
+ </Field.Root>
161
+
162
+ <Field.Root>
163
+ <div class="checkbox-row">
164
+ <Checkbox bind:checked={agreed} />
165
+ <Label>I agree to the terms</Label>
166
+ </div>
167
+ </Field.Root>
168
+
169
+ <Button type="submit" variant="solid">Submit form</Button>
170
+ </form>
171
+
172
+ <style>
173
+ .form-stack { display: grid; gap: var(--dry-space-4); }
174
+ .checkbox-row {
175
+ display: grid;
176
+ grid-template-columns: auto 1fr;
177
+ align-items: center;
178
+ gap: var(--dry-space-2);
179
+ }
180
+ </style>
205
181
  ```
206
182
 
207
183
  ## Page Patterns
@@ -209,161 +185,177 @@ Use Field.Error to show validation messages.
209
185
  ### Page with sidebar
210
186
 
211
187
  ```svelte
212
- <Flex gap="lg">
213
- <nav style="width: 240px;">
214
- <Stack gap="sm">
215
- <Button variant="ghost">Dashboard</Button>
216
- <Button variant="ghost">Settings</Button>
217
- <Button variant="ghost">Profile</Button>
218
- </Stack>
219
- </nav>
220
- <main style="flex: 1;">
221
- <Stack gap="lg">
222
- <h1>Dashboard</h1>
223
- <p>Main content here.</p>
224
- </Stack>
225
- </main>
226
- </Flex>
188
+ <div class="page-with-sidebar">
189
+ <nav class="sidebar">
190
+ <Button variant="ghost">Dashboard</Button>
191
+ <Button variant="ghost">Settings</Button>
192
+ <Button variant="ghost">Profile</Button>
193
+ </nav>
194
+ <main class="content">
195
+ <h1>Dashboard</h1>
196
+ <p>Main content here.</p>
197
+ </main>
198
+ </div>
199
+
200
+ <style>
201
+ .page-with-sidebar {
202
+ display: grid;
203
+ grid-template-columns: 15rem 1fr;
204
+ gap: var(--dry-space-6);
205
+ }
206
+ .sidebar { display: grid; gap: var(--dry-space-2); align-content: start; }
207
+ .content { display: grid; gap: var(--dry-space-6); align-content: start; }
208
+ </style>
227
209
  ```
228
210
 
229
211
  ### Card grid
230
212
 
231
213
  ```svelte
232
- <Grid --dry-grid-columns="repeat(3, 1fr)" --dry-grid-gap="var(--dry-space-6)">
233
- {#each items as item (item.id)}
234
- <Card.Root>
235
- <Card.Header>{item.title}</Card.Header>
236
- <Card.Content>
237
- <p>{item.description}</p>
238
- </Card.Content>
239
- <Card.Footer>
240
- <Button variant="outline">View</Button>
241
- </Card.Footer>
242
- </Card.Root>
243
- {/each}
244
- </Grid>
214
+ <div class="card-grid-container">
215
+ <div class="card-grid">
216
+ {#each items as item (item.id)}
217
+ <Card.Root>
218
+ <Card.Header>{item.title}</Card.Header>
219
+ <Card.Content>
220
+ <p>{item.description}</p>
221
+ </Card.Content>
222
+ <Card.Footer>
223
+ <Button variant="outline">View details</Button>
224
+ </Card.Footer>
225
+ </Card.Root>
226
+ {/each}
227
+ </div>
228
+ </div>
229
+
230
+ <style>
231
+ .card-grid-container { container-type: inline-size; }
232
+ .card-grid {
233
+ display: grid;
234
+ grid-template-columns: 1fr;
235
+ gap: var(--dry-space-6);
236
+ }
237
+ @container (min-width: 40rem) {
238
+ .card-grid { grid-template-columns: repeat(3, 1fr); }
239
+ }
240
+ </style>
245
241
  ```
246
242
 
247
243
  ### Settings page with tabs
248
244
 
249
245
  ```svelte
250
246
  <script>
251
- let activeTab = $state('general');
252
- let displayName = $state('');
247
+ let activeTab = $state('general');
248
+ let displayName = $state('');
253
249
  </script>
254
250
 
255
251
  <Container>
256
- <Stack gap="xl">
257
- <h1>Settings</h1>
258
- <Tabs.Root bind:value={activeTab}>
259
- <Tabs.List>
260
- <Tabs.Trigger value="general">General</Tabs.Trigger>
261
- <Tabs.Trigger value="security">Security</Tabs.Trigger>
262
- <Tabs.Trigger value="notifications">Notifications</Tabs.Trigger>
263
- </Tabs.List>
264
- <Tabs.Content value="general">
265
- <Card.Root>
266
- <Card.Content>
267
- <Stack gap="md">
268
- <Field.Root>
269
- <Label>Display Name</Label>
270
- <Input bind:value={displayName} />
271
- </Field.Root>
272
- <Button variant="solid">Save</Button>
273
- </Stack>
274
- </Card.Content>
275
- </Card.Root>
276
- </Tabs.Content>
277
- <Tabs.Content value="security">
278
- <!-- Security settings -->
279
- </Tabs.Content>
280
- </Tabs.Root>
281
- </Stack>
252
+ <div class="settings-stack">
253
+ <h1>Settings</h1>
254
+ <Tabs.Root bind:value={activeTab}>
255
+ <Tabs.List>
256
+ <Tabs.Trigger value="general">General</Tabs.Trigger>
257
+ <Tabs.Trigger value="security">Security</Tabs.Trigger>
258
+ <Tabs.Trigger value="notifications">Notifications</Tabs.Trigger>
259
+ </Tabs.List>
260
+ <Tabs.Content value="general">
261
+ <Card.Root>
262
+ <Card.Content>
263
+ <form class="form-stack">
264
+ <Field.Root>
265
+ <Label>Display Name</Label>
266
+ <Input bind:value={displayName} />
267
+ </Field.Root>
268
+ <Button type="submit" variant="solid">Save settings</Button>
269
+ </form>
270
+ </Card.Content>
271
+ </Card.Root>
272
+ </Tabs.Content>
273
+ <Tabs.Content value="security">
274
+ <!-- Security settings -->
275
+ </Tabs.Content>
276
+ </Tabs.Root>
277
+ </div>
282
278
  </Container>
279
+
280
+ <style>
281
+ .settings-stack { display: grid; gap: var(--dry-space-8); }
282
+ .form-stack { display: grid; gap: var(--dry-space-4); }
283
+ </style>
283
284
  ```
284
285
 
285
286
  ### Data table page
286
287
 
287
288
  ```svelte
288
289
  <Container>
289
- <Stack gap="lg">
290
- <Flex justify="between" align="center">
291
- <h1>Users</h1>
292
- <Button variant="solid">Add User</Button>
293
- </Flex>
294
- <Table.Root>
295
- <Table.Header>
296
- <Table.Row>
297
- <Table.Head>Name</Table.Head>
298
- <Table.Head>Email</Table.Head>
299
- <Table.Head>Role</Table.Head>
300
- </Table.Row>
301
- </Table.Header>
302
- <Table.Body>
303
- {#each users as user (user.id)}
304
- <Table.Row>
305
- <Table.Cell>{user.name}</Table.Cell>
306
- <Table.Cell>{user.email}</Table.Cell>
307
- <Table.Cell>
308
- <Badge variant="soft">{user.role}</Badge>
309
- </Table.Cell>
310
- </Table.Row>
311
- {/each}
312
- </Table.Body>
313
- </Table.Root>
314
- </Stack>
290
+ <div class="page-stack">
291
+ <div class="page-header">
292
+ <h1>Users</h1>
293
+ <Button variant="solid">Add user</Button>
294
+ </div>
295
+ <Table.Root>
296
+ <Table.Header>
297
+ <Table.Row>
298
+ <Table.Head>Name</Table.Head>
299
+ <Table.Head>Email</Table.Head>
300
+ <Table.Head>Role</Table.Head>
301
+ </Table.Row>
302
+ </Table.Header>
303
+ <Table.Body>
304
+ {#each users as user (user.id)}
305
+ <Table.Row>
306
+ <Table.Cell>{user.name}</Table.Cell>
307
+ <Table.Cell>{user.email}</Table.Cell>
308
+ <Table.Cell>
309
+ <Badge variant="soft">{user.role}</Badge>
310
+ </Table.Cell>
311
+ </Table.Row>
312
+ {/each}
313
+ </Table.Body>
314
+ </Table.Root>
315
+ </div>
315
316
  </Container>
317
+
318
+ <style>
319
+ .page-stack { display: grid; gap: var(--dry-space-6); }
320
+ .page-header {
321
+ display: grid;
322
+ grid-template-columns: 1fr auto;
323
+ align-items: center;
324
+ }
325
+ </style>
316
326
  ```
317
327
 
318
328
  ## Anti-Patterns
319
329
 
320
- ### Nesting layout components unnecessarily
330
+ ### Using flexbox or inline styles
321
331
 
322
332
  ```svelte
323
- <!-- Incorrect: redundant nesting -->
324
- <Stack gap="md">
325
- <Stack gap="sm">
326
- <p>One item</p>
327
- </Stack>
328
- </Stack>
329
-
330
- <!-- Correct: flat when possible -->
331
- <Stack gap="md">
332
- <p>One item</p>
333
- </Stack>
334
- ```
333
+ <!-- Wrong: flexbox -->
334
+ <div style="display: flex; gap: 1rem;">...</div>
335
335
 
336
- ### Using div wrappers instead of layout components
336
+ <!-- Wrong: inline styles -->
337
+ <div style="max-width: 1200px; margin: 0 auto;">...</div>
337
338
 
338
- ```svelte
339
- <!-- Incorrect: manual div wrappers -->
340
- <div class="flex items-center justify-between">
341
- <h2>Title</h2>
342
- <button>Action</button>
343
- </div>
339
+ <!-- Right: scoped grid -->
340
+ <div class="layout">...</div>
341
+ <Container>...</Container>
344
342
 
345
- <!-- Correct: use DryUI layout -->
346
- <Flex justify="between" align="center">
347
- <h2>Title</h2>
348
- <Button>Action</Button>
349
- </Flex>
343
+ <style>
344
+ .layout { display: grid; gap: var(--dry-space-4); }
345
+ </style>
350
346
  ```
351
347
 
352
348
  ### Forgetting Card.Root in form layouts
353
349
 
354
350
  ```svelte
355
- <!-- Incorrect: bare Card -->
351
+ <!-- Wrong: bare Card -->
356
352
  <Card>
357
- <Card.Content>
358
- <Stack gap="md">...</Stack>
359
- </Card.Content>
353
+ <Card.Content>...</Card.Content>
360
354
  </Card>
361
355
 
362
- <!-- Correct: Card.Root -->
356
+ <!-- Right: Card.Root -->
363
357
  <Card.Root>
364
- <Card.Content>
365
- <Stack gap="md">...</Stack>
366
- </Card.Content>
358
+ <Card.Content>...</Card.Content>
367
359
  </Card.Root>
368
360
  ```
369
361
 
@@ -371,37 +363,34 @@ Use Field.Error to show validation messages.
371
363
 
372
364
  Before using any component, call `compose` to get the correct component and usage snippet. This table is a quick reference — `compose` has full snippets and anti-patterns.
373
365
 
374
- | UI Need | Use This | NOT This |
375
- | ----------------- | -------------------------------------- | --------------------------------------- |
376
- | Date picker | `DatePicker.Root` | `<Input type="date">` |
377
- | Date range | `DateRangePicker.Root` | Two `<Input type="date">` |
378
- | Time input | `TimeInput` | `<Input type="time">` |
379
- | Dropdown select | `Select.Root` | `<select>` |
380
- | Searchable select | `Combobox.Root` | `<input>` + custom dropdown |
381
- | Modal dialog | `Dialog.Root` | `<dialog>` or manual overlay |
382
- | Confirmation | `AlertDialog.Root` | `window.confirm()` |
383
- | Side panel | `Drawer.Root` | Fixed-position div |
384
- | Data table | `Table.Root` | `<table>` |
385
- | Person image | `Avatar` | Emoji or bare `<img>` |
386
- | Content image | `Image` | Bare `<img>` |
387
- | Multi-step flow | `Stepper.Root` | Manual step divs |
388
- | Progress bar | `Progress` | CSS-only bar |
389
- | Inline chart | `Sparkline` | Manual SVG |
390
- | Full chart | `Chart.Root` | External chart library |
391
- | Vertical layout | `Stack` | `display: flex; flex-direction: column` |
392
- | Horizontal layout | `Flex` | `display: flex` |
393
- | Grid layout | `Grid` | `display: grid` |
394
- | Max-width wrapper | `Container` | `max-width` + `margin: 0 auto` |
395
- | Form field | `Field.Root` + `Label` + Input | `<label>` + `<input>` |
396
- | Status indicator | `Badge` | Colored `<span>` |
397
- | Loading state | `Skeleton` or `Spinner` | Text "Loading..." |
398
- | Empty state | `EmptyState.Root` | Custom empty div |
399
- | Notifications | `Toast` | Alert div |
400
- | Keyboard shortcut | `Kbd` | `<code>` |
401
- | Code display | `CodeBlock` | `<pre><code>` |
402
- | File upload | `FileUpload.Root` | `<input type="file">` |
403
- | Color picker | `ColorPicker.Root` | `<input type="color">` |
404
- | Collapsible | `Accordion.Root` or `Collapsible.Root` | Manual toggle with if/else |
366
+ | UI Need | Use This | NOT This |
367
+ | ----------------- | -------------------------------------- | --------------------------- |
368
+ | Date picker | `DatePicker.Root` | `<input type="date">` |
369
+ | Date range | `DateRangePicker.Root` | Two `<input type="date">` |
370
+ | Time input | `TimeInput` | `<input type="time">` |
371
+ | Dropdown select | `Select.Root` | `<select>` |
372
+ | Searchable select | `Combobox.Root` | `<input>` + custom dropdown |
373
+ | Modal dialog | `Dialog.Root` | `<dialog>` or manual overlay|
374
+ | Confirmation | `AlertDialog.Root` | `window.confirm()` |
375
+ | Side panel | `Drawer.Root` | Fixed-position div |
376
+ | Data table | `Table.Root` | `<table>` |
377
+ | Person image | `Avatar` | Emoji or bare `<img>` |
378
+ | Content image | `Image` | Bare `<img>` |
379
+ | Multi-step flow | `Stepper.Root` | Manual step divs |
380
+ | Progress bar | `Progress` | CSS-only bar |
381
+ | Inline chart | `Sparkline` | Manual SVG |
382
+ | Full chart | `Chart.Root` | External chart library |
383
+ | Max-width wrapper | `Container` | `max-width` + `margin: auto`|
384
+ | Form field | `Field.Root` + `Label` + Input | `<label>` + `<input>` |
385
+ | Status indicator | `Badge` | Colored `<span>` |
386
+ | Loading state | `Skeleton` or `Spinner` | Text "Loading..." |
387
+ | Empty state | `EmptyState.Root` | Custom empty div |
388
+ | Notifications | `Toast` | Alert div |
389
+ | Keyboard shortcut | `Kbd` | `<code>` |
390
+ | Code display | `CodeBlock` | `<pre><code>` |
391
+ | File upload | `FileUpload.Root` | `<input type="file">` |
392
+ | Color picker | `ColorPicker.Root` | `<input type="color">` |
393
+ | Collapsible | `Accordion.Root` or `Collapsible.Root` | Manual toggle with if/else |
405
394
 
406
395
  ## Composition Recipes
407
396
 
@@ -419,7 +408,7 @@ Call `compose` with any recipe name to get a full working snippet.
419
408
  | `sidebar-layout` | Page with sidebar nav | Grid, Sidebar, PageHeader |
420
409
  | `dashboard-page` | Full dashboard layout | Grid, Sidebar, StatCard, Chart, Table |
421
410
  | `user-profile-card` | User info card | Card, Avatar, Text, Badge, Button |
422
- | `notification-list` | Notification feed | Stack, Card, Avatar, Text, Badge |
411
+ | `notification-list` | Notification feed | Card, Avatar, Text, Badge |
423
412
  | `command-bar` | Command palette trigger | CommandPalette, Hotkey |
424
413
  | `file-upload-form` | File upload with progress | Card, FileUpload, Progress, Button |
425
- | `pricing-table` | Pricing comparison | Grid, Card, Text, Button, Badge |
414
+ | `pricing-table` | Pricing comparison | Card, Text, Button, Badge |