meta-design-composable-components
Composable Component Patterns
Quick Guide: The component owns behaviour, state and accessibility; the consumer owns markup, element type and styling. That line settles most API questions: variation becomes parts and children instead of booleans and
renderXprops, each exposed state ships asvalue+defaultValue+onValueChangewith a details argument carrying the reason and a veto, every part can render as the consumer's element through one substitution shape, state is published asdata-*attributes, and whatever the consumer passes — props, handlers, refs — is composed rather than replaced. Three React 19 facts change the code:refis an ordinary prop, a callback ref may return a cleanup that a ref-composing helper has to collect, anduseId's format changed in both 19.1 and 19.2, with only 19.2's safe in both a CSS selector and an XML id.
Detailed Resources:
- examples/core.md — Patterns 1–3: compound parts, children with a descendant registry, context scoping
- examples/state-contract.md — Patterns 4–5:
useControllableState, change details with reason and cancel, retrofitting the triple, state asdata-*attributes - examples/polymorphism.md — Patterns 6–7:
asChildandrenderbuilt without a library, the merge they share,composeEventHandlers,composeRefs, prop ordering - examples/accessibility-structure.md — Pattern 8: id wiring, focus ownership, roving tabindex, typeahead, Escape
- reference.md — prop-to-part translation, naming tables, substitution merge rules, attribute vocabulary, ARIA and keyboard contracts, the alignment checklist
Which path applies
- Designing a new component — settle the parts (Patterns 1–3), then the state contract (4–5); substitution and forwarding (6–7) apply to every part you defined. Start at examples/core.md.
- Aligning an existing component — read its call sites, run it through the alignment checklist, and fix API shape first, because the later axes depend on which parts exist. Migrate additively, as retrofitting the triple shows.
- Building on a headless library that already ships parts — use its substitution shape and its merge helper on every part you add, so yours behave like its own; the library's API is its own skill's subject, and examples/polymorphism.md shows what any merge has to guarantee.