@discourser/design-system 0.3.1 → 0.5.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 (36) hide show
  1. package/README.md +12 -4
  2. package/dist/styles.css +5126 -0
  3. package/guidelines/Guidelines.md +92 -41
  4. package/guidelines/components/accordion.md +732 -0
  5. package/guidelines/components/avatar.md +1015 -0
  6. package/guidelines/components/badge.md +728 -0
  7. package/guidelines/components/button.md +75 -40
  8. package/guidelines/components/card.md +84 -25
  9. package/guidelines/components/checkbox.md +671 -0
  10. package/guidelines/components/dialog.md +619 -31
  11. package/guidelines/components/drawer.md +1616 -0
  12. package/guidelines/components/heading.md +576 -0
  13. package/guidelines/components/icon-button.md +92 -37
  14. package/guidelines/components/input-addon.md +685 -0
  15. package/guidelines/components/input-group.md +830 -0
  16. package/guidelines/components/input.md +92 -37
  17. package/guidelines/components/popover.md +1271 -0
  18. package/guidelines/components/progress.md +836 -0
  19. package/guidelines/components/radio-group.md +852 -0
  20. package/guidelines/components/select.md +1662 -0
  21. package/guidelines/components/skeleton.md +802 -0
  22. package/guidelines/components/slider.md +911 -0
  23. package/guidelines/components/spinner.md +783 -0
  24. package/guidelines/components/switch.md +105 -38
  25. package/guidelines/components/tabs.md +1488 -0
  26. package/guidelines/components/textarea.md +495 -0
  27. package/guidelines/components/toast.md +784 -0
  28. package/guidelines/components/tooltip.md +912 -0
  29. package/guidelines/design-tokens/colors.md +309 -72
  30. package/guidelines/design-tokens/elevation.md +615 -45
  31. package/guidelines/design-tokens/spacing.md +654 -74
  32. package/guidelines/design-tokens/typography.md +432 -50
  33. package/guidelines/overview-components.md +60 -8
  34. package/guidelines/overview-imports.md +314 -0
  35. package/guidelines/overview-patterns.md +3852 -0
  36. package/package.json +4 -2
@@ -0,0 +1,732 @@
1
+ # Accordion
2
+
3
+ **Purpose:** Collapsible content panels for organizing and revealing information progressively, following Material Design 3 principles.
4
+
5
+ ## When to Use This Component
6
+
7
+ Use Accordion when you need to **organize and progressively disclose related content in collapsible sections** to conserve space while keeping content accessible.
8
+
9
+ ### Decision Tree
10
+
11
+ | Scenario | Use Accordion? | Alternative | Reasoning |
12
+ | --------------------------------------------------- | -------------- | ------------------------- | --------------------------------------------------------------- |
13
+ | Displaying FAQ with expandable answers | ✅ Yes | - | Perfect for progressively disclosing detailed information |
14
+ | Organizing settings into collapsible sections | ✅ Yes | - | Groups related settings while saving vertical space |
15
+ | User needs to view multiple sections simultaneously | ⚠️ Maybe | Tabs or Separate sections | Accordion with `multiple={true}` works, but tabs may be clearer |
16
+ | Navigation between different views | ❌ No | Tabs | Tabs show one view at a time with clear navigation |
17
+ | Content where all sections should be visible | ❌ No | Regular sections | Don't hide content that should always be visible |
18
+ | Quick comparison of all options | ❌ No | Table or Grid | Accordion requires clicking to see content |
19
+
20
+ ### Component Comparison
21
+
22
+ ```typescript
23
+ // ✅ Accordion - FAQs with expandable answers
24
+ <Accordion.Root defaultValue="faq-1">
25
+ <Accordion.Item value="faq-1">
26
+ <Accordion.ItemTrigger>
27
+ What is your return policy?
28
+ <Accordion.ItemIndicator />
29
+ </Accordion.ItemTrigger>
30
+ <Accordion.ItemContent>
31
+ <Accordion.ItemBody>
32
+ We accept returns within 30 days of purchase with original packaging.
33
+ </Accordion.ItemBody>
34
+ </Accordion.ItemContent>
35
+ </Accordion.Item>
36
+ <Accordion.Item value="faq-2">
37
+ <Accordion.ItemTrigger>
38
+ How long does shipping take?
39
+ <Accordion.ItemIndicator />
40
+ </Accordion.ItemTrigger>
41
+ <Accordion.ItemContent>
42
+ <Accordion.ItemBody>
43
+ Standard shipping takes 5-7 business days.
44
+ </Accordion.ItemBody>
45
+ </Accordion.ItemContent>
46
+ </Accordion.Item>
47
+ </Accordion.Root>
48
+
49
+ // ❌ Don't use Accordion for navigation - Use Tabs instead
50
+ <Accordion.Root>
51
+ <Accordion.Item value="overview">
52
+ <Accordion.ItemTrigger>Overview</Accordion.ItemTrigger>
53
+ <Accordion.ItemContent>
54
+ <Dashboard />
55
+ </Accordion.ItemContent>
56
+ </Accordion.Item>
57
+ <Accordion.Item value="analytics">
58
+ <Accordion.ItemTrigger>Analytics</Accordion.ItemTrigger>
59
+ <Accordion.ItemContent>
60
+ <AnalyticsView />
61
+ </Accordion.ItemContent>
62
+ </Accordion.Item>
63
+ </Accordion.Root>
64
+
65
+ // ✅ Better: Use Tabs for view switching
66
+ <Tabs.Root defaultValue="overview">
67
+ <Tabs.List>
68
+ <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
69
+ <Tabs.Trigger value="analytics">Analytics</Tabs.Trigger>
70
+ <Tabs.Indicator />
71
+ </Tabs.List>
72
+ <Tabs.Content value="overview"><Dashboard /></Tabs.Content>
73
+ <Tabs.Content value="analytics"><AnalyticsView /></Tabs.Content>
74
+ </Tabs.Root>
75
+
76
+ // ❌ Don't use Accordion when all content should be visible
77
+ <Accordion.Root multiple defaultValue={['step1', 'step2', 'step3']}>
78
+ <Accordion.Item value="step1">
79
+ <Accordion.ItemTrigger>Step 1</Accordion.ItemTrigger>
80
+ <Accordion.ItemContent>Instructions</Accordion.ItemContent>
81
+ </Accordion.Item>
82
+ {/* If all steps need to be visible, don't hide them */}
83
+ </Accordion.Root>
84
+
85
+ // ✅ Better: Use regular sections for always-visible content
86
+ <Stack gap="4">
87
+ <Section>
88
+ <Heading>Step 1</Heading>
89
+ <Text>Instructions...</Text>
90
+ </Section>
91
+ <Section>
92
+ <Heading>Step 2</Heading>
93
+ <Text>Instructions...</Text>
94
+ </Section>
95
+ </Stack>
96
+ ```
97
+
98
+ ## Import
99
+
100
+ ```typescript
101
+ import { Accordion } from '@discourser/design-system';
102
+ ```
103
+
104
+ ## Component Structure
105
+
106
+ The Accordion is a **compound component** that follows the composition pattern. All parts must be used together:
107
+
108
+ | Component | Purpose | Required |
109
+ | ------------------------- | ------------------------------------------- | ----------- |
110
+ | `Accordion.Root` | Container that manages accordion state | Yes |
111
+ | `Accordion.Item` | Individual collapsible section | Yes |
112
+ | `Accordion.ItemTrigger` | Clickable header to toggle item | Yes |
113
+ | `Accordion.ItemContent` | Collapsible content area | Yes |
114
+ | `Accordion.ItemIndicator` | Visual indicator (chevron icon) | Recommended |
115
+ | `Accordion.ItemBody` | Wrapper for content with consistent spacing | Optional |
116
+ | `Accordion.Context` | Access accordion state in custom components | Advanced |
117
+ | `Accordion.RootProvider` | Provide external accordion state | Advanced |
118
+
119
+ **Important:** Never use accordion parts in isolation. They must be nested within `Accordion.Root`.
120
+
121
+ ## Variants
122
+
123
+ The Accordion component supports 2 visual variants:
124
+
125
+ | Variant | Visual Style | Usage | When to Use |
126
+ | --------- | --------------------------- | ---------------- | --------------------------------------- |
127
+ | `outline` | Border between items | Default style | FAQs, settings panels, content sections |
128
+ | `plain` | No borders, minimal styling | Clean appearance | Minimalist designs, nested accordions |
129
+
130
+ ### Visual Characteristics
131
+
132
+ - **outline**: 1px border-bottom between items, clear visual separation
133
+ - **plain**: No borders, relying on spacing and typography for hierarchy
134
+
135
+ ## Sizes
136
+
137
+ | Size | Trigger Height | Padding | Font Size | Usage |
138
+ | ---- | -------------- | ------------------- | ------------- | ---------------------- |
139
+ | `md` | auto | 12px (y) / 16px (x) | textStyle: md | Default, all use cases |
140
+
141
+ **Note:** Currently only `md` size is defined. Additional sizes can be added to the recipe as needed.
142
+
143
+ ## Props
144
+
145
+ ### Root Props
146
+
147
+ | Prop | Type | Default | Description |
148
+ | --------------- | ---------------------------------------- | ----------- | --------------------------------------------------------- |
149
+ | `defaultValue` | `string \| string[]` | - | Initially expanded item(s) |
150
+ | `value` | `string \| string[]` | - | Controlled expanded item(s) |
151
+ | `onValueChange` | `(details: { value: string[] }) => void` | - | Callback when expansion changes |
152
+ | `multiple` | `boolean` | `false` | Allow multiple items to be expanded simultaneously |
153
+ | `collapsible` | `boolean` | `true` | Allow all items to be collapsed (when `multiple={false}`) |
154
+ | `disabled` | `boolean` | `false` | Disable all accordion items |
155
+ | `variant` | `'outline' \| 'plain'` | `'outline'` | Visual style variant |
156
+ | `size` | `'md'` | `'md'` | Accordion size |
157
+
158
+ ### Item Props
159
+
160
+ | Prop | Type | Default | Description |
161
+ | ---------- | --------- | -------- | ------------------------------ |
162
+ | `value` | `string` | Required | Unique identifier for the item |
163
+ | `disabled` | `boolean` | `false` | Disable this specific item |
164
+
165
+ ### ItemTrigger Props
166
+
167
+ | Prop | Type | Default | Description |
168
+ | ---------- | ----------- | -------- | ------------------------------------ |
169
+ | `children` | `ReactNode` | Required | Trigger content (usually title text) |
170
+
171
+ ### ItemContent Props
172
+
173
+ | Prop | Type | Default | Description |
174
+ | ---------- | ----------- | -------- | ------------------- |
175
+ | `children` | `ReactNode` | Required | Collapsible content |
176
+
177
+ ### ItemIndicator Props
178
+
179
+ | Prop | Type | Default | Description |
180
+ | ---------- | ----------- | --------------------- | ------------------------------------------- |
181
+ | `children` | `ReactNode` | `<ChevronDownIcon />` | Custom indicator icon (defaults to chevron) |
182
+
183
+ ## Examples
184
+
185
+ ### Basic Usage
186
+
187
+ ```typescript
188
+ // Single item expanded at a time (default)
189
+ <Accordion.Root defaultValue="item-1">
190
+ <Accordion.Item value="item-1">
191
+ <Accordion.ItemTrigger>
192
+ What is React?
193
+ <Accordion.ItemIndicator />
194
+ </Accordion.ItemTrigger>
195
+ <Accordion.ItemContent>
196
+ <Accordion.ItemBody>
197
+ React is a JavaScript library for building user interfaces.
198
+ </Accordion.ItemBody>
199
+ </Accordion.ItemContent>
200
+ </Accordion.Item>
201
+
202
+ <Accordion.Item value="item-2">
203
+ <Accordion.ItemTrigger>
204
+ What is TypeScript?
205
+ <Accordion.ItemIndicator />
206
+ </Accordion.ItemTrigger>
207
+ <Accordion.ItemContent>
208
+ <Accordion.ItemBody>
209
+ TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
210
+ </Accordion.ItemBody>
211
+ </Accordion.ItemContent>
212
+ </Accordion.Item>
213
+ </Accordion.Root>
214
+ ```
215
+
216
+ ### Multiple Items Expanded
217
+
218
+ ```typescript
219
+ // Allow multiple items to be open simultaneously
220
+ <Accordion.Root multiple defaultValue={['item-1', 'item-2']}>
221
+ <Accordion.Item value="item-1">
222
+ <Accordion.ItemTrigger>
223
+ Section 1
224
+ <Accordion.ItemIndicator />
225
+ </Accordion.ItemTrigger>
226
+ <Accordion.ItemContent>
227
+ <Accordion.ItemBody>
228
+ First section content
229
+ </Accordion.ItemBody>
230
+ </Accordion.ItemContent>
231
+ </Accordion.Item>
232
+
233
+ <Accordion.Item value="item-2">
234
+ <Accordion.ItemTrigger>
235
+ Section 2
236
+ <Accordion.ItemIndicator />
237
+ </Accordion.ItemTrigger>
238
+ <Accordion.ItemContent>
239
+ <Accordion.ItemBody>
240
+ Second section content
241
+ </Accordion.ItemBody>
242
+ </Accordion.ItemContent>
243
+ </Accordion.Item>
244
+ </Accordion.Root>
245
+ ```
246
+
247
+ ### Controlled State
248
+
249
+ ```typescript
250
+ const [value, setValue] = useState<string[]>(['item-1']);
251
+
252
+ <Accordion.Root
253
+ multiple
254
+ value={value}
255
+ onValueChange={(details) => setValue(details.value)}
256
+ >
257
+ <Accordion.Item value="item-1">
258
+ <Accordion.ItemTrigger>
259
+ Controlled Item 1
260
+ <Accordion.ItemIndicator />
261
+ </Accordion.ItemTrigger>
262
+ <Accordion.ItemContent>
263
+ <Accordion.ItemBody>
264
+ This accordion is controlled by React state
265
+ </Accordion.ItemBody>
266
+ </Accordion.ItemContent>
267
+ </Accordion.Item>
268
+ </Accordion.Root>
269
+ ```
270
+
271
+ ### Non-Collapsible Mode
272
+
273
+ ```typescript
274
+ // At least one item must always be open
275
+ <Accordion.Root multiple={false} collapsible={false} defaultValue="item-1">
276
+ <Accordion.Item value="item-1">
277
+ <Accordion.ItemTrigger>
278
+ Always One Open
279
+ <Accordion.ItemIndicator />
280
+ </Accordion.ItemTrigger>
281
+ <Accordion.ItemContent>
282
+ <Accordion.ItemBody>
283
+ You cannot collapse all items in this mode
284
+ </Accordion.ItemBody>
285
+ </Accordion.ItemContent>
286
+ </Accordion.Item>
287
+ </Accordion.Root>
288
+ ```
289
+
290
+ ### Disabled Items
291
+
292
+ ```typescript
293
+ <Accordion.Root>
294
+ <Accordion.Item value="item-1">
295
+ <Accordion.ItemTrigger>
296
+ Active Item
297
+ <Accordion.ItemIndicator />
298
+ </Accordion.ItemTrigger>
299
+ <Accordion.ItemContent>
300
+ <Accordion.ItemBody>
301
+ This item can be toggled
302
+ </Accordion.ItemBody>
303
+ </Accordion.ItemContent>
304
+ </Accordion.Item>
305
+
306
+ <Accordion.Item value="item-2" disabled>
307
+ <Accordion.ItemTrigger>
308
+ Disabled Item
309
+ <Accordion.ItemIndicator />
310
+ </Accordion.ItemTrigger>
311
+ <Accordion.ItemContent>
312
+ <Accordion.ItemBody>
313
+ This content cannot be accessed
314
+ </Accordion.ItemBody>
315
+ </Accordion.ItemContent>
316
+ </Accordion.Item>
317
+ </Accordion.Root>
318
+ ```
319
+
320
+ ### Plain Variant
321
+
322
+ ```typescript
323
+ // Minimal styling without borders
324
+ <Accordion.Root variant="plain">
325
+ <Accordion.Item value="item-1">
326
+ <Accordion.ItemTrigger>
327
+ Clean Design
328
+ <Accordion.ItemIndicator />
329
+ </Accordion.ItemTrigger>
330
+ <Accordion.ItemContent>
331
+ <Accordion.ItemBody>
332
+ No borders for a minimal aesthetic
333
+ </Accordion.ItemBody>
334
+ </Accordion.ItemContent>
335
+ </Accordion.Item>
336
+ </Accordion.Root>
337
+ ```
338
+
339
+ ### Dynamic Content
340
+
341
+ ```typescript
342
+ const faqs = [
343
+ { id: 'faq-1', question: 'How do I reset my password?', answer: 'Click on "Forgot Password" on the login page.' },
344
+ { id: 'faq-2', question: 'How do I contact support?', answer: 'Email us at support@example.com' },
345
+ { id: 'faq-3', question: 'What payment methods do you accept?', answer: 'We accept all major credit cards and PayPal.' },
346
+ ];
347
+
348
+ <Accordion.Root defaultValue="faq-1">
349
+ {faqs.map((faq) => (
350
+ <Accordion.Item key={faq.id} value={faq.id}>
351
+ <Accordion.ItemTrigger>
352
+ {faq.question}
353
+ <Accordion.ItemIndicator />
354
+ </Accordion.ItemTrigger>
355
+ <Accordion.ItemContent>
356
+ <Accordion.ItemBody>
357
+ {faq.answer}
358
+ </Accordion.ItemBody>
359
+ </Accordion.ItemContent>
360
+ </Accordion.Item>
361
+ ))}
362
+ </Accordion.Root>
363
+ ```
364
+
365
+ ## Common Patterns
366
+
367
+ ### FAQ Section
368
+
369
+ ```typescript
370
+ <section>
371
+ <h2>Frequently Asked Questions</h2>
372
+ <Accordion.Root variant="outline">
373
+ <Accordion.Item value="shipping">
374
+ <Accordion.ItemTrigger>
375
+ What are your shipping options?
376
+ <Accordion.ItemIndicator />
377
+ </Accordion.ItemTrigger>
378
+ <Accordion.ItemContent>
379
+ <Accordion.ItemBody>
380
+ We offer standard (5-7 days) and express (2-3 days) shipping options.
381
+ Free shipping on orders over $50.
382
+ </Accordion.ItemBody>
383
+ </Accordion.ItemContent>
384
+ </Accordion.Item>
385
+
386
+ <Accordion.Item value="returns">
387
+ <Accordion.ItemTrigger>
388
+ What is your return policy?
389
+ <Accordion.ItemIndicator />
390
+ </Accordion.ItemTrigger>
391
+ <Accordion.ItemContent>
392
+ <Accordion.ItemBody>
393
+ We accept returns within 30 days of purchase. Items must be unused
394
+ and in original packaging.
395
+ </Accordion.ItemBody>
396
+ </Accordion.ItemContent>
397
+ </Accordion.Item>
398
+ </Accordion.Root>
399
+ </section>
400
+ ```
401
+
402
+ ### Settings Panel
403
+
404
+ ```typescript
405
+ <Accordion.Root multiple defaultValue={['account', 'privacy']}>
406
+ <Accordion.Item value="account">
407
+ <Accordion.ItemTrigger>
408
+ Account Settings
409
+ <Accordion.ItemIndicator />
410
+ </Accordion.ItemTrigger>
411
+ <Accordion.ItemContent>
412
+ <Accordion.ItemBody>
413
+ <Input label="Email" defaultValue="user@example.com" />
414
+ <Input label="Username" defaultValue="johndoe" />
415
+ <Button>Save Changes</Button>
416
+ </Accordion.ItemBody>
417
+ </Accordion.ItemContent>
418
+ </Accordion.Item>
419
+
420
+ <Accordion.Item value="privacy">
421
+ <Accordion.ItemTrigger>
422
+ Privacy Settings
423
+ <Accordion.ItemIndicator />
424
+ </Accordion.ItemTrigger>
425
+ <Accordion.ItemContent>
426
+ <Accordion.ItemBody>
427
+ <Switch label="Allow marketing emails" />
428
+ <Switch label="Public profile" defaultChecked />
429
+ </Accordion.ItemBody>
430
+ </Accordion.ItemContent>
431
+ </Accordion.Item>
432
+ </Accordion.Root>
433
+ ```
434
+
435
+ ### Rich Content
436
+
437
+ ```typescript
438
+ <Accordion.Root>
439
+ <Accordion.Item value="features">
440
+ <Accordion.ItemTrigger>
441
+ Key Features
442
+ <Accordion.ItemIndicator />
443
+ </Accordion.ItemTrigger>
444
+ <Accordion.ItemContent>
445
+ <Accordion.ItemBody>
446
+ <ul>
447
+ <li>Real-time collaboration</li>
448
+ <li>Cloud storage integration</li>
449
+ <li>Advanced security features</li>
450
+ </ul>
451
+ <Button variant="text" rightIcon={<ArrowIcon />}>
452
+ Learn More
453
+ </Button>
454
+ </Accordion.ItemBody>
455
+ </Accordion.ItemContent>
456
+ </Accordion.Item>
457
+ </Accordion.Root>
458
+ ```
459
+
460
+ ## DO NOT
461
+
462
+ ```typescript
463
+ // ❌ Don't use accordion parts without Root
464
+ <Accordion.Item value="item-1">
465
+ <Accordion.ItemTrigger>Won't work</Accordion.ItemTrigger>
466
+ </Accordion.Item>
467
+
468
+ // ❌ Don't forget unique values for each item
469
+ <Accordion.Root>
470
+ <Accordion.Item value="same">...</Accordion.Item>
471
+ <Accordion.Item value="same">...</Accordion.Item> // Collision!
472
+ </Accordion.Root>
473
+
474
+ // ❌ Don't use multiple={false} with array defaultValue
475
+ <Accordion.Root multiple={false} defaultValue={['item-1', 'item-2']}>
476
+ // Only works with single string value when multiple={false}
477
+ </Accordion.Root>
478
+
479
+ // ❌ Don't nest interactive elements in ItemTrigger
480
+ <Accordion.ItemTrigger>
481
+ <button>Nested button</button> // Breaks accessibility
482
+ <Accordion.ItemIndicator />
483
+ </Accordion.ItemTrigger>
484
+
485
+ // ❌ Don't omit ItemIndicator (poor UX)
486
+ <Accordion.ItemTrigger>
487
+ No visual cue for expansion // Users won't know it's expandable
488
+ </Accordion.ItemTrigger>
489
+
490
+ // ❌ Don't override styles with inline styles
491
+ <Accordion.Root style={{ backgroundColor: 'red' }}> // Use variants instead
492
+ </Accordion.Root>
493
+
494
+ // ✅ Use compound components properly
495
+ <Accordion.Root>
496
+ <Accordion.Item value="item-1">
497
+ <Accordion.ItemTrigger>
498
+ Proper Structure
499
+ <Accordion.ItemIndicator />
500
+ </Accordion.ItemTrigger>
501
+ <Accordion.ItemContent>
502
+ <Accordion.ItemBody>Content here</Accordion.ItemBody>
503
+ </Accordion.ItemContent>
504
+ </Accordion.Item>
505
+ </Accordion.Root>
506
+ ```
507
+
508
+ ## Accessibility
509
+
510
+ The Accordion component follows WCAG 2.1 Level AA standards and implements WAI-ARIA Accordion Pattern:
511
+
512
+ - **Keyboard Navigation**:
513
+ - `Tab` / `Shift+Tab`: Navigate between triggers
514
+ - `Enter` / `Space`: Toggle item expansion
515
+ - `ArrowDown`: Focus next trigger
516
+ - `ArrowUp`: Focus previous trigger
517
+ - `Home`: Focus first trigger
518
+ - `End`: Focus last trigger
519
+
520
+ - **ARIA Attributes**: Automatically managed
521
+ - `role="region"` on content areas
522
+ - `aria-expanded` on triggers (true/false)
523
+ - `aria-controls` links trigger to content
524
+ - `aria-labelledby` links content to trigger
525
+ - `aria-disabled` on disabled items
526
+
527
+ - **Focus Management**: Clear focus indicators on keyboard navigation
528
+ - **Screen Readers**: Announce expansion state and content structure
529
+
530
+ ### Accessibility Best Practices
531
+
532
+ ```typescript
533
+ // ✅ Use descriptive trigger text
534
+ <Accordion.ItemTrigger>
535
+ How do I reset my password?
536
+ <Accordion.ItemIndicator />
537
+ </Accordion.ItemTrigger>
538
+
539
+ // ✅ Provide meaningful content
540
+ <Accordion.ItemContent>
541
+ <Accordion.ItemBody>
542
+ Step-by-step instructions with clear language
543
+ </Accordion.ItemBody>
544
+ </Accordion.ItemContent>
545
+
546
+ // ✅ Use semantic HTML in content
547
+ <Accordion.ItemContent>
548
+ <Accordion.ItemBody>
549
+ <h3>Subsection Title</h3>
550
+ <p>Well-structured content improves screen reader navigation</p>
551
+ </Accordion.ItemBody>
552
+ </Accordion.ItemContent>
553
+
554
+ // ✅ Indicate disabled state clearly
555
+ <Accordion.Item value="locked" disabled>
556
+ <Accordion.ItemTrigger>
557
+ Premium Feature (Upgrade Required)
558
+ <Accordion.ItemIndicator />
559
+ </Accordion.ItemTrigger>
560
+ </Accordion.Item>
561
+ ```
562
+
563
+ ## Variant Selection Guide
564
+
565
+ | Scenario | Recommended Variant | Reasoning |
566
+ | ------------------ | ------------------- | ------------------------------------------ |
567
+ | FAQ section | `outline` | Clear visual separation between questions |
568
+ | Settings panel | `outline` | Organized, scannable interface |
569
+ | Nested content | `plain` | Avoid visual clutter with too many borders |
570
+ | Minimal design | `plain` | Clean, modern aesthetic |
571
+ | Form sections | `outline` | Clear boundaries between form groups |
572
+ | Sidebar navigation | `plain` | Streamlined appearance |
573
+
574
+ ## State Behaviors
575
+
576
+ | State | Visual Change | Behavior |
577
+ | ------------- | --------------------------------------------------- | ------------------------------------------- |
578
+ | **Collapsed** | Content hidden, chevron points down | ItemContent has `display: none` |
579
+ | **Expanded** | Content visible, chevron points up (rotated 180deg) | Smooth height animation via `expand-height` |
580
+ | **Hover** | Trigger shows hover state | Visual feedback on interactive element |
581
+ | **Focus** | Focus ring on trigger | Keyboard navigation indicator |
582
+ | **Disabled** | 38% opacity, cursor not-allowed | Item cannot be toggled |
583
+
584
+ ## Animation Details
585
+
586
+ The Accordion uses smooth expand/collapse animations:
587
+
588
+ - **Expand**: `expand-height` + `fade-in` over `normal` duration
589
+ - **Collapse**: `collapse-height` + `fade-out` over `normal` duration
590
+ - **Indicator**: 0.2s rotation transition
591
+
592
+ ## Responsive Considerations
593
+
594
+ ```typescript
595
+ // Mobile: Full width is default
596
+ <Accordion.Root>
597
+ {/* Automatically responsive */}
598
+ </Accordion.Root>
599
+
600
+ // Desktop: May want to constrain width
601
+ <div className={css({ maxWidth: '800px', mx: 'auto' })}>
602
+ <Accordion.Root>
603
+ {/* Content with reasonable line length */}
604
+ </Accordion.Root>
605
+ </div>
606
+
607
+ // Responsive padding in content
608
+ <Accordion.ItemContent>
609
+ <Accordion.ItemBody>
610
+ <div className={css({
611
+ px: { base: '4', md: '6' },
612
+ py: { base: '3', md: '4' }
613
+ })}>
614
+ Responsive content spacing
615
+ </div>
616
+ </Accordion.ItemBody>
617
+ </Accordion.ItemContent>
618
+ ```
619
+
620
+ ## Testing
621
+
622
+ When testing Accordion components:
623
+
624
+ ```typescript
625
+ import { render, screen } from '@testing-library/react';
626
+ import userEvent from '@testing-library/user-event';
627
+
628
+ test('accordion expands on trigger click', async () => {
629
+ render(
630
+ <Accordion.Root>
631
+ <Accordion.Item value="test">
632
+ <Accordion.ItemTrigger>
633
+ Question
634
+ <Accordion.ItemIndicator />
635
+ </Accordion.ItemTrigger>
636
+ <Accordion.ItemContent>
637
+ <Accordion.ItemBody>Answer</Accordion.ItemBody>
638
+ </Accordion.ItemContent>
639
+ </Accordion.Item>
640
+ </Accordion.Root>
641
+ );
642
+
643
+ const trigger = screen.getByText('Question');
644
+ expect(screen.queryByText('Answer')).not.toBeVisible();
645
+
646
+ await userEvent.click(trigger);
647
+ expect(screen.getByText('Answer')).toBeVisible();
648
+ });
649
+
650
+ test('disabled item cannot be toggled', async () => {
651
+ render(
652
+ <Accordion.Root>
653
+ <Accordion.Item value="test" disabled>
654
+ <Accordion.ItemTrigger>
655
+ Disabled
656
+ <Accordion.ItemIndicator />
657
+ </Accordion.ItemTrigger>
658
+ <Accordion.ItemContent>
659
+ <Accordion.ItemBody>Content</Accordion.ItemBody>
660
+ </Accordion.ItemContent>
661
+ </Accordion.Item>
662
+ </Accordion.Root>
663
+ );
664
+
665
+ const trigger = screen.getByText('Disabled');
666
+ await userEvent.click(trigger);
667
+
668
+ expect(screen.queryByText('Content')).not.toBeVisible();
669
+ expect(trigger).toHaveAttribute('aria-disabled', 'true');
670
+ });
671
+
672
+ test('multiple mode allows multiple items open', async () => {
673
+ render(
674
+ <Accordion.Root multiple>
675
+ <Accordion.Item value="item-1">
676
+ <Accordion.ItemTrigger>
677
+ First
678
+ <Accordion.ItemIndicator />
679
+ </Accordion.ItemTrigger>
680
+ <Accordion.ItemContent>
681
+ <Accordion.ItemBody>First content</Accordion.ItemBody>
682
+ </Accordion.ItemContent>
683
+ </Accordion.Item>
684
+ <Accordion.Item value="item-2">
685
+ <Accordion.ItemTrigger>
686
+ Second
687
+ <Accordion.ItemIndicator />
688
+ </Accordion.ItemTrigger>
689
+ <Accordion.ItemContent>
690
+ <Accordion.ItemBody>Second content</Accordion.ItemBody>
691
+ </Accordion.ItemContent>
692
+ </Accordion.Item>
693
+ </Accordion.Root>
694
+ );
695
+
696
+ await userEvent.click(screen.getByText('First'));
697
+ await userEvent.click(screen.getByText('Second'));
698
+
699
+ expect(screen.getByText('First content')).toBeVisible();
700
+ expect(screen.getByText('Second content')).toBeVisible();
701
+ });
702
+
703
+ test('controlled accordion updates on value change', async () => {
704
+ const TestComponent = () => {
705
+ const [value, setValue] = useState<string[]>([]);
706
+ return (
707
+ <>
708
+ <button onClick={() => setValue(['test'])}>Expand</button>
709
+ <Accordion.Root value={value} onValueChange={(d) => setValue(d.value)}>
710
+ <Accordion.Item value="test">
711
+ <Accordion.ItemTrigger>Trigger</Accordion.ItemTrigger>
712
+ <Accordion.ItemContent>
713
+ <Accordion.ItemBody>Content</Accordion.ItemBody>
714
+ </Accordion.ItemContent>
715
+ </Accordion.Item>
716
+ </Accordion.Root>
717
+ </>
718
+ );
719
+ };
720
+
721
+ render(<TestComponent />);
722
+ await userEvent.click(screen.getByText('Expand'));
723
+ expect(screen.getByText('Content')).toBeVisible();
724
+ });
725
+ ```
726
+
727
+ ## Related Components
728
+
729
+ - **Tabs**: For switching between different views (not progressive disclosure)
730
+ - **Dialog**: For modal overlays with focused content
731
+ - **Collapsible**: For single collapsible sections (simpler alternative)
732
+ - **Menu**: For navigation or action menus