@lilydesignsystem/react-headless 0.1.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.
package/index.md ADDED
@@ -0,0 +1,1161 @@
1
+ # Lily Design System™ - React Headless
2
+
3
+ A comprehensive headless React component library with 490 accessible, unstyled components for building design systems. Every component follows WCAG 2.2 AAA guidelines with full ARIA support, keyboard navigation, and internationalization readiness.
4
+
5
+ **Headless** means zero CSS, zero styles, zero opinions about appearance. You provide all styling. Components provide structure, semantics, accessibility, and behavior.
6
+
7
+ ## Features
8
+
9
+ - 490 headless React components
10
+ - TypeScript with full type definitions
11
+ - WCAG 2.2 AAA accessibility compliance
12
+ - Full keyboard navigation support
13
+ - ARIA attributes and roles
14
+ - Internationalization-ready (no hardcoded strings)
15
+ - Zero dependencies beyond React
16
+ - Works with any CSS framework or custom styles
17
+
18
+ ## Quick Start
19
+
20
+ ### Install
21
+
22
+ ```bash
23
+ pnpm install @lilydesignsystem/react-headless
24
+ ```
25
+
26
+ ### Peer Dependencies
27
+
28
+ ```json
29
+ {
30
+ "react": "^18.0.0 || ^19.0.0",
31
+ "react-dom": "^18.0.0 || ^19.0.0"
32
+ }
33
+ ```
34
+
35
+ ### Basic Usage
36
+
37
+ ```tsx
38
+ import { useState } from "react";
39
+ import Button from "lily-design-system-react-headless/components/Button";
40
+ import TextInput from "lily-design-system-react-headless/components/TextInput";
41
+ import Alert from "lily-design-system-react-headless/components/Alert";
42
+
43
+ function App() {
44
+ const [name, setName] = useState("");
45
+
46
+ return (
47
+ <div>
48
+ <TextInput
49
+ label="Your name"
50
+ value={name}
51
+ onChange={setName}
52
+ placeholder="Enter your name"
53
+ />
54
+ <Button onClick={() => alert(`Hello, ${name}!`)}>Greet</Button>
55
+ {name && (
56
+ <Alert type="success" heading="Greeting">
57
+ Welcome, {name}!
58
+ </Alert>
59
+ )}
60
+ </div>
61
+ );
62
+ }
63
+ ```
64
+
65
+ ## Architecture
66
+
67
+ ### Headless Design
68
+
69
+ Components provide:
70
+
71
+ - Semantic HTML structure
72
+ - ARIA attributes for accessibility
73
+ - Props and events for behavior
74
+ - Keyboard interaction patterns
75
+
76
+ Components do NOT provide:
77
+
78
+ - CSS styles or stylesheets
79
+ - Visual styling or themes
80
+ - Tailwind classes
81
+ - Styled-components
82
+ - Any built-in appearance
83
+
84
+ ### Component File Structure
85
+
86
+ Each component consists of three files:
87
+
88
+ ```
89
+ components/
90
+ Button.tsx # Component implementation
91
+ Button.test.tsx # Tests (React Testing Library + Vitest)
92
+ Button.md # Documentation
93
+ ```
94
+
95
+ ### Naming Conventions
96
+
97
+ Component names follow a suffix-based pattern that indicates the root HTML element:
98
+
99
+ | Suffix | HTML Element | Example |
100
+ | --------- | ------------ | -------------------------------------------- |
101
+ | Button | `<button>` | `Button`, `ToggleButton` |
102
+ | Input | `<input>` | `TextInput`, `DateInput` |
103
+ | Select | `<select>` | `Select`, `ThemeSelect` |
104
+ | Dialog | `<dialog>` | `Dialog`, `AlertDialog` |
105
+ | Nav | `<nav>` | `BreadcrumbNav`, `TreeNav` |
106
+ | List | `<ol>` | `CheckList`, `TaskList` |
107
+ | ListItem | `<li>` | `CheckListItem`, `TaskListItem` |
108
+ | Kbd | `<kbd>` | `Kbd` |
109
+ | Table | `<table>` | `DataTable`, `CalendarTable` |
110
+ | TableHead | `<thead>` | `DataTableHead` |
111
+ | TableBody | `<tbody>` | `DataTableBody` |
112
+ | TableFoot | `<tfoot>` | `DataTableFoot` |
113
+ | TableTD | `<th>` | `DataTableTD` |
114
+ | TableRow | `<tr>` | `DataTableRow` |
115
+ | TableTD | `<td>` | `DataTableTD` |
116
+ | Div | `<div>` | `PinInputDiv`, `PasswordInputOrTextInputDiv` |
117
+ | Meter | `<meter>` | `Meter` |
118
+ | Progress | `<progress>` | `Progress` |
119
+ | Fieldset | `<fieldset>` | `Fieldset` |
120
+
121
+ ### CSS Class Convention
122
+
123
+ Every component's root element includes a semantic CSS class matching its kebab-case name, plus any consumer-provided `className`:
124
+
125
+ ```tsx
126
+ <Button className="my-custom">Click</Button>
127
+ // Renders: <button class="button my-custom">Click</button>
128
+ ```
129
+
130
+ ## Component Categories
131
+
132
+ ### Navigation
133
+
134
+ | Component | Description |
135
+ | ------------------ | ------------------------------------------------------- |
136
+ | AccordionNav | Navigation container for collapsible accordion sections |
137
+ | AccordionList | Ordered list of accordion items |
138
+ | AccordionListItem | One collapsible accordion item |
139
+ | ActionLink | Hyperlink styled as an action trigger |
140
+ | BackLink | Link to return to a previous page |
141
+ | BreadcrumbNav | Breadcrumb trail navigation container |
142
+ | BreadcrumbList | Ordered list of breadcrumb items |
143
+ | BreadcrumbListItem | One breadcrumb link in the trail |
144
+ | ContentsNav | Table of contents navigation landmark |
145
+ | ContentsList | Ordered list of contents items |
146
+ | ContentsListItem | One link to a page section |
147
+ | NavigationMenu | Site-wide navigation menu |
148
+ | PaginationNav | Page navigation links container |
149
+ | PaginationList | Ordered list of page links |
150
+ | PaginationListItem | One page link |
151
+ | SkipLink | Hidden link for keyboard users to skip to main content |
152
+ | TabBar | Group of tabs for switching content panels |
153
+ | TabBarButton | One tab button |
154
+ | TreeMenu | Hierarchical tree menu with expandable branches |
155
+ | TreeNav | Hierarchical navigation with expandable branches |
156
+
157
+ ### Forms & Inputs
158
+
159
+ | Component | Description |
160
+ | --------------------------- | ------------------------------------------------------------------------- |
161
+ | Button | Clickable button for actions |
162
+ | ButtonInput | Input element of type button |
163
+ | Citation | Acknowledges relevance of another's work to the topic |
164
+ | CheckboxInput | Checkbox for toggling a boolean value |
165
+ | CheckboxGroup | Group component that manages a collection of checkboxes with shared state |
166
+ | Code | Inline code span for short code snippets |
167
+ | CodeBlock | Block of formatted code with line numbers and highlighting |
168
+ | ColorInput | Color value selector |
169
+ | Comment | Displays user discussions and user feedback |
170
+ | Combobox | Text input with dropdown filter list |
171
+ | CurrencyInput | Locale-aware currency input with automatic formatting |
172
+ | DateField | Labeled date input with validation |
173
+ | DateInput | Date value input |
174
+ | DateTimeLocalInput | Date and time input |
175
+ | EmailInput | Email address input |
176
+ | Field | Form field wrapper with label and error |
177
+ | Fieldset | Group of related form fields |
178
+ | FileInput | File selector input |
179
+ | Form | Form element for data collection |
180
+ | Hero | Large box or image with title and description |
181
+ | HiddenInput | Hidden form data input |
182
+ | ImageFileInput | Image file selector with preview |
183
+ | ImageInput | Graphical submit button that displays an image |
184
+ | Input | Generic HTML input element |
185
+ | Label | Label for a form input |
186
+ | MonthInput | Month and year selector |
187
+ | NumberInput | Numeric value input with validation |
188
+ | Option | Option in a select dropdown |
189
+ | Organization | Organization container for organization-related information |
190
+ | Person | Person container for person-related information |
191
+ | Place | Place container for place-related information |
192
+ | PasswordInput | Password input with obscured text |
193
+ | PasswordInputOrTextInputDiv | Password input with show/hide toggle |
194
+ | PinInputDiv | Multi-digit PIN/OTP entry |
195
+ | PostalCodeInput | Postal/ZIP code input |
196
+ | RadioGroup | Group of radio buttons |
197
+ | RadioInput | Single radio button |
198
+ | RangeInput | Slider for selecting a value in a range |
199
+ | ResetInput | Form reset button |
200
+ | SearchInput | Search query input |
201
+ | Select | Dropdown select element |
202
+ | SelectWithExtras | Enhanced select with search/groups |
203
+ | SubmitInput | Form submit button |
204
+ | SwitchButton | Toggle switch for on/off settings |
205
+ | TagInput | Input for adding and removing tags |
206
+ | TelInput | Telephone number input |
207
+ | TextInput | Single-line text input |
208
+ | TextAreaInput | Multi-line text area |
209
+ | ThemeSelect | Visual theme picker |
210
+ | ThemeSelect | Theme select dropdown |
211
+ | ThemeSelectOption | One option in theme select |
212
+ | TimeInput | Time value input |
213
+ | TimePickerInput | Time picker with dropdown |
214
+ | UrlInput | URL input |
215
+ | WeekInput | Week and year selector |
216
+
217
+ ### Data Display
218
+
219
+ | Component | Description |
220
+ | --------------------------- | ----------------------------------------- |
221
+ | Avatar | Avatar indicator (image or text) |
222
+ | AvatarImage | Avatar image (user photo) |
223
+ | AvatarGroup | Group of avatar components |
224
+ | AvatarText | Avatar text (user initials) |
225
+ | Badge | Small label for counts/statuses |
226
+ | CalendarTable | Calendar grid for dates |
227
+ | CalendarTableHead/Body/Foot | Calendar table sections |
228
+ | CalendarTableTD/Row/Data | Calendar table cells |
229
+ | Card | Grouped content container |
230
+ | DataTable | Sortable data table grid |
231
+ | DataTableHead/Body/Foot | Data table sections |
232
+ | DataTableTD/Row/Data | Data table cells |
233
+ | GanttTable | Gantt chart schedule table |
234
+ | GanttTableHead/Body/Foot | Gantt table sections |
235
+ | GanttTableTH/Row/Data | Gantt table cells |
236
+ | KanbanTable | Kanban board table |
237
+ | KanbanTableHead/Body/Foot | Kanban table sections |
238
+ | KanbanTableTD/Row/Data | Kanban table cells |
239
+ | Table | Table with rows and columns |
240
+ | TableHead | Table thead |
241
+ | TableBody | Table tbody |
242
+ | TableFoot | Table tfoot |
243
+ | TableTD | Table column |
244
+ | TableRow | Table row |
245
+ | TableTD | Table data cell |
246
+ | Caption | Table/figure caption |
247
+ | SuperBanner | High-priority state banner for entire app |
248
+ | SummaryList | Key-value summary pairs |
249
+ | SummaryListItem | One key-value pair |
250
+
251
+ ### Feedback & Status
252
+
253
+ | Component | Description |
254
+ | ---------------------------- | --------------------------------------------------- |
255
+ | Alert | Status message for feedback |
256
+ | AlertDialog | Modal for critical messages |
257
+ | Banner | Prominent message bar |
258
+ | CharacterCounter | Character count for text fields |
259
+ | ErrorMessage | Form field error message |
260
+ | Event | Event container for event-related information |
261
+ | ErrorSummary | Summary of validation errors |
262
+ | FiveFaceRatingView | Read-only face rating display |
263
+ | FiveFaceRatingPicker | 1-5 face rating selector |
264
+ | FiveFaceRatingPickerButton | Face rating button |
265
+ | FiveStarRatingView | Read-only star rating display |
266
+ | FiveStarRatingPicker | 1-5 star rating selector |
267
+ | FiveStarRatingPickerButton | Star rating button |
268
+ | Meter | Scalar value gauge |
269
+ | MockupBrowser | Box area that looks like a web browser |
270
+ | MockupLaptop | Box area that looks like a laptop computer |
271
+ | MockupPhonePortrait | Box area that looks like a mobile phone |
272
+ | MockupTabletLandscape | Box area that looks like a tablet in landscape mode |
273
+ | MockupTabletPortrait | Box area that looks like a tablet in portrait mode |
274
+ | MockupShell | Box area that looks like a terminal shell |
275
+ | MockupWatch | Box area that looks like a smart watch |
276
+ | MockupWindow | Box area that looks like a desktop window |
277
+ | NetPromoterScoreView | Read-only NPS display |
278
+ | NetPromoterScorePicker | 0-10 NPS selector |
279
+ | NetPromoterScorePickerButton | NPS button |
280
+ | Progress | Horizontal progress bar |
281
+ | ProgressCircle | Circular progress indicator |
282
+ | ProgressSpinner | Indeterminate spinner |
283
+ | Toast | Auto-dismissing notification |
284
+
285
+ ### Overlays & Panels
286
+
287
+ | Component | Description |
288
+ | --------------- | -------------------------------- |
289
+ | Collapsible | Expandable/collapsible container |
290
+ | ContextMenu | Right-click context menu |
291
+ | ContextMenuItem | One context menu item |
292
+ | Details | Disclosure widget |
293
+ | Dialog | Modal/non-modal dialog |
294
+ | Drawer | Edge-sliding panel |
295
+ | DropdownMenu | Button-triggered dropdown menu |
296
+ | Expander | Expandable content control |
297
+ | FloatingPanel | Panel floating above content |
298
+ | HoverCard | Card appearing on hover |
299
+ | Popover | Floating box anchored to trigger |
300
+ | Popup | Temporary overlay |
301
+ | Sheet | Edge-sliding overlay panel |
302
+ | Tooltip | Descriptive text on hover/focus |
303
+
304
+ ### Layout & Structure
305
+
306
+ | Component | Description |
307
+ | ----------------------- | ----------------------------------------------------------------------------------------- |
308
+ | AspectRatioContainer | Fixed aspect ratio container |
309
+ | Footer | Page/section footer |
310
+ | Header | Page/section header |
311
+ | GrailLayout | Responsive web design structure with header, left aside, center main, right aside, footer |
312
+ | GrailLayoutTopHeader | Grail layout top header full width |
313
+ | GrailLayoutLeftAside | Grail layout left aside sidebar |
314
+ | GrailLayoutCenterMain | Grail layout center main content |
315
+ | GrailLayoutRightAside | Grail layout right aside sidebar |
316
+ | GrailLayoutBottomFooter | Grail layout bottom footer full width |
317
+ | Panel | Generic content panel |
318
+ | Separator | Horizontal/vertical divider |
319
+ | Sidebar | Side navigation panel |
320
+ | SlideOutDrawer | Side-sliding drawer |
321
+ | Splitter | Draggable panel resizer |
322
+ | Resizable | User-resizable container |
323
+
324
+ ### Content
325
+
326
+ | Component | Description |
327
+ | -------------------- | --------------------------------------------------------- |
328
+ | AiLabel | Indicator of AI instances for AI explainability |
329
+ | BeachBall | Decorative animated element |
330
+ | Carousel | Content slideshow |
331
+ | Character | Single character display |
332
+ | ChatNav | Navigation container for chat information |
333
+ | ChatList | Ordered list of chat list item components |
334
+ | ChatListItem | One chat list item, typically containing one chat message |
335
+ | ChatMessage | Chat conversation message entry with author, time, etc. |
336
+ | ClipboardCopyButton | Copy-to-clipboard button |
337
+ | Emoji | Accessible emoji character |
338
+ | EmojiCharacterPicker | Emoji browser/picker |
339
+ | Figure | Self-contained figure with caption |
340
+ | Flair | Decorative highlight element |
341
+ | Footnote | Footnote reference and content |
342
+ | Icon | Icon container |
343
+ | Image | Accessible image element |
344
+ | InsetText | Indented distinguishing text |
345
+ | Kbd | Keyboard shortcuts and key combinations display |
346
+ | QrCodeImage | QR code generator |
347
+ | Skeleton | Loading placeholder animation |
348
+ | Sparkline | Inline data trend chart |
349
+ | Notification | Event notification message |
350
+ | SignaturePad | Handwritten signature capture |
351
+
352
+ ### Lists & Groups
353
+
354
+ | Component | Description |
355
+ | ---------------- | --------------------------------------------- |
356
+ | CheckList | Checklist container |
357
+ | CheckListItem | Checklist item with checkbox |
358
+ | DoList | Encouraged-practice guideline list |
359
+ | DoListItem | One encouraged-practice item |
360
+ | DontList | Discouraged-practice guideline list |
361
+ | DontListItem | One discouraged-practice item |
362
+ | Listbox | Selectable options list |
363
+ | Loading | Loading indicator (text, image, or animation) |
364
+ | Menu | Actions/options list |
365
+ | MenuItem | One menu item |
366
+ | MenuBar | Horizontal menu bar |
367
+ | MenuBarButton | One menu bar item |
368
+ | SegmentGroup | Mutually exclusive segment options |
369
+ | SegmentGroupItem | One segment option |
370
+ | TagGroup | Group of tags |
371
+ | Tag | Keyword label |
372
+ | TaskList | Task list container |
373
+ | TaskListItem | Task item with checkbox |
374
+ | TimelineList | Chronological events list |
375
+ | TimelineListItem | One timeline event |
376
+ | ToggleGroup | Toggle button group |
377
+ | ToggleButton | Pressable toggle button |
378
+ | TreeList | Hierarchical expandable list |
379
+ | TreeListItem | One item in a tree navigation list |
380
+
381
+ ### Toolbars & Actions
382
+
383
+ | Component | Description |
384
+ | ------------------- | ---------------------------------- |
385
+ | CallToAction | Prominent action prompt |
386
+ | Command | Command palette for search/execute |
387
+ | HamburgerMenu | Mobile navigation toggle |
388
+ | TaskBar | Task shortcuts bar |
389
+ | TaskBarButton | One task bar item |
390
+ | ToolBar | Tool actions bar |
391
+ | ToolBarButton | One tool bar action |
392
+ | CalendarRangePicker | Date range picker |
393
+ | ColorPicker | 2D color picker board |
394
+ | ColorPickerButton | Color swatch button |
395
+
396
+ ### Specialty
397
+
398
+ | Component | Description |
399
+ | --------------------------- | --------------------------------------------- |
400
+ | CareCard | Medical care instruction card |
401
+ | Dial | Rotary dial value selector |
402
+ | DialGroup | Group of dial components |
403
+ | Diff | Side-by-side comparison of two items |
404
+ | DigitalObjectIdentifierLink | Permanent DOI hyperlink to electronic source |
405
+ | AngleSliderRangeInput | Angle selection range input |
406
+ | DateRange | Start/end date range display |
407
+ | Editable | Inline-editable text element |
408
+ | EditableForm | Inline editing form wrapper |
409
+ | FileDialog | File browser dialog |
410
+ | FileManager | File navigation manager |
411
+ | FileUpload | Drag-and-drop file upload |
412
+ | MedicalRecordRedBox | Critical medical info box |
413
+ | ReviewDate | Content review date display |
414
+ | Slider | Draggable value selector |
415
+ | Sonner | Toast notification manager |
416
+ | Tile | Structured content grouping container |
417
+ | Timer | Countdown/elapsed time display |
418
+ | TimerButton | Button with auto-click timer |
419
+ | ThemeSelectButton | Picker button for selecting a visual theme |
420
+ | ThemeView | Current theme display |
421
+ | Tour | Step-by-step guide |
422
+ | TourList | Tour steps list |
423
+ | TourListItem | One tour step |
424
+ | ScreenReaderSpan | Visually hidden span for screen reader labels |
425
+ | ScrollArea | Scrollable container |
426
+ | ScrollBar | Custom scrollbar |
427
+ | WarningCallout | Warning callout box |
428
+ | InformationCallout | Informational callout box |
429
+
430
+ ### Regional & Specialized
431
+
432
+ | Component | Description |
433
+ | ------------------------------------------------ | -------------------------------- |
434
+ | PostalCodeView | Postal/ZIP code display |
435
+ | RedAmberGreenView/Picker/PickerButton | RAG status display and selection |
436
+ | RedOrangeYellowGreenBlueView/Picker/PickerButton | Five-level color status |
437
+ | UnitedKingdomNationalHealthServiceNumberInput | UK NHS number input |
438
+ | UnitedKingdomNationalHealthServiceNumberView | UK NHS number display |
439
+ | UnitedStatesSocialSecurityNumberInput | US SSN input |
440
+ | UnitedStatesSocialSecurityNumberView | US SSN display |
441
+ | MeasurementInstanceInput/View | Measurement value input/display |
442
+ | MeasurementSystemInput/View | Measurement system input/display |
443
+ | MeasurementUnitInput/View | Measurement unit input/display |
444
+
445
+ ## Usage Examples
446
+
447
+ See the `examples/` directory for comprehensive standalone examples:
448
+
449
+ | Example | Components Used |
450
+ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
451
+ | [ContactForm](examples/ContactForm.tsx) | Form, Field, TextInput, EmailInput, TextAreaInput, Select, Option, Button, ErrorSummary |
452
+ | [SettingsPage](examples/SettingsPage.tsx) | SwitchButton, RadioGroup, RadioInput, Select, Separator, Fieldset, Button, Banner |
453
+ | [Dashboard](examples/Dashboard.tsx) | Card, Progress, ProgressCircle, Badge, Banner, DataTable, DataTableHead/Body/Row/Data |
454
+ | [PageLayout](examples/PageLayout.tsx) | SkipLink, Header, Footer, NavigationMenu, BreadcrumbNav/List/ListItem, Sidebar |
455
+ | [TabbedInterface](examples/TabbedInterface.tsx) | TabBar, TabBarButton, AccordionNav/List/ListItem, Badge |
456
+ | [DialogFlow](examples/DialogFlow.tsx) | Dialog, AlertDialog, Drawer, Button, Popover, Tooltip |
457
+ | [RatingAndFeedback](examples/RatingAndFeedback.tsx) | FiveStarRatingPicker/View, FiveFaceRatingPicker, NetPromoterScorePicker, TextAreaInput, Alert |
458
+ | [FileUploadForm](examples/FileUploadForm.tsx) | FileUpload, Progress, Button, Alert, Badge, Form, Field |
459
+ | [TaskManagement](examples/TaskManagement.tsx) | TaskList, TaskListItem, TextInput, CheckboxInput, Button, Badge, Progress |
460
+ | [NavigationAndMenus](examples/NavigationAndMenus.tsx) | NavigationMenu, MenuBar, MenuBarButton, ToolBar, ToolBarButton, HamburgerMenu, DropdownMenu, Separator |
461
+ | [TimelineAndCards](examples/TimelineAndCards.tsx) | TimelineList, TimelineListItem, Card, Badge, DateRange, ReviewDate, SummaryList, SummaryListItem |
462
+ | [SearchAndFilter](examples/SearchAndFilter.tsx) | Combobox, SearchInput, TagInput, TagGroup, Tag, DataTable, DataTableHead/Body/Row/Data, Badge |
463
+
464
+ ### Form with Validation
465
+
466
+ ```tsx
467
+ import { useState } from "react";
468
+ import Field from "./components/Field";
469
+ import TextInput from "./components/TextInput";
470
+ import EmailInput from "./components/EmailInput";
471
+ import Button from "./components/Button";
472
+ import ErrorSummary from "./components/ErrorSummary";
473
+
474
+ function ContactForm() {
475
+ const [name, setName] = useState("");
476
+ const [email, setEmail] = useState("");
477
+ const [errors, setErrors] = useState<Record<string, string>>({});
478
+
479
+ function validate() {
480
+ const newErrors: Record<string, string> = {};
481
+ if (!name) newErrors.name = "Name is required";
482
+ if (!email) newErrors.email = "Email is required";
483
+ setErrors(newErrors);
484
+ return Object.keys(newErrors).length === 0;
485
+ }
486
+
487
+ function handleSubmit() {
488
+ if (validate()) {
489
+ // submit form
490
+ }
491
+ }
492
+
493
+ return (
494
+ <div>
495
+ {Object.keys(errors).length > 0 && (
496
+ <ErrorSummary title="Please fix the following errors">
497
+ <ul>
498
+ {Object.entries(errors).map(([field, msg]) => (
499
+ <li key={field}>
500
+ <a href={`#${field}`}>{msg}</a>
501
+ </li>
502
+ ))}
503
+ </ul>
504
+ </ErrorSummary>
505
+ )}
506
+ <Field label="Name" required error={errors.name}>
507
+ <TextInput label="Name" value={name} onChange={setName} />
508
+ </Field>
509
+ <Field label="Email" required error={errors.email}>
510
+ <EmailInput label="Email" value={email} onChange={setEmail} />
511
+ </Field>
512
+ <Button onClick={handleSubmit}>Submit</Button>
513
+ </div>
514
+ );
515
+ }
516
+ ```
517
+
518
+ ### Tabbed Interface
519
+
520
+ ```tsx
521
+ import { useState } from "react";
522
+ import TabBar from "./components/TabBar";
523
+ import TabBarButton from "./components/TabBarButton";
524
+
525
+ function TabbedContent() {
526
+ const [activeTab, setActiveTab] = useState("overview");
527
+
528
+ return (
529
+ <div>
530
+ <TabBar label="Content sections">
531
+ <TabBarButton
532
+ selected={activeTab === "overview"}
533
+ controls="overview-panel"
534
+ onClick={() => setActiveTab("overview")}
535
+ >
536
+ Overview
537
+ </TabBarButton>
538
+ <TabBarButton
539
+ selected={activeTab === "details"}
540
+ controls="details-panel"
541
+ onClick={() => setActiveTab("details")}
542
+ >
543
+ Details
544
+ </TabBarButton>
545
+ </TabBar>
546
+ {activeTab === "overview" && (
547
+ <div id="overview-panel">Overview content</div>
548
+ )}
549
+ {activeTab === "details" && <div id="details-panel">Details content</div>}
550
+ </div>
551
+ );
552
+ }
553
+ ```
554
+
555
+ ### Breadcrumb Navigation
556
+
557
+ ```tsx
558
+ import BreadcrumbNav from "./components/BreadcrumbNav";
559
+ import BreadcrumbList from "./components/BreadcrumbList";
560
+ import BreadcrumbListItem from "./components/BreadcrumbListItem";
561
+
562
+ function Breadcrumbs() {
563
+ return (
564
+ <BreadcrumbNav label="Breadcrumb">
565
+ <BreadcrumbList>
566
+ <BreadcrumbListItem>
567
+ <a href="/">Home</a>
568
+ </BreadcrumbListItem>
569
+ <BreadcrumbListItem>
570
+ <a href="/products">Products</a>
571
+ </BreadcrumbListItem>
572
+ <BreadcrumbListItem current>Widget Pro</BreadcrumbListItem>
573
+ </BreadcrumbList>
574
+ </BreadcrumbNav>
575
+ );
576
+ }
577
+ ```
578
+
579
+ ### Modal Dialog
580
+
581
+ ```tsx
582
+ import { useState } from "react";
583
+ import Dialog from "./components/Dialog";
584
+ import Button from "./components/Button";
585
+
586
+ function ConfirmDelete() {
587
+ const [open, setOpen] = useState(false);
588
+
589
+ return (
590
+ <div>
591
+ <Button onClick={() => setOpen(true)}>Delete Item</Button>
592
+ <Dialog open={open} label="Confirm Deletion" onChange={setOpen}>
593
+ <p>Are you sure you want to delete this item?</p>
594
+ <Button onClick={() => setOpen(false)}>Cancel</Button>
595
+ <Button
596
+ onClick={() => {
597
+ deleteItem();
598
+ setOpen(false);
599
+ }}
600
+ >
601
+ Delete
602
+ </Button>
603
+ </Dialog>
604
+ </div>
605
+ );
606
+ }
607
+ ```
608
+
609
+ ### Data Table
610
+
611
+ ```tsx
612
+ import DataTable from "./components/DataTable";
613
+ import DataTableHead from "./components/DataTableHead";
614
+ import DataTableBody from "./components/DataTableBody";
615
+ import DataTableRow from "./components/DataTableRow";
616
+ import DataTableTD from "./components/DataTableTD";
617
+
618
+ function UserTable({ users }) {
619
+ return (
620
+ <DataTable label="Users">
621
+ <DataTableHead>
622
+ <DataTableRow>
623
+ <th>Name</th>
624
+ <th>Email</th>
625
+ <th>Role</th>
626
+ </DataTableRow>
627
+ </DataTableHead>
628
+ <DataTableBody>
629
+ {users.map((user) => (
630
+ <DataTableRow key={user.id}>
631
+ <DataTableTD>{user.name}</DataTableTD>
632
+ <DataTableTD>{user.email}</DataTableTD>
633
+ <DataTableTD>{user.role}</DataTableTD>
634
+ </DataTableRow>
635
+ ))}
636
+ </DataTableBody>
637
+ </DataTable>
638
+ );
639
+ }
640
+ ```
641
+
642
+ ### Star Rating
643
+
644
+ ```tsx
645
+ import { useState } from "react";
646
+ import FiveStarRatingPicker from "./components/FiveStarRatingPicker";
647
+ import FiveStarRatingView from "./components/FiveStarRatingView";
648
+
649
+ function RatingExample() {
650
+ const [rating, setRating] = useState(0);
651
+
652
+ return (
653
+ <div>
654
+ <FiveStarRatingPicker
655
+ label="Rate this product"
656
+ value={rating}
657
+ onChange={setRating}
658
+ />
659
+ <FiveStarRatingView label="Current rating" value={rating} />
660
+ </div>
661
+ );
662
+ }
663
+ ```
664
+
665
+ ### Accordion Navigation
666
+
667
+ ```tsx
668
+ import { useState } from "react";
669
+ import AccordionNav from "./components/AccordionNav";
670
+ import AccordionList from "./components/AccordionList";
671
+ import AccordionListItem from "./components/AccordionListItem";
672
+
673
+ function FAQ() {
674
+ return (
675
+ <AccordionNav label="Frequently asked questions">
676
+ <AccordionList>
677
+ <AccordionListItem>
678
+ <h3>What is a headless component?</h3>
679
+ <p>
680
+ A headless component provides behavior, accessibility, and structure
681
+ without any visual styling. You supply all CSS.
682
+ </p>
683
+ </AccordionListItem>
684
+ <AccordionListItem>
685
+ <h3>Can I use Tailwind CSS?</h3>
686
+ <p>
687
+ Yes. Pass Tailwind classes via the className prop on every
688
+ component. Any CSS framework works.
689
+ </p>
690
+ </AccordionListItem>
691
+ <AccordionListItem>
692
+ <h3>Is keyboard navigation supported?</h3>
693
+ <p>All interactive components follow WAI-ARIA keyboard patterns.</p>
694
+ </AccordionListItem>
695
+ </AccordionList>
696
+ </AccordionNav>
697
+ );
698
+ }
699
+ ```
700
+
701
+ ### Settings with Toggle Switches
702
+
703
+ ```tsx
704
+ import { useState } from "react";
705
+ import SwitchButton from "./components/SwitchButton";
706
+ import Separator from "./components/Separator";
707
+
708
+ function SettingsPanel() {
709
+ const [darkMode, setDarkMode] = useState(false);
710
+ const [notifications, setNotifications] = useState(true);
711
+ const [autoSave, setAutoSave] = useState(true);
712
+
713
+ return (
714
+ <div>
715
+ <h2>Settings</h2>
716
+ <SwitchButton
717
+ label="Dark mode"
718
+ checked={darkMode}
719
+ onChange={setDarkMode}
720
+ />
721
+ <Separator />
722
+ <SwitchButton
723
+ label="Push notifications"
724
+ checked={notifications}
725
+ onChange={setNotifications}
726
+ />
727
+ <Separator />
728
+ <SwitchButton
729
+ label="Auto-save"
730
+ checked={autoSave}
731
+ onChange={setAutoSave}
732
+ />
733
+ </div>
734
+ );
735
+ }
736
+ ```
737
+
738
+ ### Inline Editing
739
+
740
+ ```tsx
741
+ import Editable from "./components/Editable";
742
+
743
+ function EditableProfile() {
744
+ return (
745
+ <div>
746
+ <h2>Profile</h2>
747
+ <div>
748
+ <span>Name: </span>
749
+ <Editable label="Edit name" value="Jane Doe" />
750
+ </div>
751
+ <div>
752
+ <span>Title: </span>
753
+ <Editable label="Edit title" value="Software Engineer" />
754
+ </div>
755
+ </div>
756
+ );
757
+ }
758
+ ```
759
+
760
+ ### Checkbox Form
761
+
762
+ ```tsx
763
+ import { useState } from "react";
764
+ import CheckboxInput from "./components/CheckboxInput";
765
+ import Button from "./components/Button";
766
+
767
+ function TermsForm() {
768
+ const [accepted, setAccepted] = useState(false);
769
+ const [newsletter, setNewsletter] = useState(false);
770
+
771
+ return (
772
+ <form>
773
+ <CheckboxInput
774
+ label="I accept the terms and conditions"
775
+ checked={accepted}
776
+ onChange={setAccepted}
777
+ required
778
+ />
779
+ <CheckboxInput
780
+ label="Subscribe to newsletter"
781
+ checked={newsletter}
782
+ onChange={setNewsletter}
783
+ />
784
+ <Button type="submit" disabled={!accepted}>
785
+ Sign Up
786
+ </Button>
787
+ </form>
788
+ );
789
+ }
790
+ ```
791
+
792
+ ### Collapsible Sections
793
+
794
+ ```tsx
795
+ import Collapsible from "./components/Collapsible";
796
+
797
+ function CollapsibleContent() {
798
+ return (
799
+ <div>
800
+ <Collapsible summary="System requirements">
801
+ <ul>
802
+ <li>Node.js 18 or later</li>
803
+ <li>React 18 or 19</li>
804
+ <li>TypeScript 5.x</li>
805
+ </ul>
806
+ </Collapsible>
807
+ <Collapsible summary="Installation steps">
808
+ <ol>
809
+ <li>Clone the repository</li>
810
+ <li>Run pnpm install</li>
811
+ <li>Import components as needed</li>
812
+ </ol>
813
+ </Collapsible>
814
+ </div>
815
+ );
816
+ }
817
+ ```
818
+
819
+ ### Progress Dashboard
820
+
821
+ ```tsx
822
+ import ProgressCircle from "./components/ProgressCircle";
823
+ import Badge from "./components/Badge";
824
+ import Banner from "./components/Banner";
825
+
826
+ function Dashboard() {
827
+ const tasksCompleted = 7;
828
+ const tasksTotal = 10;
829
+ const percent = Math.round((tasksCompleted / tasksTotal) * 100);
830
+
831
+ return (
832
+ <div>
833
+ <Banner type="info">
834
+ Sprint ends in 3 days. {tasksTotal - tasksCompleted} tasks remaining.
835
+ </Banner>
836
+ <div>
837
+ <ProgressCircle label="Sprint progress" value={percent} />
838
+ <Badge type="success">{percent}% complete</Badge>
839
+ </div>
840
+ </div>
841
+ );
842
+ }
843
+ ```
844
+
845
+ ## API Patterns
846
+
847
+ ### Common Props
848
+
849
+ Every component accepts these props:
850
+
851
+ | Prop | Type | Description |
852
+ | -------------- | ----------------- | -------------------------------------------------------------------- |
853
+ | `className` | `string` | Additional CSS classes (combined with the built-in kebab-case class) |
854
+ | `children` | `React.ReactNode` | Child content (on container components) |
855
+ | `...restProps` | `unknown` | Any additional HTML attributes are forwarded to the root element |
856
+
857
+ ### Controlled Components
858
+
859
+ Interactive components follow the controlled component pattern with `value` + `onChange`:
860
+
861
+ ```tsx
862
+ // Text inputs: onChange receives the string value directly
863
+ <TextInput label="Name" value={name} onChange={setName} />
864
+ <EmailInput label="Email" value={email} onChange={setEmail} />
865
+ <TextAreaInput label="Bio" value={bio} onChange={setBio} />
866
+
867
+ // Numeric inputs: onChange receives a number
868
+ <NumberInput label="Age" value={age} onChange={setAge} />
869
+ <RangeInput label="Volume" value={volume} onChange={setVolume} />
870
+
871
+ // Boolean inputs: onChange receives a boolean
872
+ <CheckboxInput label="Accept" checked={accepted} onChange={setAccepted} />
873
+ <SwitchButton label="Dark mode" checked={darkMode} onChange={setDarkMode} />
874
+ <ToggleButton label="Bold" pressed={bold} onChange={setBold} />
875
+
876
+ // Select: onChange receives the selected value string
877
+ <Select label="Country" value={country} onChange={setCountry}>
878
+ <Option value="us">United States</Option>
879
+ <Option value="uk">United Kingdom</Option>
880
+ </Select>
881
+ ```
882
+
883
+ ### Open/Close Components
884
+
885
+ Overlays and expandable components use `open` + `onChange`:
886
+
887
+ ```tsx
888
+ <Dialog open={open} label="Title" onChange={setOpen}>...</Dialog>
889
+ <Drawer open={open} label="Menu" side="right" onChange={setOpen}>...</Drawer>
890
+ <Collapsible summary="Details" open={open} onChange={setOpen}>...</Collapsible>
891
+ <Popover label="Info" open={open} onChange={setOpen}>...</Popover>
892
+ ```
893
+
894
+ ### Paired Input/View Components
895
+
896
+ Several components come in interactive + read-only pairs:
897
+
898
+ | Interactive (Input/Picker) | Read-Only (View) |
899
+ | -------------------------- | ------------------------- |
900
+ | `FiveStarRatingPicker` | `FiveStarRatingView` |
901
+ | `FiveFaceRatingPicker` | `FiveFaceRatingView` |
902
+ | `NetPromoterScorePicker` | `NetPromoterScoreView` |
903
+ | `RedAmberGreenPicker` | `RedAmberGreenView` |
904
+ | `PostalCodeInput` | `PostalCodeView` |
905
+ | `MeasurementInstanceInput` | `MeasurementInstanceView` |
906
+
907
+ ### Composable Component Families
908
+
909
+ Many components form hierarchical families meant to be used together:
910
+
911
+ ```tsx
912
+ // Navigation: Nav → List → ListItem
913
+ <BreadcrumbNav label="...">
914
+ <BreadcrumbList>
915
+ <BreadcrumbListItem>...</BreadcrumbListItem>
916
+ </BreadcrumbList>
917
+ </BreadcrumbNav>
918
+
919
+ // Tables: Table → Head/Body/Foot → Row → Data
920
+ <DataTable label="...">
921
+ <DataTableHead><DataTableRow><th>...</th></DataTableRow></DataTableHead>
922
+ <DataTableBody><DataTableRow><DataTableTD>...</DataTableTD></DataTableRow></DataTableBody>
923
+ </DataTable>
924
+
925
+ // Forms: Form → Field → Input
926
+ <Form label="...">
927
+ <Field label="..." error={error}>
928
+ <TextInput label="..." value={v} onChange={setV} />
929
+ </Field>
930
+ </Form>
931
+
932
+ // Grail Layout: GrailLayout → Top/Left/Center/Right/Bottom
933
+ <GrailLayout>
934
+ <GrailLayoutTopHeader>…</GrailLayoutTopHeader>
935
+ <GrailLayoutLeftAside>…</GrailLayoutLeftAside>
936
+ <GrailLayoutCenterMain>…</GrailLayoutCenterMain>
937
+ <GrailLayoutRightAside>…</GrailLayoutRightAside>
938
+ <GrailLayoutBottomFooter>…</GrailLayoutBottomFooter>
939
+ </GrailLayout>
940
+ ```
941
+
942
+ ## Styling Guide
943
+
944
+ Since components are headless, you provide all styling. Every component renders a semantic CSS class on its root element:
945
+
946
+ ```tsx
947
+ // Component renders: <button class="button my-btn">
948
+ <Button className="my-btn">Click</Button>
949
+
950
+ // Component renders: <dialog class="dialog modal-lg">
951
+ <Dialog className="modal-lg" label="Title" open>Content</Dialog>
952
+ ```
953
+
954
+ ### With Tailwind CSS
955
+
956
+ ```tsx
957
+ <Button className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
958
+ Save
959
+ </Button>
960
+
961
+ <Card className="border rounded-lg shadow-md p-6">
962
+ <h3 className="text-lg font-bold">Title</h3>
963
+ <p>Content</p>
964
+ </Card>
965
+
966
+ <Alert type="error" heading="Error" className="bg-red-50 border-l-4 border-red-500 p-4">
967
+ Something went wrong.
968
+ </Alert>
969
+ ```
970
+
971
+ ### With CSS Modules
972
+
973
+ ```tsx
974
+ import styles from "./MyComponent.module.css";
975
+
976
+ <TextInput className={styles.input} label="Name" value={name} onChange={setName} />
977
+ <Button className={styles.primaryButton}>Submit</Button>
978
+ ```
979
+
980
+ ### With Plain CSS
981
+
982
+ ```css
983
+ /* Target built-in semantic classes */
984
+ .button {
985
+ padding: 8px 16px;
986
+ border-radius: 4px;
987
+ }
988
+ .alert {
989
+ padding: 12px;
990
+ border-left: 4px solid;
991
+ }
992
+ .alert[data-type="error"] {
993
+ border-color: red;
994
+ background: #fff0f0;
995
+ }
996
+ .alert[data-type="success"] {
997
+ border-color: green;
998
+ background: #f0fff0;
999
+ }
1000
+ .dialog {
1001
+ max-width: 480px;
1002
+ border-radius: 8px;
1003
+ }
1004
+ ```
1005
+
1006
+ ### Data Attributes for Styling
1007
+
1008
+ Many components expose data attributes for CSS-based styling:
1009
+
1010
+ | Component | Attribute | Values |
1011
+ | -------------------- | ----------------------------- | ------------------------------------------------ |
1012
+ | Alert | `data-type` | `info`, `success`, `warning`, `error` |
1013
+ | Badge | `data-type` | `default`, `info`, `success`, `warning`, `error` |
1014
+ | Banner | `data-type` | `info`, `success`, `warning`, `error` |
1015
+ | ClipboardCopyButton | `data-copied` | `true`, `false` |
1016
+ | AspectRatioContainer | `data-aspect-ratio-container` | ratio number |
1017
+
1018
+ ## Development
1019
+
1020
+ ### Prerequisites
1021
+
1022
+ - Node.js 18+
1023
+ - pnpm 9+
1024
+
1025
+ ### Setup
1026
+
1027
+ ```bash
1028
+ git clone https://github.com/lily/@lilydesignsystem/react-headless.git
1029
+ cd lily-design-system-react-headless
1030
+ pnpm install
1031
+ ```
1032
+
1033
+ ### Testing
1034
+
1035
+ ```bash
1036
+ # Run all tests (490 files)
1037
+ pnpm test
1038
+
1039
+ # Run tests in watch mode
1040
+ pnpm run test:watch
1041
+
1042
+ # Run tests with UI
1043
+ pnpm run test:ui
1044
+
1045
+ # Run a specific component test
1046
+ pnpm exec vitest run components/Button.test.tsx
1047
+ ```
1048
+
1049
+ ### Tech Stack
1050
+
1051
+ - **React 19** with TypeScript
1052
+ - **Vitest** for testing (not Jest)
1053
+ - **React Testing Library** for component tests
1054
+ - **@testing-library/user-event** for interaction simulation
1055
+ - **jsdom** for DOM environment
1056
+
1057
+ ### Testing Conventions
1058
+
1059
+ Tests use Vitest built-in matchers only. `@testing-library/jest-dom` is not used.
1060
+
1061
+ ```tsx
1062
+ // Correct — Vitest matchers
1063
+ expect(el).toBeTruthy();
1064
+ expect(el).toBeNull();
1065
+ expect(el.getAttribute("role")).toBe("button");
1066
+ expect(el.textContent).toContain("text");
1067
+ expect(button.disabled).toBe(true);
1068
+
1069
+ // Incorrect — jest-dom matchers (not used in this project)
1070
+ expect(el).toBeInTheDocument();
1071
+ expect(el).toHaveAttribute("role");
1072
+ expect(el).toHaveTextContent("text");
1073
+ ```
1074
+
1075
+ ## Accessibility
1076
+
1077
+ All components follow WCAG 2.2 AAA guidelines:
1078
+
1079
+ - **Semantic HTML**: Proper element usage (`<button>`, `<nav>`, `<dialog>`, etc.)
1080
+ - **ARIA attributes**: Roles, labels, states, and properties
1081
+ - **Keyboard navigation**: Full keyboard support for all interactive components
1082
+ - **Screen readers**: Proper announcements via aria-live, role="alert", etc.
1083
+ - **Focus management**: Logical focus order and visible focus indicators
1084
+ - **Error handling**: Accessible error messages linked via aria-errormessage
1085
+
1086
+ ### WAI-ARIA Patterns
1087
+
1088
+ Components implement standard WAI-ARIA Authoring Practices patterns:
1089
+
1090
+ | Pattern | Components |
1091
+ | ----------- | --------------------------------------------------- |
1092
+ | Button | Button, ToggleButton, SwitchButton |
1093
+ | Dialog | Dialog, AlertDialog |
1094
+ | Menu | Menu, MenuItem, MenuBar, MenuBarButton, ContextMenu |
1095
+ | Tabs | TabBar, TabBarButton |
1096
+ | Tree View | TreeNav, TreeList, TreeListItem |
1097
+ | Listbox | Listbox, Combobox |
1098
+ | Grid | DataTable, CalendarTable, GanttTable, KanbanTable |
1099
+ | Accordion | AccordionNav, AccordionList, AccordionListItem |
1100
+ | Breadcrumb | BreadcrumbNav, BreadcrumbList, BreadcrumbListItem |
1101
+ | Alert | Alert, AlertDialog |
1102
+ | Disclosure | Details, Collapsible, Expander |
1103
+ | Separator | Separator |
1104
+ | Meter | Meter |
1105
+ | Progressbar | Progress, ProgressCircle |
1106
+ | Slider | Slider, SliderButton, RangeInput, Dial |
1107
+ | Switch | SwitchButton |
1108
+ | Tooltip | Tooltip |
1109
+
1110
+ ## Internationalization
1111
+
1112
+ No strings are hardcoded in any component. All user-facing text comes through props:
1113
+
1114
+ ```tsx
1115
+ // All text is consumer-provided — use your i18n library
1116
+ <Button>{t("submit")}</Button>
1117
+ <Alert type="error" heading={t("error.title")}>{t("error.message")}</Alert>
1118
+ <Field label={t("form.name")} error={t("validation.required")}>
1119
+ <TextInput label={t("form.name")} value={name} onChange={setName} />
1120
+ </Field>
1121
+ <SkipLink label={t("navigation.skip_to_content")} />
1122
+ ```
1123
+
1124
+ ## Contributing
1125
+
1126
+ ### Adding a New Component
1127
+
1128
+ 1. Create `components/ComponentName.tsx` with exported interface and default function
1129
+ 2. Create `components/ComponentName.test.tsx` following the test file pattern
1130
+ 3. Create `components/ComponentName.md` with props documentation
1131
+
1132
+ ### Code Conventions
1133
+
1134
+ - All props interfaces end with `[key: string]: unknown` for rest props
1135
+ - Default `className` to `""` in all components
1136
+ - Use `...restProps` spread on the root element
1137
+ - camelCase for all callback props (`onChange`, `onClose`, `onSubmit`)
1138
+ - camelCase for HTML attributes in JSX (`autoComplete`, `tabIndex`, `htmlFor`)
1139
+
1140
+ ## Related Projects
1141
+
1142
+ - [Lily Design System](https://github.com/LilyDesignSystem/lily) — Parent project
1143
+ - [React Next.js Examples](../lily-design-system-react-next-examples/) — Example app
1144
+ - [HTML Headless](../lily-design-system-html-headless/) — Plain HTML equivalent
1145
+ - [Svelte Headless](../lily-design-system-svelte-headless/) — Svelte equivalent
1146
+ - [Vue Headless](../lily-design-system-vue-headless/) — Vue equivalent
1147
+ - [Angular Headless](../lily-design-system-angular-headless/) — Angular 20 equivalent
1148
+ - [Blazor Headless](../lily-design-system-blazor-headless/) — Blazor equivalent
1149
+ - [Nunjucks Headless](../lily-design-system-nunjucks-headless/) — Nunjucks equivalent
1150
+
1151
+ ## License
1152
+
1153
+ MIT or Apache-2.0 or GPL-2.0 or GPL-3.0, or contact us for more options.
1154
+
1155
+ ## Contact
1156
+
1157
+ Joel Parker Henderson (joel@joelparkerhenderson.com)
1158
+
1159
+ ---
1160
+
1161
+ Lily™ and Lily Design System™ are trademarks.