@lilydesignsystem/react-headless 0.5.1 → 0.6.1

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