Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    Headless Component Libraries

    A library shared across products never fits any one of them perfectly — ship structure and behaviour, and let each app own the styling.

    Growingtended planted 1 min read

    A component library that serves more than one product has a problem baked in: every product has its own look, and an opinionated library will be wrong for most of them. Teams end up fighting the styles, forking the component, or wrapping it in so many overrides that upgrades become scary.

    The way out is to ship behaviour and structure, not looks. Headless libraries such as Radix do this well: they handle focus management, keyboard interaction, ARIA and state, and hand you unstyled parts to dress however you like.

    What that looks like in practice

    • Compound components. Instead of one component with forty props, expose parts that compose: <Graph>, <Graph.Canvas>, <Graph.Toolbar>, <Graph.ZoomControls>. Consumers rearrange or omit parts without a new prop for every layout.
    • Events, not opinions. Emit what happened (onNodeSelect, onEdgeCreate) and let the app decide what it means.
    • Minimal default styling, exposed through tokens or CSS variables, so theming is configuration rather than surgery.
    • A story per requirement. Storybook doubles as documentation, a progress demo for stakeholders and a home for interaction tests.

    The trade-off

    Headless means more work for each consumer on day one. It pays off on day one hundred, when three products are using the same library and nobody has forked it. I learnt this most clearly while Building a Graph Component Library.

    • #component-libraries
    • #design-systems
    • #react
    • #storybook

    ↪ Links to (1)