@ceed/cds 1.22.3 → 1.22.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/data-display/InfoSign.md +74 -91
- package/dist/components/data-display/Typography.md +363 -63
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -104
- package/dist/components/inputs/CurrencyInput.md +181 -8
- package/dist/components/inputs/DatePicker.md +108 -436
- package/dist/components/inputs/DateRangePicker.md +130 -496
- package/dist/components/inputs/FilterableCheckboxGroup.md +141 -20
- package/dist/components/inputs/FormControl.md +361 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/Input.md +203 -77
- package/dist/components/inputs/MonthPicker.md +95 -427
- package/dist/components/inputs/MonthRangePicker.md +89 -471
- package/dist/components/inputs/PercentageInput.md +183 -19
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +146 -62
- package/dist/components/inputs/Select.md +219 -328
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +143 -376
- package/dist/components/inputs/Textarea.md +209 -11
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +3 -0
- package/dist/components/navigation/Breadcrumbs.md +57 -308
- package/dist/components/navigation/Drawer.md +180 -0
- package/dist/components/navigation/Dropdown.md +98 -215
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +281 -650
- package/dist/components/navigation/Link.md +31 -348
- package/dist/components/navigation/Menu.md +92 -285
- package/dist/components/navigation/MenuButton.md +55 -448
- package/dist/components/navigation/Pagination.md +47 -338
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Accordions.md +49 -804
- package/dist/components/surfaces/Card.md +97 -157
- package/dist/components/surfaces/Divider.md +83 -234
- package/dist/components/surfaces/Sheet.md +152 -327
- package/dist/guides/ThemeProvider.md +89 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- package/package.json +1 -1
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
Accordions is a component that displays a vertically stacked list of collapsible sections
|
|
5
|
+
Accordions is a component that displays a vertically stacked list of collapsible sections. Each section has a header that users can click to expand or collapse the associated content. This pattern efficiently organizes information through progressive disclosure, reducing visual clutter by allowing users to focus on one section at a time.
|
|
6
|
+
|
|
7
|
+
Accordions are ideal for FAQ pages, settings panels, product detail sections, and any content that benefits from being grouped into expandable categories. The component supports multiple variants, colors, and sizes for flexible visual customization.
|
|
6
8
|
|
|
7
9
|
```tsx
|
|
8
10
|
<Accordions {...args} />
|
|
@@ -12,15 +14,6 @@ Accordions is a component that displays a vertically stacked list of collapsible
|
|
|
12
14
|
| ----- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
13
15
|
| items | Array of accordion summaries and details | \[\{ summary: 'First header', details: \<h3>Content of the first accordion.\</h3> }, \{ summary: 'Second header', details: \<h3>Content of the second accordion.\</h3> }, \{ summary: 'Third header', details: \<h3>Content of the third accordion.\</h3> }] |
|
|
14
16
|
|
|
15
|
-
> ⚠️ **Usage Warning** ⚠️
|
|
16
|
-
>
|
|
17
|
-
> Consider these guidelines when using Accordions:
|
|
18
|
-
>
|
|
19
|
-
> - **Don't hide critical content**: Important information should be visible by default
|
|
20
|
-
> - **Limit nesting**: Avoid nesting accordions within accordions
|
|
21
|
-
> - **Keep headers concise**: Users should understand content without expanding
|
|
22
|
-
> - **Consider Tabs**: Use Tabs for content that users need to compare side-by-side
|
|
23
|
-
|
|
24
17
|
## Usage
|
|
25
18
|
|
|
26
19
|
```tsx
|
|
@@ -48,19 +41,9 @@ function FAQSection() {
|
|
|
48
41
|
}
|
|
49
42
|
```
|
|
50
43
|
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
-
### Playground
|
|
54
|
-
|
|
55
|
-
Interactive example with all controls.
|
|
56
|
-
|
|
57
|
-
```tsx
|
|
58
|
-
<Accordions {...args} />
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Basics
|
|
44
|
+
## Basics
|
|
62
45
|
|
|
63
|
-
|
|
46
|
+
A basic accordion with multiple collapsible sections.
|
|
64
47
|
|
|
65
48
|
```tsx
|
|
66
49
|
<Accordions
|
|
@@ -77,9 +60,9 @@ Basic accordion with multiple collapsible sections.
|
|
|
77
60
|
/>
|
|
78
61
|
```
|
|
79
62
|
|
|
80
|
-
|
|
63
|
+
## Sizes
|
|
81
64
|
|
|
82
|
-
Available
|
|
65
|
+
Available in three sizes: `sm`, `md`, and `lg`. Use smaller sizes for sidebars or navigation menus, and larger sizes for main content areas.
|
|
83
66
|
|
|
84
67
|
```tsx
|
|
85
68
|
<>
|
|
@@ -89,9 +72,9 @@ Available size options: `sm`, `md`, `lg`.
|
|
|
89
72
|
</>
|
|
90
73
|
```
|
|
91
74
|
|
|
92
|
-
|
|
75
|
+
## Variants
|
|
93
76
|
|
|
94
|
-
|
|
77
|
+
Four style variants are available: `plain`, `outlined`, `soft`, and `solid`. Choose the variant that best matches the surrounding context.
|
|
95
78
|
|
|
96
79
|
```tsx
|
|
97
80
|
<>
|
|
@@ -114,9 +97,9 @@ Style variants: `plain`, `outlined`, `soft`, `solid`.
|
|
|
114
97
|
</>
|
|
115
98
|
```
|
|
116
99
|
|
|
117
|
-
|
|
100
|
+
## Colors
|
|
118
101
|
|
|
119
|
-
|
|
102
|
+
Apply semantic colors to match the accordion's purpose: `primary`, `neutral`, `danger`, `success`, or `warning`.
|
|
120
103
|
|
|
121
104
|
```tsx
|
|
122
105
|
<>
|
|
@@ -143,40 +126,21 @@ Color options: `primary`, `neutral`, `danger`, `success`, `warning`.
|
|
|
143
126
|
</>
|
|
144
127
|
```
|
|
145
128
|
|
|
146
|
-
|
|
129
|
+
## Removing Divider
|
|
147
130
|
|
|
148
|
-
Use `disableDivider` to remove the divider lines between accordion items.
|
|
131
|
+
Use the `disableDivider` prop to remove the divider lines between accordion items for a cleaner look.
|
|
149
132
|
|
|
150
133
|
```tsx
|
|
151
134
|
<Accordions {...args} disableDivider />
|
|
152
135
|
```
|
|
153
136
|
|
|
154
|
-
## When to Use
|
|
155
|
-
|
|
156
|
-
### ✅ Good Use Cases
|
|
157
|
-
|
|
158
|
-
- **FAQs**: Frequently asked questions with expandable answers
|
|
159
|
-
- **Settings panels**: Grouped settings that can be expanded individually
|
|
160
|
-
- **Product details**: Specifications, reviews, shipping info sections
|
|
161
|
-
- **Documentation**: Collapsible sections for detailed explanations
|
|
162
|
-
- **Navigation menus**: Expandable category menus
|
|
163
|
-
- **Form sections**: Long forms grouped into logical sections
|
|
164
|
-
- **Help content**: Step-by-step instructions or troubleshooting guides
|
|
165
|
-
|
|
166
|
-
### ❌ When Not to Use
|
|
167
|
-
|
|
168
|
-
- **Critical information**: Don't hide must-read content in accordions
|
|
169
|
-
- **Short content**: Use a list or cards if content is minimal
|
|
170
|
-
- **Comparison content**: Use Tabs when users need to compare sections
|
|
171
|
-
- **Primary navigation**: Use Navigator or Tabs for main navigation
|
|
172
|
-
- **Single item**: Don't use accordion for a single collapsible section
|
|
173
|
-
- **Deeply nested content**: Avoid nesting accordions within accordions
|
|
174
|
-
|
|
175
137
|
## Common Use Cases
|
|
176
138
|
|
|
177
139
|
### FAQ Section
|
|
178
140
|
|
|
179
141
|
```tsx
|
|
142
|
+
import { Accordions, Typography, Stack, Box } from '@ceed/cds';
|
|
143
|
+
|
|
180
144
|
function FAQAccordion() {
|
|
181
145
|
const faqs = [
|
|
182
146
|
{
|
|
@@ -184,7 +148,7 @@ function FAQAccordion() {
|
|
|
184
148
|
details: (
|
|
185
149
|
<Typography>
|
|
186
150
|
We accept Visa, MasterCard, American Express, and PayPal.
|
|
187
|
-
All payments are processed securely
|
|
151
|
+
All payments are processed securely.
|
|
188
152
|
</Typography>
|
|
189
153
|
),
|
|
190
154
|
},
|
|
@@ -192,9 +156,8 @@ function FAQAccordion() {
|
|
|
192
156
|
summary: 'How can I track my order?',
|
|
193
157
|
details: (
|
|
194
158
|
<Typography>
|
|
195
|
-
Once your order ships, you
|
|
196
|
-
number
|
|
197
|
-
site to track your package.
|
|
159
|
+
Once your order ships, you will receive an email with a tracking
|
|
160
|
+
number that you can use on our website.
|
|
198
161
|
</Typography>
|
|
199
162
|
),
|
|
200
163
|
},
|
|
@@ -227,7 +190,9 @@ function FAQAccordion() {
|
|
|
227
190
|
### Settings Panel
|
|
228
191
|
|
|
229
192
|
```tsx
|
|
230
|
-
|
|
193
|
+
import { Accordions, Stack, FormControl, FormLabel, Input, Checkbox } from '@ceed/cds';
|
|
194
|
+
|
|
195
|
+
function SettingsPanel() {
|
|
231
196
|
const settingsItems = [
|
|
232
197
|
{
|
|
233
198
|
summary: 'Account Settings',
|
|
@@ -235,18 +200,11 @@ function SettingsAccordion({ settings, onSettingChange }) {
|
|
|
235
200
|
<Stack gap={2}>
|
|
236
201
|
<FormControl>
|
|
237
202
|
<FormLabel>Display Name</FormLabel>
|
|
238
|
-
<Input
|
|
239
|
-
value={settings.displayName}
|
|
240
|
-
onChange={(e) => onSettingChange('displayName', e.target.value)}
|
|
241
|
-
/>
|
|
203
|
+
<Input placeholder="Enter your name" />
|
|
242
204
|
</FormControl>
|
|
243
205
|
<FormControl>
|
|
244
206
|
<FormLabel>Email</FormLabel>
|
|
245
|
-
<Input
|
|
246
|
-
type="email"
|
|
247
|
-
value={settings.email}
|
|
248
|
-
onChange={(e) => onSettingChange('email', e.target.value)}
|
|
249
|
-
/>
|
|
207
|
+
<Input type="email" placeholder="your@email.com" />
|
|
250
208
|
</FormControl>
|
|
251
209
|
</Stack>
|
|
252
210
|
),
|
|
@@ -255,179 +213,32 @@ function SettingsAccordion({ settings, onSettingChange }) {
|
|
|
255
213
|
summary: 'Notification Preferences',
|
|
256
214
|
details: (
|
|
257
215
|
<Stack gap={1}>
|
|
258
|
-
<Checkbox
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
onChange={(e) =>
|
|
262
|
-
onSettingChange('emailNotifications', e.target.checked)
|
|
263
|
-
}
|
|
264
|
-
/>
|
|
265
|
-
<Checkbox
|
|
266
|
-
label="Push notifications"
|
|
267
|
-
checked={settings.pushNotifications}
|
|
268
|
-
onChange={(e) =>
|
|
269
|
-
onSettingChange('pushNotifications', e.target.checked)
|
|
270
|
-
}
|
|
271
|
-
/>
|
|
272
|
-
<Checkbox
|
|
273
|
-
label="Weekly digest"
|
|
274
|
-
checked={settings.weeklyDigest}
|
|
275
|
-
onChange={(e) => onSettingChange('weeklyDigest', e.target.checked)}
|
|
276
|
-
/>
|
|
277
|
-
</Stack>
|
|
278
|
-
),
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
summary: 'Privacy Settings',
|
|
282
|
-
details: (
|
|
283
|
-
<Stack gap={1}>
|
|
284
|
-
<Checkbox
|
|
285
|
-
label="Make profile public"
|
|
286
|
-
checked={settings.publicProfile}
|
|
287
|
-
onChange={(e) => onSettingChange('publicProfile', e.target.checked)}
|
|
288
|
-
/>
|
|
289
|
-
<Checkbox
|
|
290
|
-
label="Show activity status"
|
|
291
|
-
checked={settings.showActivity}
|
|
292
|
-
onChange={(e) => onSettingChange('showActivity', e.target.checked)}
|
|
293
|
-
/>
|
|
294
|
-
</Stack>
|
|
295
|
-
),
|
|
296
|
-
},
|
|
297
|
-
];
|
|
298
|
-
|
|
299
|
-
return (
|
|
300
|
-
<Box sx={{ maxWidth: 500 }}>
|
|
301
|
-
<Typography level="h3" sx={{ mb: 2 }}>
|
|
302
|
-
Settings
|
|
303
|
-
</Typography>
|
|
304
|
-
<Accordions items={settingsItems} variant="soft" color="neutral" />
|
|
305
|
-
</Box>
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### Product Details
|
|
311
|
-
|
|
312
|
-
```tsx
|
|
313
|
-
function ProductAccordion({ product }) {
|
|
314
|
-
const productDetails = [
|
|
315
|
-
{
|
|
316
|
-
summary: 'Description',
|
|
317
|
-
details: (
|
|
318
|
-
<Typography>{product.description}</Typography>
|
|
319
|
-
),
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
summary: 'Specifications',
|
|
323
|
-
details: (
|
|
324
|
-
<Table>
|
|
325
|
-
<tbody>
|
|
326
|
-
{Object.entries(product.specs).map(([key, value]) => (
|
|
327
|
-
<tr key={key}>
|
|
328
|
-
<td>
|
|
329
|
-
<Typography level="body-sm" fontWeight="md">
|
|
330
|
-
{key}
|
|
331
|
-
</Typography>
|
|
332
|
-
</td>
|
|
333
|
-
<td>
|
|
334
|
-
<Typography level="body-sm">{value}</Typography>
|
|
335
|
-
</td>
|
|
336
|
-
</tr>
|
|
337
|
-
))}
|
|
338
|
-
</tbody>
|
|
339
|
-
</Table>
|
|
340
|
-
),
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
summary: 'Shipping & Returns',
|
|
344
|
-
details: (
|
|
345
|
-
<Stack gap={2}>
|
|
346
|
-
<Box>
|
|
347
|
-
<Typography level="title-sm">Shipping</Typography>
|
|
348
|
-
<Typography level="body-sm">
|
|
349
|
-
Free shipping on orders over $50. Standard delivery 3-5 days.
|
|
350
|
-
</Typography>
|
|
351
|
-
</Box>
|
|
352
|
-
<Box>
|
|
353
|
-
<Typography level="title-sm">Returns</Typography>
|
|
354
|
-
<Typography level="body-sm">
|
|
355
|
-
30-day return policy. Items must be unused with original tags.
|
|
356
|
-
</Typography>
|
|
357
|
-
</Box>
|
|
358
|
-
</Stack>
|
|
359
|
-
),
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
summary: `Reviews (${product.reviewCount})`,
|
|
363
|
-
details: (
|
|
364
|
-
<Stack gap={2}>
|
|
365
|
-
{product.reviews.slice(0, 3).map((review) => (
|
|
366
|
-
<Card key={review.id} variant="outlined">
|
|
367
|
-
<CardContent>
|
|
368
|
-
<Stack direction="row" justifyContent="space-between">
|
|
369
|
-
<Typography level="title-sm">{review.author}</Typography>
|
|
370
|
-
<Typography level="body-xs" color="neutral">
|
|
371
|
-
{review.date}
|
|
372
|
-
</Typography>
|
|
373
|
-
</Stack>
|
|
374
|
-
<Typography level="body-sm">{review.content}</Typography>
|
|
375
|
-
</CardContent>
|
|
376
|
-
</Card>
|
|
377
|
-
))}
|
|
378
|
-
<Button variant="plain" size="sm">
|
|
379
|
-
View all reviews
|
|
380
|
-
</Button>
|
|
216
|
+
<Checkbox label="Email notifications" />
|
|
217
|
+
<Checkbox label="Push notifications" />
|
|
218
|
+
<Checkbox label="Weekly digest" />
|
|
381
219
|
</Stack>
|
|
382
220
|
),
|
|
383
221
|
},
|
|
384
222
|
];
|
|
385
223
|
|
|
386
|
-
return <Accordions items={
|
|
224
|
+
return <Accordions items={settingsItems} variant="soft" color="neutral" />;
|
|
387
225
|
}
|
|
388
226
|
```
|
|
389
227
|
|
|
390
228
|
### Navigation Menu
|
|
391
229
|
|
|
392
230
|
```tsx
|
|
393
|
-
|
|
231
|
+
import { Accordions, List, ListItem, ListItemButton, Box } from '@ceed/cds';
|
|
232
|
+
|
|
233
|
+
function SidebarMenu() {
|
|
394
234
|
const menuItems = [
|
|
395
235
|
{
|
|
396
236
|
summary: 'Products',
|
|
397
237
|
details: (
|
|
398
238
|
<List size="sm">
|
|
399
|
-
<ListItem>
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
</ListItemButton>
|
|
403
|
-
</ListItem>
|
|
404
|
-
<ListItem>
|
|
405
|
-
<ListItemButton onClick={() => navigate('/products/clothing')}>
|
|
406
|
-
Clothing
|
|
407
|
-
</ListItemButton>
|
|
408
|
-
</ListItem>
|
|
409
|
-
<ListItem>
|
|
410
|
-
<ListItemButton onClick={() => navigate('/products/home')}>
|
|
411
|
-
Home & Garden
|
|
412
|
-
</ListItemButton>
|
|
413
|
-
</ListItem>
|
|
414
|
-
</List>
|
|
415
|
-
),
|
|
416
|
-
},
|
|
417
|
-
{
|
|
418
|
-
summary: 'Services',
|
|
419
|
-
details: (
|
|
420
|
-
<List size="sm">
|
|
421
|
-
<ListItem>
|
|
422
|
-
<ListItemButton onClick={() => navigate('/services/consulting')}>
|
|
423
|
-
Consulting
|
|
424
|
-
</ListItemButton>
|
|
425
|
-
</ListItem>
|
|
426
|
-
<ListItem>
|
|
427
|
-
<ListItemButton onClick={() => navigate('/services/support')}>
|
|
428
|
-
Support
|
|
429
|
-
</ListItemButton>
|
|
430
|
-
</ListItem>
|
|
239
|
+
<ListItem><ListItemButton>Electronics</ListItemButton></ListItem>
|
|
240
|
+
<ListItem><ListItemButton>Clothing</ListItemButton></ListItem>
|
|
241
|
+
<ListItem><ListItemButton>Home & Garden</ListItemButton></ListItem>
|
|
431
242
|
</List>
|
|
432
243
|
),
|
|
433
244
|
},
|
|
@@ -435,21 +246,8 @@ function NavigationAccordion() {
|
|
|
435
246
|
summary: 'Resources',
|
|
436
247
|
details: (
|
|
437
248
|
<List size="sm">
|
|
438
|
-
<ListItem>
|
|
439
|
-
|
|
440
|
-
Documentation
|
|
441
|
-
</ListItemButton>
|
|
442
|
-
</ListItem>
|
|
443
|
-
<ListItem>
|
|
444
|
-
<ListItemButton onClick={() => navigate('/blog')}>
|
|
445
|
-
Blog
|
|
446
|
-
</ListItemButton>
|
|
447
|
-
</ListItem>
|
|
448
|
-
<ListItem>
|
|
449
|
-
<ListItemButton onClick={() => navigate('/tutorials')}>
|
|
450
|
-
Tutorials
|
|
451
|
-
</ListItemButton>
|
|
452
|
-
</ListItem>
|
|
249
|
+
<ListItem><ListItemButton>Documentation</ListItemButton></ListItem>
|
|
250
|
+
<ListItem><ListItemButton>Blog</ListItemButton></ListItem>
|
|
453
251
|
</List>
|
|
454
252
|
),
|
|
455
253
|
},
|
|
@@ -463,578 +261,25 @@ function NavigationAccordion() {
|
|
|
463
261
|
}
|
|
464
262
|
```
|
|
465
263
|
|
|
466
|
-
### Help Documentation
|
|
467
|
-
|
|
468
|
-
```tsx
|
|
469
|
-
function HelpAccordion() {
|
|
470
|
-
const helpTopics = [
|
|
471
|
-
{
|
|
472
|
-
summary: 'Getting Started',
|
|
473
|
-
details: (
|
|
474
|
-
<Stack gap={2}>
|
|
475
|
-
<Typography level="title-sm">Step 1: Create an Account</Typography>
|
|
476
|
-
<Typography level="body-sm">
|
|
477
|
-
Click the "Sign Up" button and fill in your details.
|
|
478
|
-
</Typography>
|
|
479
|
-
<Typography level="title-sm">Step 2: Verify Email</Typography>
|
|
480
|
-
<Typography level="body-sm">
|
|
481
|
-
Check your inbox for a verification email and click the link.
|
|
482
|
-
</Typography>
|
|
483
|
-
<Typography level="title-sm">Step 3: Complete Profile</Typography>
|
|
484
|
-
<Typography level="body-sm">
|
|
485
|
-
Add your profile picture and bio to personalize your account.
|
|
486
|
-
</Typography>
|
|
487
|
-
</Stack>
|
|
488
|
-
),
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
summary: 'Troubleshooting',
|
|
492
|
-
details: (
|
|
493
|
-
<Stack gap={2}>
|
|
494
|
-
<Box>
|
|
495
|
-
<Typography level="title-sm" color="danger">
|
|
496
|
-
Can't log in?
|
|
497
|
-
</Typography>
|
|
498
|
-
<Typography level="body-sm">
|
|
499
|
-
Try resetting your password or clearing your browser cache.
|
|
500
|
-
</Typography>
|
|
501
|
-
</Box>
|
|
502
|
-
<Box>
|
|
503
|
-
<Typography level="title-sm" color="danger">
|
|
504
|
-
Page not loading?
|
|
505
|
-
</Typography>
|
|
506
|
-
<Typography level="body-sm">
|
|
507
|
-
Check your internet connection and try refreshing the page.
|
|
508
|
-
</Typography>
|
|
509
|
-
</Box>
|
|
510
|
-
</Stack>
|
|
511
|
-
),
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
summary: 'Contact Support',
|
|
515
|
-
details: (
|
|
516
|
-
<Stack gap={2}>
|
|
517
|
-
<Typography level="body-sm">
|
|
518
|
-
Need more help? Reach out to our support team:
|
|
519
|
-
</Typography>
|
|
520
|
-
<Stack gap={1}>
|
|
521
|
-
<Typography level="body-sm">
|
|
522
|
-
Email: support@example.com
|
|
523
|
-
</Typography>
|
|
524
|
-
<Typography level="body-sm">
|
|
525
|
-
Live Chat: Available 9am-5pm EST
|
|
526
|
-
</Typography>
|
|
527
|
-
<Typography level="body-sm">
|
|
528
|
-
Phone: 1-800-EXAMPLE
|
|
529
|
-
</Typography>
|
|
530
|
-
</Stack>
|
|
531
|
-
</Stack>
|
|
532
|
-
),
|
|
533
|
-
},
|
|
534
|
-
];
|
|
535
|
-
|
|
536
|
-
return (
|
|
537
|
-
<Box sx={{ maxWidth: 600 }}>
|
|
538
|
-
<Typography level="h2" sx={{ mb: 2 }}>
|
|
539
|
-
Help Center
|
|
540
|
-
</Typography>
|
|
541
|
-
<Accordions items={helpTopics} variant="outlined" color="primary" />
|
|
542
|
-
</Box>
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
### Collapsible Form Sections
|
|
548
|
-
|
|
549
|
-
```tsx
|
|
550
|
-
function MultiSectionForm({ onSubmit }) {
|
|
551
|
-
const [formData, setFormData] = useState({
|
|
552
|
-
personal: { firstName: '', lastName: '', email: '' },
|
|
553
|
-
address: { street: '', city: '', zipCode: '' },
|
|
554
|
-
payment: { cardNumber: '', expiry: '', cvv: '' },
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
const updateField = (section, field, value) => {
|
|
558
|
-
setFormData((prev) => ({
|
|
559
|
-
...prev,
|
|
560
|
-
[section]: { ...prev[section], [field]: value },
|
|
561
|
-
}));
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
const formSections = [
|
|
565
|
-
{
|
|
566
|
-
summary: 'Personal Information',
|
|
567
|
-
details: (
|
|
568
|
-
<Stack gap={2}>
|
|
569
|
-
<Stack direction="row" gap={2}>
|
|
570
|
-
<FormControl sx={{ flex: 1 }}>
|
|
571
|
-
<FormLabel>First Name</FormLabel>
|
|
572
|
-
<Input
|
|
573
|
-
value={formData.personal.firstName}
|
|
574
|
-
onChange={(e) =>
|
|
575
|
-
updateField('personal', 'firstName', e.target.value)
|
|
576
|
-
}
|
|
577
|
-
/>
|
|
578
|
-
</FormControl>
|
|
579
|
-
<FormControl sx={{ flex: 1 }}>
|
|
580
|
-
<FormLabel>Last Name</FormLabel>
|
|
581
|
-
<Input
|
|
582
|
-
value={formData.personal.lastName}
|
|
583
|
-
onChange={(e) =>
|
|
584
|
-
updateField('personal', 'lastName', e.target.value)
|
|
585
|
-
}
|
|
586
|
-
/>
|
|
587
|
-
</FormControl>
|
|
588
|
-
</Stack>
|
|
589
|
-
<FormControl>
|
|
590
|
-
<FormLabel>Email</FormLabel>
|
|
591
|
-
<Input
|
|
592
|
-
type="email"
|
|
593
|
-
value={formData.personal.email}
|
|
594
|
-
onChange={(e) =>
|
|
595
|
-
updateField('personal', 'email', e.target.value)
|
|
596
|
-
}
|
|
597
|
-
/>
|
|
598
|
-
</FormControl>
|
|
599
|
-
</Stack>
|
|
600
|
-
),
|
|
601
|
-
},
|
|
602
|
-
{
|
|
603
|
-
summary: 'Shipping Address',
|
|
604
|
-
details: (
|
|
605
|
-
<Stack gap={2}>
|
|
606
|
-
<FormControl>
|
|
607
|
-
<FormLabel>Street Address</FormLabel>
|
|
608
|
-
<Input
|
|
609
|
-
value={formData.address.street}
|
|
610
|
-
onChange={(e) =>
|
|
611
|
-
updateField('address', 'street', e.target.value)
|
|
612
|
-
}
|
|
613
|
-
/>
|
|
614
|
-
</FormControl>
|
|
615
|
-
<Stack direction="row" gap={2}>
|
|
616
|
-
<FormControl sx={{ flex: 2 }}>
|
|
617
|
-
<FormLabel>City</FormLabel>
|
|
618
|
-
<Input
|
|
619
|
-
value={formData.address.city}
|
|
620
|
-
onChange={(e) =>
|
|
621
|
-
updateField('address', 'city', e.target.value)
|
|
622
|
-
}
|
|
623
|
-
/>
|
|
624
|
-
</FormControl>
|
|
625
|
-
<FormControl sx={{ flex: 1 }}>
|
|
626
|
-
<FormLabel>ZIP Code</FormLabel>
|
|
627
|
-
<Input
|
|
628
|
-
value={formData.address.zipCode}
|
|
629
|
-
onChange={(e) =>
|
|
630
|
-
updateField('address', 'zipCode', e.target.value)
|
|
631
|
-
}
|
|
632
|
-
/>
|
|
633
|
-
</FormControl>
|
|
634
|
-
</Stack>
|
|
635
|
-
</Stack>
|
|
636
|
-
),
|
|
637
|
-
},
|
|
638
|
-
{
|
|
639
|
-
summary: 'Payment Details',
|
|
640
|
-
details: (
|
|
641
|
-
<Stack gap={2}>
|
|
642
|
-
<FormControl>
|
|
643
|
-
<FormLabel>Card Number</FormLabel>
|
|
644
|
-
<Input
|
|
645
|
-
placeholder="1234 5678 9012 3456"
|
|
646
|
-
value={formData.payment.cardNumber}
|
|
647
|
-
onChange={(e) =>
|
|
648
|
-
updateField('payment', 'cardNumber', e.target.value)
|
|
649
|
-
}
|
|
650
|
-
/>
|
|
651
|
-
</FormControl>
|
|
652
|
-
<Stack direction="row" gap={2}>
|
|
653
|
-
<FormControl sx={{ flex: 1 }}>
|
|
654
|
-
<FormLabel>Expiry Date</FormLabel>
|
|
655
|
-
<Input
|
|
656
|
-
placeholder="MM/YY"
|
|
657
|
-
value={formData.payment.expiry}
|
|
658
|
-
onChange={(e) =>
|
|
659
|
-
updateField('payment', 'expiry', e.target.value)
|
|
660
|
-
}
|
|
661
|
-
/>
|
|
662
|
-
</FormControl>
|
|
663
|
-
<FormControl sx={{ flex: 1 }}>
|
|
664
|
-
<FormLabel>CVV</FormLabel>
|
|
665
|
-
<Input
|
|
666
|
-
type="password"
|
|
667
|
-
placeholder="123"
|
|
668
|
-
value={formData.payment.cvv}
|
|
669
|
-
onChange={(e) =>
|
|
670
|
-
updateField('payment', 'cvv', e.target.value)
|
|
671
|
-
}
|
|
672
|
-
/>
|
|
673
|
-
</FormControl>
|
|
674
|
-
</Stack>
|
|
675
|
-
</Stack>
|
|
676
|
-
),
|
|
677
|
-
},
|
|
678
|
-
];
|
|
679
|
-
|
|
680
|
-
return (
|
|
681
|
-
<Box sx={{ maxWidth: 500 }}>
|
|
682
|
-
<Accordions items={formSections} variant="outlined" />
|
|
683
|
-
<Button sx={{ mt: 2 }} fullWidth onClick={() => onSubmit(formData)}>
|
|
684
|
-
Submit Order
|
|
685
|
-
</Button>
|
|
686
|
-
</Box>
|
|
687
|
-
);
|
|
688
|
-
}
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
## Props and Customization
|
|
692
|
-
|
|
693
|
-
### Key Props
|
|
694
|
-
|
|
695
|
-
| Prop | Type | Default | Description |
|
|
696
|
-
| ---------------- | -------------------------------------------------------------- | ----------- | ------------------------------------------------- |
|
|
697
|
-
| `items` | `Array<{ summary: string; details: ReactNode }>` | - | Array of accordion items with headers and content |
|
|
698
|
-
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the accordion |
|
|
699
|
-
| `variant` | `'plain' \| 'outlined' \| 'soft' \| 'solid'` | `'plain'` | Visual style variant |
|
|
700
|
-
| `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | `'neutral'` | Color theme |
|
|
701
|
-
| `disableDivider` | `boolean` | `false` | Remove divider lines between items |
|
|
702
|
-
|
|
703
|
-
### Items Structure
|
|
704
|
-
|
|
705
|
-
```tsx
|
|
706
|
-
interface AccordionItem {
|
|
707
|
-
summary: string; // Header text displayed when collapsed
|
|
708
|
-
details: ReactNode; // Content shown when expanded
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// Example items array
|
|
712
|
-
const items = [
|
|
713
|
-
{
|
|
714
|
-
summary: 'Section Title',
|
|
715
|
-
details: <Typography>Section content...</Typography>,
|
|
716
|
-
},
|
|
717
|
-
{
|
|
718
|
-
summary: 'Another Section',
|
|
719
|
-
details: (
|
|
720
|
-
<Stack gap={2}>
|
|
721
|
-
<Typography>Multiple elements...</Typography>
|
|
722
|
-
<Button>Action</Button>
|
|
723
|
-
</Stack>
|
|
724
|
-
),
|
|
725
|
-
},
|
|
726
|
-
];
|
|
727
|
-
```
|
|
728
|
-
|
|
729
|
-
### Variant Options
|
|
730
|
-
|
|
731
|
-
```tsx
|
|
732
|
-
// Plain - minimal styling, no background
|
|
733
|
-
<Accordions items={items} variant="plain" />
|
|
734
|
-
|
|
735
|
-
// Outlined - bordered container
|
|
736
|
-
<Accordions items={items} variant="outlined" />
|
|
737
|
-
|
|
738
|
-
// Soft - subtle background color
|
|
739
|
-
<Accordions items={items} variant="soft" />
|
|
740
|
-
|
|
741
|
-
// Solid - full background color
|
|
742
|
-
<Accordions items={items} variant="solid" />
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
### Size Options
|
|
746
|
-
|
|
747
|
-
```tsx
|
|
748
|
-
// Small - compact for sidebars/menus
|
|
749
|
-
<Accordions items={items} size="sm" />
|
|
750
|
-
|
|
751
|
-
// Medium - default size
|
|
752
|
-
<Accordions items={items} size="md" />
|
|
753
|
-
|
|
754
|
-
// Large - spacious for main content
|
|
755
|
-
<Accordions items={items} size="lg" />
|
|
756
|
-
```
|
|
757
|
-
|
|
758
|
-
### Color Options
|
|
759
|
-
|
|
760
|
-
```tsx
|
|
761
|
-
// Primary - brand color emphasis
|
|
762
|
-
<Accordions items={items} color="primary" />
|
|
763
|
-
|
|
764
|
-
// Neutral - subtle, default
|
|
765
|
-
<Accordions items={items} color="neutral" />
|
|
766
|
-
|
|
767
|
-
// Status colors
|
|
768
|
-
<Accordions items={items} color="success" />
|
|
769
|
-
<Accordions items={items} color="warning" />
|
|
770
|
-
<Accordions items={items} color="danger" />
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
### Without Dividers
|
|
774
|
-
|
|
775
|
-
```tsx
|
|
776
|
-
// Clean look without divider lines
|
|
777
|
-
<Accordions items={items} disableDivider />
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
## Accessibility
|
|
781
|
-
|
|
782
|
-
Accordions includes built-in accessibility features:
|
|
783
|
-
|
|
784
|
-
### ARIA Attributes
|
|
785
|
-
|
|
786
|
-
- Accordion headers have `role="button"` with `aria-expanded`
|
|
787
|
-
- Content regions have `role="region"` with `aria-labelledby`
|
|
788
|
-
- Proper focus management between sections
|
|
789
|
-
|
|
790
|
-
### Keyboard Navigation
|
|
791
|
-
|
|
792
|
-
- **Tab**: Move focus between accordion headers
|
|
793
|
-
- **Enter/Space**: Expand or collapse focused accordion
|
|
794
|
-
- **Arrow Down**: Move focus to next accordion header
|
|
795
|
-
- **Arrow Up**: Move focus to previous accordion header
|
|
796
|
-
- **Home**: Move focus to first accordion header
|
|
797
|
-
- **End**: Move focus to last accordion header
|
|
798
|
-
|
|
799
|
-
### Screen Reader Support
|
|
800
|
-
|
|
801
|
-
```tsx
|
|
802
|
-
// Headers announce: "Section Title, collapsed/expanded, button"
|
|
803
|
-
<Accordions
|
|
804
|
-
items={[
|
|
805
|
-
{
|
|
806
|
-
summary: 'Shipping Information', // Announced as header
|
|
807
|
-
details: <Typography>Content...</Typography>,
|
|
808
|
-
},
|
|
809
|
-
]}
|
|
810
|
-
/>
|
|
811
|
-
```
|
|
812
|
-
|
|
813
|
-
### Focus Visibility
|
|
814
|
-
|
|
815
|
-
- Clear focus indicators on headers
|
|
816
|
-
- Focus remains on header after expand/collapse
|
|
817
|
-
- Tab navigation works within expanded content
|
|
818
|
-
|
|
819
264
|
## Best Practices
|
|
820
265
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
```tsx
|
|
826
|
-
// ✅ Good: Descriptive headers
|
|
827
|
-
<Accordions
|
|
828
|
-
items={[
|
|
829
|
-
{ summary: 'Return Policy (30 days)', details: '...' },
|
|
830
|
-
{ summary: 'Shipping Options & Costs', details: '...' },
|
|
831
|
-
]}
|
|
832
|
-
/>
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
2. **Group related content**: Keep sections logically organized
|
|
836
|
-
|
|
837
|
-
```tsx
|
|
838
|
-
// ✅ Good: Logical grouping
|
|
839
|
-
<Accordions
|
|
840
|
-
items={[
|
|
841
|
-
{ summary: 'Account Settings', details: accountSettings },
|
|
842
|
-
{ summary: 'Notification Settings', details: notificationSettings },
|
|
843
|
-
{ summary: 'Privacy Settings', details: privacySettings },
|
|
844
|
-
]}
|
|
845
|
-
/>
|
|
846
|
-
```
|
|
847
|
-
|
|
848
|
-
3. **Keep content scannable**: Use lists and headings inside accordions
|
|
849
|
-
|
|
850
|
-
```tsx
|
|
851
|
-
// ✅ Good: Scannable content
|
|
852
|
-
<Accordions
|
|
853
|
-
items={[
|
|
854
|
-
{
|
|
855
|
-
summary: 'Features',
|
|
856
|
-
details: (
|
|
857
|
-
<ul>
|
|
858
|
-
<li>Feature one</li>
|
|
859
|
-
<li>Feature two</li>
|
|
860
|
-
<li>Feature three</li>
|
|
861
|
-
</ul>
|
|
862
|
-
),
|
|
863
|
-
},
|
|
864
|
-
]}
|
|
865
|
-
/>
|
|
866
|
-
```
|
|
867
|
-
|
|
868
|
-
4. **Match variant to context**: Use appropriate styling
|
|
869
|
-
|
|
870
|
-
```tsx
|
|
871
|
-
// ✅ Good: Outlined for forms, plain for navigation
|
|
872
|
-
<Accordions items={formSections} variant="outlined" />
|
|
873
|
-
<Accordions items={menuItems} variant="plain" size="sm" />
|
|
874
|
-
```
|
|
875
|
-
|
|
876
|
-
### ❌ Don't
|
|
877
|
-
|
|
878
|
-
1. **Don't hide critical information**: Important content should be visible
|
|
879
|
-
|
|
880
|
-
```tsx
|
|
881
|
-
// ❌ Bad: Hiding important warnings
|
|
882
|
-
<Accordions
|
|
883
|
-
items={[
|
|
884
|
-
{ summary: 'Important Safety Information', details: criticalWarning },
|
|
885
|
-
]}
|
|
886
|
-
/>
|
|
887
|
-
|
|
888
|
-
// ✅ Good: Show warnings prominently
|
|
889
|
-
<Alert color="warning">{criticalWarning}</Alert>
|
|
890
|
-
```
|
|
891
|
-
|
|
892
|
-
2. **Don't nest accordions**: Avoid complex hierarchies
|
|
893
|
-
|
|
894
|
-
```tsx
|
|
895
|
-
// ❌ Bad: Nested accordions
|
|
896
|
-
<Accordions
|
|
897
|
-
items={[
|
|
898
|
-
{
|
|
899
|
-
summary: 'Parent',
|
|
900
|
-
details: (
|
|
901
|
-
<Accordions items={[{ summary: 'Child', details: '...' }]} />
|
|
902
|
-
),
|
|
903
|
-
},
|
|
904
|
-
]}
|
|
905
|
-
/>
|
|
906
|
-
|
|
907
|
-
// ✅ Good: Flat structure or use alternative
|
|
908
|
-
<Accordions items={flattenedItems} />
|
|
909
|
-
```
|
|
266
|
+
- **Use descriptive headers.** Users should understand what content a section contains without needing to expand it.
|
|
267
|
+
- ✔ `"Shipping Options & Estimated Costs"`
|
|
268
|
+
- ✘ `"Section 3"`
|
|
910
269
|
|
|
911
|
-
|
|
270
|
+
- **Keep the number of items manageable.** Aim for 3 to 7 items per accordion group. If you have more, consider grouping them into categories or adding a search filter.
|
|
912
271
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
272
|
+
- **Do not hide critical information.** Important warnings, legal notices, or mandatory instructions should be displayed prominently rather than tucked inside an accordion.
|
|
273
|
+
- ✔ Show safety warnings with an Alert component
|
|
274
|
+
- ✘ Hide safety warnings inside an accordion
|
|
916
275
|
|
|
917
|
-
|
|
918
|
-
<Card>
|
|
919
|
-
<CardContent>{content}</CardContent>
|
|
920
|
-
</Card>
|
|
921
|
-
```
|
|
276
|
+
- **Avoid nesting accordions within accordions.** Deeply nested structures are confusing and difficult to navigate. Flatten the hierarchy or use a different pattern such as tabs.
|
|
922
277
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
```tsx
|
|
926
|
-
// ❌ Bad: Too many accordion items (15+)
|
|
927
|
-
<Accordions items={arrayOf15Items} />
|
|
928
|
-
|
|
929
|
-
// ✅ Good: Group or paginate if needed
|
|
930
|
-
<Accordions items={arrayOf5To7Items} />
|
|
931
|
-
```
|
|
278
|
+
- **Match the variant to the context.** Use `outlined` for form sections, `plain` for navigation menus, and `soft` for informational panels.
|
|
932
279
|
|
|
933
|
-
##
|
|
934
|
-
|
|
935
|
-
### Lazy Load Heavy Content
|
|
936
|
-
|
|
937
|
-
For accordions with heavy content, render only when expanded:
|
|
938
|
-
|
|
939
|
-
```tsx
|
|
940
|
-
function LazyAccordionContent({ isExpanded, children }) {
|
|
941
|
-
const [hasBeenExpanded, setHasBeenExpanded] = useState(false);
|
|
942
|
-
|
|
943
|
-
useEffect(() => {
|
|
944
|
-
if (isExpanded) setHasBeenExpanded(true);
|
|
945
|
-
}, [isExpanded]);
|
|
946
|
-
|
|
947
|
-
// Render placeholder until first expansion
|
|
948
|
-
if (!hasBeenExpanded) {
|
|
949
|
-
return <Typography color="neutral">Loading...</Typography>;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
return children;
|
|
953
|
-
}
|
|
954
|
-
```
|
|
955
|
-
|
|
956
|
-
### Memoize Items Array
|
|
957
|
-
|
|
958
|
-
Prevent unnecessary re-renders by memoizing items:
|
|
959
|
-
|
|
960
|
-
```tsx
|
|
961
|
-
const accordionItems = useMemo(
|
|
962
|
-
() => [
|
|
963
|
-
{
|
|
964
|
-
summary: 'Section 1',
|
|
965
|
-
details: <ExpensiveComponent data={data1} />,
|
|
966
|
-
},
|
|
967
|
-
{
|
|
968
|
-
summary: 'Section 2',
|
|
969
|
-
details: <ExpensiveComponent data={data2} />,
|
|
970
|
-
},
|
|
971
|
-
],
|
|
972
|
-
[data1, data2]
|
|
973
|
-
);
|
|
974
|
-
|
|
975
|
-
<Accordions items={accordionItems} />
|
|
976
|
-
```
|
|
977
|
-
|
|
978
|
-
### Avoid Inline Functions in Items
|
|
979
|
-
|
|
980
|
-
```tsx
|
|
981
|
-
// ❌ Bad: Creates new objects on every render
|
|
982
|
-
<Accordions
|
|
983
|
-
items={data.map((item) => ({
|
|
984
|
-
summary: item.title,
|
|
985
|
-
details: <Content data={item} />,
|
|
986
|
-
}))}
|
|
987
|
-
/>
|
|
988
|
-
|
|
989
|
-
// ✅ Good: Memoized transformation
|
|
990
|
-
const items = useMemo(
|
|
991
|
-
() =>
|
|
992
|
-
data.map((item) => ({
|
|
993
|
-
summary: item.title,
|
|
994
|
-
details: <Content data={item} />,
|
|
995
|
-
})),
|
|
996
|
-
[data]
|
|
997
|
-
);
|
|
998
|
-
<Accordions items={items} />
|
|
999
|
-
```
|
|
1000
|
-
|
|
1001
|
-
### Virtualize Long Lists
|
|
1002
|
-
|
|
1003
|
-
For many accordion items, consider virtualization:
|
|
1004
|
-
|
|
1005
|
-
```tsx
|
|
1006
|
-
// For very long lists (20+ items), consider:
|
|
1007
|
-
// 1. Pagination with limited items per page
|
|
1008
|
-
// 2. Search/filter to reduce visible items
|
|
1009
|
-
// 3. Grouping into categories
|
|
1010
|
-
|
|
1011
|
-
function FilteredAccordions({ allItems }) {
|
|
1012
|
-
const [search, setSearch] = useState('');
|
|
1013
|
-
|
|
1014
|
-
const filteredItems = useMemo(
|
|
1015
|
-
() =>
|
|
1016
|
-
allItems.filter((item) =>
|
|
1017
|
-
item.summary.toLowerCase().includes(search.toLowerCase())
|
|
1018
|
-
),
|
|
1019
|
-
[allItems, search]
|
|
1020
|
-
);
|
|
1021
|
-
|
|
1022
|
-
return (
|
|
1023
|
-
<Stack gap={2}>
|
|
1024
|
-
<Input
|
|
1025
|
-
placeholder="Search..."
|
|
1026
|
-
value={search}
|
|
1027
|
-
onChange={(e) => setSearch(e.target.value)}
|
|
1028
|
-
/>
|
|
1029
|
-
<Accordions items={filteredItems.slice(0, 10)} />
|
|
1030
|
-
{filteredItems.length > 10 && (
|
|
1031
|
-
<Typography level="body-sm" color="neutral">
|
|
1032
|
-
Showing 10 of {filteredItems.length} results
|
|
1033
|
-
</Typography>
|
|
1034
|
-
)}
|
|
1035
|
-
</Stack>
|
|
1036
|
-
);
|
|
1037
|
-
}
|
|
1038
|
-
```
|
|
280
|
+
## Accessibility
|
|
1039
281
|
|
|
1040
|
-
|
|
282
|
+
- Accordion headers use `role="button"` with `aria-expanded` to indicate their current state to screen readers.
|
|
283
|
+
- Content regions use `role="region"` with `aria-labelledby` to associate them with their headers.
|
|
284
|
+
- Keyboard navigation is fully supported: **Tab** moves focus between headers, **Enter/Space** toggles expand/collapse, **Arrow Up/Down** moves between headers, and **Home/End** jumps to the first or last header.
|
|
285
|
+
- Focus indicators are clearly visible on headers, and focus remains on the header after toggling to avoid disorienting the user.
|