@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,671 @@
1
+ # Checkbox
2
+
3
+ **Purpose:** Binary selection control for toggling options on/off, supporting single checkboxes and checkbox groups.
4
+
5
+ ## When to Use This Component
6
+
7
+ Use Checkbox when you need **binary selections** that are part of a form or when users can select multiple independent options.
8
+
9
+ **Decision Tree:**
10
+
11
+ | Scenario | Use This | Why |
12
+ | --------------------------------------------------------------- | ----------- | ----------------------------------------- |
13
+ | Form consent (accept terms, subscribe to newsletter) | Checkbox ✅ | Standard for form agreements |
14
+ | Multiple selections from a list (select interests, features) | Checkbox ✅ | Users can pick multiple items |
15
+ | Single binary choice in a form | Checkbox ✅ | Form submission context |
16
+ | Toggle setting with immediate effect (dark mode, notifications) | Switch | Visual metaphor for instant state changes |
17
+ | Mutually exclusive choices (select one from many) | RadioGroup | Only one option can be selected |
18
+ | Action trigger (save, delete) | Button | Explicit action with confirmation |
19
+
20
+ **Component Comparison:**
21
+
22
+ ```typescript
23
+ // ✅ Use Checkbox for form consent
24
+ <form onSubmit={handleSubmit}>
25
+ <Checkbox.Root name="terms">
26
+ <Checkbox.Control>
27
+ <Checkbox.Indicator />
28
+ </Checkbox.Control>
29
+ <Checkbox.Label>I accept the terms and conditions</Checkbox.Label>
30
+ </Checkbox.Root>
31
+ <Button type="submit">Sign Up</Button>
32
+ </form>
33
+
34
+ // ✅ Use Checkbox for multiple selections
35
+ <Checkbox.Group value={selectedInterests} onValueChange={setSelectedInterests}>
36
+ <Checkbox.Root value="sports">
37
+ <Checkbox.Control><Checkbox.Indicator /></Checkbox.Control>
38
+ <Checkbox.Label>Sports</Checkbox.Label>
39
+ </Checkbox.Root>
40
+ <Checkbox.Root value="music">
41
+ <Checkbox.Control><Checkbox.Indicator /></Checkbox.Control>
42
+ <Checkbox.Label>Music</Checkbox.Label>
43
+ </Checkbox.Root>
44
+ <Checkbox.Root value="travel">
45
+ <Checkbox.Control><Checkbox.Indicator /></Checkbox.Control>
46
+ <Checkbox.Label>Travel</Checkbox.Label>
47
+ </Checkbox.Root>
48
+ </Checkbox.Group>
49
+
50
+ // ❌ Don't use Checkbox for instant toggles - use Switch
51
+ <Checkbox.Root checked={isDarkMode} onCheckedChange={setDarkMode}>
52
+ <Checkbox.Control><Checkbox.Indicator /></Checkbox.Control>
53
+ <Checkbox.Label>Dark mode</Checkbox.Label>
54
+ </Checkbox.Root> // Wrong - settings that apply immediately should use Switch
55
+
56
+ <Switch.Root checked={isDarkMode} onCheckedChange={setDarkMode}>
57
+ <Switch.Label>Dark mode</Switch.Label>
58
+ <Switch.Control><Switch.Thumb /></Switch.Control>
59
+ </Switch.Root> // Correct
60
+
61
+ // ❌ Don't use Checkbox for exclusive choices - use RadioGroup
62
+ <Checkbox.Group>
63
+ <Checkbox.Root value="small">
64
+ <Checkbox.Control><Checkbox.Indicator /></Checkbox.Control>
65
+ <Checkbox.Label>Small</Checkbox.Label>
66
+ </Checkbox.Root>
67
+ <Checkbox.Root value="medium">
68
+ <Checkbox.Control><Checkbox.Indicator /></Checkbox.Control>
69
+ <Checkbox.Label>Medium</Checkbox.Label>
70
+ </Checkbox.Root>
71
+ <Checkbox.Root value="large">
72
+ <Checkbox.Control><Checkbox.Indicator /></Checkbox.Control>
73
+ <Checkbox.Label>Large</Checkbox.Label>
74
+ </Checkbox.Root>
75
+ </Checkbox.Group> // Wrong - users shouldn't select multiple sizes
76
+
77
+ <RadioGroup.Root>
78
+ <RadioGroup.Item value="small">
79
+ <RadioGroup.ItemControl />
80
+ <RadioGroup.ItemText>Small</RadioGroup.ItemText>
81
+ </RadioGroup.Item>
82
+ <RadioGroup.Item value="medium">
83
+ <RadioGroup.ItemControl />
84
+ <RadioGroup.ItemText>Medium</RadioGroup.ItemText>
85
+ </RadioGroup.Item>
86
+ <RadioGroup.Item value="large">
87
+ <RadioGroup.ItemControl />
88
+ <RadioGroup.ItemText>Large</RadioGroup.ItemText>
89
+ </RadioGroup.Item>
90
+ </RadioGroup.Root> // Correct
91
+ ```
92
+
93
+ ## Import
94
+
95
+ ```typescript
96
+ import * as Checkbox from '@discourser/design-system';
97
+ ```
98
+
99
+ ## Component Structure
100
+
101
+ Checkbox uses a compound component pattern with these parts:
102
+
103
+ - `Checkbox.Root` - Container for the checkbox
104
+ - `Checkbox.Control` - The visual checkbox element
105
+ - `Checkbox.Label` - Text label for the checkbox
106
+ - `Checkbox.Indicator` - Checkmark icon (built-in)
107
+ - `Checkbox.HiddenInput` - Hidden native input
108
+ - `Checkbox.Group` - Container for multiple checkboxes
109
+
110
+ ## Variants
111
+
112
+ | Variant | Visual Style | Usage | When to Use |
113
+ | --------- | ------------------------------------- | -------------------- | ------------------------ |
114
+ | `solid` | Filled background when checked | Primary checkboxes | Default, most use cases |
115
+ | `outline` | Border only, highlighted when checked | Secondary checkboxes | Forms with less emphasis |
116
+ | `surface` | Surface background | Alternative style | Cards, elevated surfaces |
117
+ | `subtle` | Subtle background | Low-emphasis options | Settings, preferences |
118
+ | `plain` | Minimal styling | Text-like checkboxes | Inline selections |
119
+
120
+ ### Visual Characteristics
121
+
122
+ - **solid**: Filled with primary color when checked, white checkmark
123
+ - **outline**: Border changes to primary color when checked, checkmark inside
124
+ - **surface**: Surface background with border
125
+ - **subtle**: Subtle gray background
126
+ - **plain**: Minimal styling, blends with text
127
+
128
+ ## Sizes
129
+
130
+ | Size | Box Size | Label Text | Usage |
131
+ | ---- | -------- | ------------- | ---------------------------- |
132
+ | `sm` | 18px | Small (14px) | Compact forms, dense layouts |
133
+ | `md` | 20px | Medium (16px) | Default, most use cases |
134
+ | `lg` | 22px | Large (18px) | Touch targets, mobile-first |
135
+
136
+ **Recommendation:** Use `md` for most cases. Use `lg` for mobile or touch-focused interfaces.
137
+
138
+ ## Props
139
+
140
+ ### Root Props
141
+
142
+ | Prop | Type | Default | Description |
143
+ | ----------------- | ---------------------------- | ------- | ----------------------------------- |
144
+ | `checked` | `boolean \| 'indeterminate'` | - | Controlled checked state |
145
+ | `defaultChecked` | `boolean` | `false` | Uncontrolled default checked state |
146
+ | `onCheckedChange` | `(details) => void` | - | Callback when checked state changes |
147
+ | `disabled` | `boolean` | `false` | Disable interaction |
148
+ | `invalid` | `boolean` | `false` | Mark as invalid |
149
+ | `required` | `boolean` | `false` | Mark as required |
150
+ | `name` | `string` | - | Form field name |
151
+ | `value` | `string` | - | Form field value |
152
+
153
+ ### Style Props
154
+
155
+ | Prop | Type | Default | Description |
156
+ | --------- | ---------------------------------------------------------- | --------- | -------------------- |
157
+ | `variant` | `'solid' \| 'outline' \| 'surface' \| 'subtle' \| 'plain'` | `'solid'` | Visual style variant |
158
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Checkbox size |
159
+
160
+ ## Examples
161
+
162
+ ### Basic Usage
163
+
164
+ ```typescript
165
+ import * as Checkbox from '@discourser/design-system';
166
+
167
+ // Single checkbox
168
+ <Checkbox.Root>
169
+ <Checkbox.HiddenInput />
170
+ <Checkbox.Control>
171
+ <Checkbox.Indicator />
172
+ </Checkbox.Control>
173
+ <Checkbox.Label>Accept terms and conditions</Checkbox.Label>
174
+ </Checkbox.Root>
175
+ ```
176
+
177
+ ### Controlled Checkbox
178
+
179
+ ```typescript
180
+ const [accepted, setAccepted] = useState(false);
181
+
182
+ <Checkbox.Root
183
+ checked={accepted}
184
+ onCheckedChange={(details) => setAccepted(details.checked)}
185
+ >
186
+ <Checkbox.HiddenInput />
187
+ <Checkbox.Control>
188
+ <Checkbox.Indicator />
189
+ </Checkbox.Control>
190
+ <Checkbox.Label>I agree to the terms</Checkbox.Label>
191
+ </Checkbox.Root>
192
+ ```
193
+
194
+ ### Different Variants
195
+
196
+ ```typescript
197
+ // Solid (default)
198
+ <Checkbox.Root variant="solid">
199
+ <Checkbox.HiddenInput />
200
+ <Checkbox.Control>
201
+ <Checkbox.Indicator />
202
+ </Checkbox.Control>
203
+ <Checkbox.Label>Solid checkbox</Checkbox.Label>
204
+ </Checkbox.Root>
205
+
206
+ // Outline
207
+ <Checkbox.Root variant="outline">
208
+ <Checkbox.HiddenInput />
209
+ <Checkbox.Control>
210
+ <Checkbox.Indicator />
211
+ </Checkbox.Control>
212
+ <Checkbox.Label>Outline checkbox</Checkbox.Label>
213
+ </Checkbox.Root>
214
+
215
+ // Subtle
216
+ <Checkbox.Root variant="subtle">
217
+ <Checkbox.HiddenInput />
218
+ <Checkbox.Control>
219
+ <Checkbox.Indicator />
220
+ </Checkbox.Control>
221
+ <Checkbox.Label>Subtle checkbox</Checkbox.Label>
222
+ </Checkbox.Root>
223
+ ```
224
+
225
+ ### Different Sizes
226
+
227
+ ```typescript
228
+ // Small
229
+ <Checkbox.Root size="sm">
230
+ <Checkbox.HiddenInput />
231
+ <Checkbox.Control>
232
+ <Checkbox.Indicator />
233
+ </Checkbox.Control>
234
+ <Checkbox.Label>Small checkbox</Checkbox.Label>
235
+ </Checkbox.Root>
236
+
237
+ // Medium (default)
238
+ <Checkbox.Root size="md">
239
+ <Checkbox.HiddenInput />
240
+ <Checkbox.Control>
241
+ <Checkbox.Indicator />
242
+ </Checkbox.Control>
243
+ <Checkbox.Label>Medium checkbox</Checkbox.Label>
244
+ </Checkbox.Root>
245
+
246
+ // Large
247
+ <Checkbox.Root size="lg">
248
+ <Checkbox.HiddenInput />
249
+ <Checkbox.Control>
250
+ <Checkbox.Indicator />
251
+ </Checkbox.Control>
252
+ <Checkbox.Label>Large checkbox</Checkbox.Label>
253
+ </Checkbox.Root>
254
+ ```
255
+
256
+ ### Indeterminate State
257
+
258
+ ```typescript
259
+ // "Select all" checkbox that shows indeterminate when some items selected
260
+ const [selectedItems, setSelectedItems] = useState<string[]>([]);
261
+ const allItems = ['item1', 'item2', 'item3'];
262
+ const allSelected = selectedItems.length === allItems.length;
263
+ const someSelected = selectedItems.length > 0 && !allSelected;
264
+
265
+ <Checkbox.Root
266
+ checked={someSelected ? 'indeterminate' : allSelected}
267
+ onCheckedChange={(details) => {
268
+ if (details.checked) {
269
+ setSelectedItems(allItems);
270
+ } else {
271
+ setSelectedItems([]);
272
+ }
273
+ }}
274
+ >
275
+ <Checkbox.HiddenInput />
276
+ <Checkbox.Control>
277
+ <Checkbox.Indicator />
278
+ </Checkbox.Control>
279
+ <Checkbox.Label>Select all</Checkbox.Label>
280
+ </Checkbox.Root>
281
+ ```
282
+
283
+ ### Checkbox Group
284
+
285
+ ```typescript
286
+ import * as Checkbox from '@discourser/design-system';
287
+
288
+ const [selectedFeatures, setSelectedFeatures] = useState<string[]>(['email']);
289
+
290
+ <Checkbox.Group
291
+ value={selectedFeatures}
292
+ onValueChange={(details) => setSelectedFeatures(details.value)}
293
+ >
294
+ <label>
295
+ <span>Notification Preferences</span>
296
+ </label>
297
+
298
+ <Checkbox.Root value="email">
299
+ <Checkbox.HiddenInput />
300
+ <Checkbox.Control>
301
+ <Checkbox.Indicator />
302
+ </Checkbox.Control>
303
+ <Checkbox.Label>Email notifications</Checkbox.Label>
304
+ </Checkbox.Root>
305
+
306
+ <Checkbox.Root value="push">
307
+ <Checkbox.HiddenInput />
308
+ <Checkbox.Control>
309
+ <Checkbox.Indicator />
310
+ </Checkbox.Control>
311
+ <Checkbox.Label>Push notifications</Checkbox.Label>
312
+ </Checkbox.Root>
313
+
314
+ <Checkbox.Root value="sms">
315
+ <Checkbox.HiddenInput />
316
+ <Checkbox.Control>
317
+ <Checkbox.Indicator />
318
+ </Checkbox.Control>
319
+ <Checkbox.Label>SMS notifications</Checkbox.Label>
320
+ </Checkbox.Root>
321
+ </Checkbox.Group>
322
+ ```
323
+
324
+ ### Disabled State
325
+
326
+ ```typescript
327
+ // Disabled unchecked
328
+ <Checkbox.Root disabled>
329
+ <Checkbox.HiddenInput />
330
+ <Checkbox.Control>
331
+ <Checkbox.Indicator />
332
+ </Checkbox.Control>
333
+ <Checkbox.Label>Disabled option</Checkbox.Label>
334
+ </Checkbox.Root>
335
+
336
+ // Disabled checked
337
+ <Checkbox.Root disabled checked>
338
+ <Checkbox.HiddenInput />
339
+ <Checkbox.Control>
340
+ <Checkbox.Indicator />
341
+ </Checkbox.Control>
342
+ <Checkbox.Label>Disabled checked option</Checkbox.Label>
343
+ </Checkbox.Root>
344
+ ```
345
+
346
+ ### Invalid State
347
+
348
+ ```typescript
349
+ const [accepted, setAccepted] = useState(false);
350
+ const [submitted, setSubmitted] = useState(false);
351
+ const invalid = submitted && !accepted;
352
+
353
+ <div>
354
+ <Checkbox.Root
355
+ checked={accepted}
356
+ invalid={invalid}
357
+ onCheckedChange={(details) => setAccepted(details.checked)}
358
+ >
359
+ <Checkbox.HiddenInput />
360
+ <Checkbox.Control>
361
+ <Checkbox.Indicator />
362
+ </Checkbox.Control>
363
+ <Checkbox.Label>I accept the terms and conditions *</Checkbox.Label>
364
+ </Checkbox.Root>
365
+
366
+ {invalid && (
367
+ <div className={css({ color: 'error', textStyle: 'sm', mt: '1' })}>
368
+ You must accept the terms to continue
369
+ </div>
370
+ )}
371
+
372
+ <Button onClick={() => setSubmitted(true)}>Submit</Button>
373
+ </div>
374
+ ```
375
+
376
+ ## Common Patterns
377
+
378
+ ### Terms and Conditions
379
+
380
+ ```typescript
381
+ const [agreed, setAgreed] = useState(false);
382
+
383
+ <div className={css({ display: 'flex', flexDirection: 'column', gap: 'md' })}>
384
+ <Checkbox.Root
385
+ checked={agreed}
386
+ onCheckedChange={(details) => setAgreed(details.checked)}
387
+ >
388
+ <Checkbox.HiddenInput />
389
+ <Checkbox.Control>
390
+ <Checkbox.Indicator />
391
+ </Checkbox.Control>
392
+ <Checkbox.Label>
393
+ I agree to the{' '}
394
+ <a href="/terms" className={css({ color: 'primary', textDecoration: 'underline' })}>
395
+ Terms of Service
396
+ </a>{' '}
397
+ and{' '}
398
+ <a href="/privacy" className={css({ color: 'primary', textDecoration: 'underline' })}>
399
+ Privacy Policy
400
+ </a>
401
+ </Checkbox.Label>
402
+ </Checkbox.Root>
403
+
404
+ <Button variant="filled" disabled={!agreed}>
405
+ Continue
406
+ </Button>
407
+ </div>
408
+ ```
409
+
410
+ ### Settings List
411
+
412
+ ```typescript
413
+ const [settings, setSettings] = useState({
414
+ emailNotifications: true,
415
+ pushNotifications: false,
416
+ darkMode: true,
417
+ autoSave: true,
418
+ });
419
+
420
+ <div className={css({ display: 'flex', flexDirection: 'column', gap: 'lg' })}>
421
+ <Heading size="md">Settings</Heading>
422
+
423
+ <Checkbox.Root
424
+ checked={settings.emailNotifications}
425
+ onCheckedChange={(details) =>
426
+ setSettings({ ...settings, emailNotifications: details.checked })
427
+ }
428
+ >
429
+ <Checkbox.HiddenInput />
430
+ <Checkbox.Control>
431
+ <Checkbox.Indicator />
432
+ </Checkbox.Control>
433
+ <Checkbox.Label>Email notifications</Checkbox.Label>
434
+ </Checkbox.Root>
435
+
436
+ <Checkbox.Root
437
+ checked={settings.pushNotifications}
438
+ onCheckedChange={(details) =>
439
+ setSettings({ ...settings, pushNotifications: details.checked })
440
+ }
441
+ >
442
+ <Checkbox.HiddenInput />
443
+ <Checkbox.Control>
444
+ <Checkbox.Indicator />
445
+ </Checkbox.Control>
446
+ <Checkbox.Label>Push notifications</Checkbox.Label>
447
+ </Checkbox.Root>
448
+
449
+ <Checkbox.Root
450
+ checked={settings.darkMode}
451
+ onCheckedChange={(details) =>
452
+ setSettings({ ...settings, darkMode: details.checked })
453
+ }
454
+ >
455
+ <Checkbox.HiddenInput />
456
+ <Checkbox.Control>
457
+ <Checkbox.Indicator />
458
+ </Checkbox.Control>
459
+ <Checkbox.Label>Dark mode</Checkbox.Label>
460
+ </Checkbox.Root>
461
+ </div>
462
+ ```
463
+
464
+ ### Filter List
465
+
466
+ ```typescript
467
+ const [selectedFilters, setSelectedFilters] = useState<string[]>([]);
468
+
469
+ <div>
470
+ <Heading size="sm">Filter Results</Heading>
471
+
472
+ <Checkbox.Group
473
+ value={selectedFilters}
474
+ onValueChange={(details) => setSelectedFilters(details.value)}
475
+ >
476
+ <Checkbox.Root value="inStock" variant="outline">
477
+ <Checkbox.HiddenInput />
478
+ <Checkbox.Control>
479
+ <Checkbox.Indicator />
480
+ </Checkbox.Control>
481
+ <Checkbox.Label>In stock</Checkbox.Label>
482
+ </Checkbox.Root>
483
+
484
+ <Checkbox.Root value="onSale" variant="outline">
485
+ <Checkbox.HiddenInput />
486
+ <Checkbox.Control>
487
+ <Checkbox.Indicator />
488
+ </Checkbox.Control>
489
+ <Checkbox.Label>On sale</Checkbox.Label>
490
+ </Checkbox.Root>
491
+
492
+ <Checkbox.Root value="freeShipping" variant="outline">
493
+ <Checkbox.HiddenInput />
494
+ <Checkbox.Control>
495
+ <Checkbox.Indicator />
496
+ </Checkbox.Control>
497
+ <Checkbox.Label>Free shipping</Checkbox.Label>
498
+ </Checkbox.Root>
499
+ </Checkbox.Group>
500
+ </div>
501
+ ```
502
+
503
+ ## DO NOT
504
+
505
+ ```typescript
506
+ // ❌ Don't use native checkbox input
507
+ <input type="checkbox" /> // Use Checkbox component instead
508
+
509
+ // ❌ Don't forget HiddenInput (needed for forms)
510
+ <Checkbox.Root>
511
+ <Checkbox.Control>
512
+ <Checkbox.Indicator />
513
+ </Checkbox.Control>
514
+ <Checkbox.Label>Option</Checkbox.Label>
515
+ </Checkbox.Root> // Missing <Checkbox.HiddenInput />
516
+
517
+ // ❌ Don't forget Label (accessibility issue)
518
+ <Checkbox.Root>
519
+ <Checkbox.HiddenInput />
520
+ <Checkbox.Control>
521
+ <Checkbox.Indicator />
522
+ </Checkbox.Control>
523
+ </Checkbox.Root> // Missing label
524
+
525
+ // ❌ Don't use checkbox for single exclusive choice
526
+ <Checkbox.Root>...</Checkbox.Root> // Use RadioGroup for exclusive selections
527
+
528
+ // ❌ Don't override checkbox colors with inline styles
529
+ <Checkbox.Control style={{ backgroundColor: 'red' }}>
530
+ <Checkbox.Indicator />
531
+ </Checkbox.Control> // Use variants instead
532
+
533
+ // ✅ Use complete Checkbox structure
534
+ <Checkbox.Root>
535
+ <Checkbox.HiddenInput />
536
+ <Checkbox.Control>
537
+ <Checkbox.Indicator />
538
+ </Checkbox.Control>
539
+ <Checkbox.Label>Accept terms</Checkbox.Label>
540
+ </Checkbox.Root>
541
+ ```
542
+
543
+ ## Accessibility
544
+
545
+ The Checkbox component follows WCAG 2.1 Level AA standards:
546
+
547
+ - **Keyboard Navigation**: Space to toggle, Tab to navigate
548
+ - **Focus Indicator**: Clear focus ring on keyboard navigation
549
+ - **ARIA Attributes**: Proper `role="checkbox"` and `aria-checked`
550
+ - **Labels**: Associated labels for screen readers
551
+ - **Disabled State**: Proper `aria-disabled` attribute
552
+ - **Touch Targets**: Minimum 44x44px with size md or larger
553
+
554
+ ### Accessibility Best Practices
555
+
556
+ ```typescript
557
+ // ✅ Always provide a label
558
+ <Checkbox.Root>
559
+ <Checkbox.HiddenInput />
560
+ <Checkbox.Control>
561
+ <Checkbox.Indicator />
562
+ </Checkbox.Control>
563
+ <Checkbox.Label>Subscribe to newsletter</Checkbox.Label>
564
+ </Checkbox.Root>
565
+
566
+ // ✅ Use required attribute for required fields
567
+ <Checkbox.Root required>
568
+ <Checkbox.HiddenInput />
569
+ <Checkbox.Control>
570
+ <Checkbox.Indicator />
571
+ </Checkbox.Control>
572
+ <Checkbox.Label>I accept the terms *</Checkbox.Label>
573
+ </Checkbox.Root>
574
+
575
+ // ✅ Provide error messages for invalid state
576
+ <div>
577
+ <Checkbox.Root invalid={!accepted && submitted}>
578
+ <Checkbox.HiddenInput />
579
+ <Checkbox.Control>
580
+ <Checkbox.Indicator />
581
+ </Checkbox.Control>
582
+ <Checkbox.Label>Accept terms</Checkbox.Label>
583
+ </Checkbox.Root>
584
+ {!accepted && submitted && (
585
+ <span role="alert" className={css({ color: 'error' })}>
586
+ Required field
587
+ </span>
588
+ )}
589
+ </div>
590
+
591
+ // ✅ Group related checkboxes
592
+ <Checkbox.Group aria-label="Notification preferences">
593
+ {/* Checkboxes here */}
594
+ </Checkbox.Group>
595
+ ```
596
+
597
+ ## Variant Selection Guide
598
+
599
+ | Scenario | Recommended Variant | Reasoning |
600
+ | ----------------- | ------------------- | -------------------------------------- |
601
+ | Terms acceptance | `solid` | Clear, prominent for important consent |
602
+ | Settings toggles | `solid` or `subtle` | Clear state indication |
603
+ | Filter options | `outline` | Less prominent, multiple selections |
604
+ | Feature flags | `solid` | Default, clear on/off state |
605
+ | Inline selections | `plain` | Minimal, blends with content |
606
+ | Form checkboxes | `solid` | Standard, clear visual feedback |
607
+
608
+ ## State Behaviors
609
+
610
+ | State | Visual Change | Behavior |
611
+ | ----------------- | ----------------------------- | -------------------------------------- |
612
+ | **Unchecked** | Empty box with border | Default state |
613
+ | **Checked** | Filled box with checkmark | Selected state |
614
+ | **Indeterminate** | Filled box with dash | Partial selection (e.g., "select all") |
615
+ | **Hover** | Background color change | Interactive feedback |
616
+ | **Focus** | Focus ring appears | Keyboard navigation indicator |
617
+ | **Disabled** | Grayed out, reduced opacity | Cannot be toggled |
618
+ | **Invalid** | Error color border/background | Validation error |
619
+
620
+ ## Testing
621
+
622
+ ```typescript
623
+ import { render, screen } from '@testing-library/react';
624
+ import userEvent from '@testing-library/user-event';
625
+
626
+ test('checkbox can be checked and unchecked', async () => {
627
+ const handleChange = vi.fn();
628
+
629
+ render(
630
+ <Checkbox.Root onCheckedChange={handleChange}>
631
+ <Checkbox.HiddenInput />
632
+ <Checkbox.Control>
633
+ <Checkbox.Indicator />
634
+ </Checkbox.Control>
635
+ <Checkbox.Label>Accept terms</Checkbox.Label>
636
+ </Checkbox.Root>
637
+ );
638
+
639
+ const checkbox = screen.getByRole('checkbox', { name: 'Accept terms' });
640
+
641
+ await userEvent.click(checkbox);
642
+ expect(handleChange).toHaveBeenCalledWith(expect.objectContaining({ checked: true }));
643
+
644
+ await userEvent.click(checkbox);
645
+ expect(handleChange).toHaveBeenCalledWith(expect.objectContaining({ checked: false }));
646
+ });
647
+
648
+ test('disabled checkbox cannot be toggled', async () => {
649
+ render(
650
+ <Checkbox.Root disabled>
651
+ <Checkbox.HiddenInput />
652
+ <Checkbox.Control>
653
+ <Checkbox.Indicator />
654
+ </Checkbox.Control>
655
+ <Checkbox.Label>Disabled</Checkbox.Label>
656
+ </Checkbox.Root>
657
+ );
658
+
659
+ const checkbox = screen.getByRole('checkbox');
660
+ await userEvent.click(checkbox);
661
+
662
+ expect(checkbox).not.toBeChecked();
663
+ });
664
+ ```
665
+
666
+ ## Related Components
667
+
668
+ - **RadioGroup** - For exclusive single selections
669
+ - **Switch** - For on/off toggles (different visual metaphor)
670
+ - **Button** - For action triggers
671
+ - **Select** - For choosing from many options