design-module-composition
Design Module Composition
Concept of the skill
Design module composition is the discipline of shaping a reusable component's public API so that its variation is achieved by composing exposed parts rather than by configuring a growing set of props. A composable module externalizes its internals as a contract — most often through one of four mainstream patterns: compound components (a parent and a named set of children share context, like Tabs / Tabs.List / Tabs.Trigger / Tabs.Panel), slot/children APIs (named slots accept arbitrary content), render props or function-as-children (the parent supplies state and the consumer supplies markup), and headless primitives (state and behavior leave the module entirely as hooks or unstyled components, leaving all markup and styling to the consumer). The polymorphic "asChild"/"as" pattern lets a consumer swap the rendered element while inheriting behavior and ARIA wiring. The central design judgment is the trade between configuration and composition: a prop-heavy API is fast for the common case but brittle for unanticipated variants, while a composition API costs more typing up front and absorbs variants with no friction — which is why mature systems often offer both, a high-level summary component built on low-level composable primitives. The module's real API is the context contract between parent and children, not the prop signatures, so the discipline asks for every variation whether it belongs to the module's identity (make it a prop) or to how a specific consumer uses the module (make it a slot).
Coverage
A composable component module exposes its parts to consumers rather than hiding them behind a configuration prop. The four mainstream patterns are compound components (a parent and a named set of children share context: , <Tabs.List>, <Tabs.Trigger>, <Tabs.Panel>), slot/children APIs (named slots accept arbitrary content: ), render props or function-as-children (the parent provides state, the consumer provides markup: {({open}) => ...}), and headless primitives (state and behavior are exposed as hooks or unstyled components — Radix, Headless UI, TanStack Table — leaving all markup and styling to the consumer).
The "asChild" or polymorphic pattern (Radix's term; also called "as" prop, "render" prop in some libraries) lets a consumer change the rendered element while inheriting all behavior: <Dialog.Trigger asChild>Open</Dialog.Trigger>. The pattern collapses two-level wrappers and avoids the "button inside button" accessibility error, but requires the parent to clone or render-prop its single child carefully.
Choosing between configuration and composition is a trade-off between control surface and expressiveness. A prop-heavy API () is fast to consume for the common case and friction-heavy for variants the original author didn't anticipate. A composition API (<Card.Header>...</Card.Header>) reverses this: more typing for the common case, no friction for variants. Mature design systems often offer both: a high-level "summary" component that consumes the low-level composable primitives.
State sharing between compound-component pieces uses React context (or framework-equivalent). The context contract — what the parent provides, what the children expect — is the real API of the module, and changing it is a breaking change even when the prop signatures stay the same. Headless primitives push this further: state and behavior leave the module entirely, and the visual layer is the consumer's responsibility.
Philosophy of the skill
Composition externalizes variation. Every boolean prop on a component is a decision the module author made on behalf of every future consumer; every slot is a decision deferred. The discipline is to ask whether the variant being added is part of the module's identity (it should be a prop) or part of how a specific consumer uses the module (it should be a slot).
Headless primitives separate three concerns that are routinely conflated: state (open/closed, selected, expanded), behavior (focus trapping, keyboard navigation, ARIA attribute wiring), and presentation (markup and styles). Conflation is convenient until the design system needs a second visual treatment of the same behavior; separation makes that addition trivial.