← Back to Website builders

Skill · Version 1.0.0

Composable component APIs

Designs shared components with a small predictable interface and flexible composition while retaining native interaction behavior.

component-api-composition

Team members using this skill

See how it helps

When is it useful?

Use when shared components accumulate conflicting options or cannot accommodate real screen content.

What does it help you do?

  • Replaces incompatible boolean combinations with clear variants and constrained sizes.
  • Provides slots or compound components for flexible content and shared state.
  • Preserves native attributes, focus references and safe button defaults.

Try asking this

Redesign a card with twelve header options using composable title, actions and optional badge regions. Demonstrate a plain card and one with long text and two actions. Invalid combinations should become difficult to express without multiplying boolean settings.

Detailed documentation

Read the skill’s original instructions, examples and checks in their authored language.

Details…Show less

Component API & Composition

A shared component is only as good as its public interface. Following John Ousterhout's principle of deep modules and Nathan Curtis's component API discipline, hide internal styling, state synchronization and accessibility wiring behind a small, predictable interface while giving consumers clean composition seams.

1. Preventing Boolean Prop Explosion ("Prop Apocalypse")

  • Use mutually exclusive union variants instead of conflicting booleans:
    • Bad: <Button primary secondary danger outline small large /> (allows illegal combinations like primary + danger + small + large).
    • Good: variant: 'primary' | 'secondary' | 'ghost' | 'danger' and size: 'sm' | 'md' | 'lg'.
  • Do not add a prop for every one-off layout tweak: if a single screen needs an extra badge and a link inside a card header, expose a header slot / children / compound subcomponent rather than adding badgeText, badgeColor, headerLinkUrl and headerLinkLabel props to Card.

2. Composition Over Monolithic Configuration

Choose the right composition pattern for the component's complexity:

  • Slots / Named Regions: for structured containers (Card, PageHeader, EmptyState, FormField, Alert), accept content slots (leadingIcon, title, actions, footer, default body) so consumers can pass rich, localized content without string-only limitations.
  • Compound Components: for multi-part interactive widgets (Dialog.Root, Dialog.Trigger, Dialog.Content, Dialog.Title, Dialog.Description, Dialog.Close, or Tabs.List / Tabs.Trigger / Tabs.Panel), share internal state and ARIA IDs through component context/provide-inject while letting the consumer control structure and order cleanly.
  • Controlled & Uncontrolled Parity: for stateful inputs and overlays (open / onOpenChange, value / onChange), support deterministic controlled usage and sensible uncontrolled defaults where appropriate to the framework.

3. Transparent Native Attribute & Ref Forwarding

A design-system wrapper must never swallow essential HTML semantics or event handlers:

  • Forward native attributes: allow standard HTML, aria-*, data-*, id, name, disabled, type and event attributes to reach the underlying interactive DOM element (<button>, <input>, <a>).
  • Safe defaults: default <button> components to type="button" unless type="submit" is explicitly passed, preventing accidental form submissions inside dialogs and toolbars.
  • Ref / Element access: forward DOM refs so parent flows, focus managers and positioning engines (popovers, tooltips) can measure or focus the underlying element without reaching into undocumented .querySelector() internals.
  • Encapsulated internal DOM: treat internal wrapper elements and private CSS class names as implementation details, not public API — consumers should customize via documented props, slots and semantic tokens, never by writing parent CSS selectors targeting internal child tags.

Included files 1

  • SKILL.md