All articles
Architecture

The Headless Design System, Explained

JUL 8, 2026 · 8 min read · Subin

Headless is one of those words that sounds like jargon until you have felt the problem it solves. The problem is that most component libraries fuse two very different things, behaviour and appearance, and then charge you twice every time either one needs to change.

Coupled by default

A typical button component bakes three things into one file: the logic (focus, keyboard, pressed and loading states, the ARIA role), the structure, and the paint (colour, radius, padding, the exact shade of the hover). It looks tidy until the day you need a second brand, a new framework, or a visual refresh.

Now every visual change risks the accessibility you already got right, and every behaviour fix has to be reapplied across each themed copy. Teams respond by forking, and a fork is where a design system quietly starts to die.

What headless actually means

A headless component owns the behaviour and nothing else. It manages state, keyboard interaction, focus, and accessibility, and exposes them through props and hooks. It has zero opinion about colour, shape, or spacing. You bring the skin.

Split that way, the hard, invisible work (the part that is easy to get subtly wrong) is written once and audited once. The paint becomes a thin, swappable layer on top.

Headless logic core
state: idle · hover · pressed · loading
keyboard + focus + ARIA role
onPress, disabled, async pending
zero opinions about colour or shape
One logic core. Switch the skin; the behaviour, focus, and accessibility never change.
Accessibility is the quiet winKeyboard support, focus management, and ARIA are where component bugs hide. In a headless core you solve them one time, and every brand and theme built on top inherits the fix. One accessibility review serves the whole product.

Why it pays off: multi-brand and multi-framework

Once behaviour and paint are separate, a rebrand is a new skin rather than a rewrite. Two products can share one interaction model and look nothing alike. Support a new framework and you port the thin presentation layer, not the accessibility you already trust.

This is exactly why the same button can read as calm and corporate in one product and loud and playful in another while behaving identically in both. The behaviour is a contract; the look is a choice.

The division of labour

  • The core owns: state machine, keyboard and focus, ARIA roles and relationships, disabled and pending handling, event callbacks.
  • The skin owns: colour, radius, spacing, typography, motion, density, iconography.
  • Tokens sit between them, so the skin is driven by named decisions rather than hardcoded values.

The honest tradeoff

Pure headless asks more of you up front. Bare logic with no default styling is powerful and also a blank page, and a blank page is where consistency erodes. The answer is not to abandon headless; it is to ship a strong default skin on top of it.

That is the line Optimistic walks. The behaviour is headless underneath, but you are handed a complete, on-brand skin so the fast path is a working component, not a wiring exercise. Keep the skin and you move fast; replace it and the behaviour still holds.

Behaviour is a contract you keep. The look is a choice you make.On headless architecture

Where pure headless goes wrong

The failure mode of headless is not technical; it is the blank page. Hand a team bare logic with no styling and consistency erodes immediately, because every team paints the button differently and you are back to the drift you were trying to escape. Freedom without a default is just fragmentation with extra steps.

The fix is not to abandon the separation but to ship a strong default skin on top of it. Keep the skin and you get consistency for free; replace it deliberately for a second brand and the behaviour underneath never moves. Headless is the foundation, not the whole house.

Headless is not about having no styles. It is about deciding, on purpose, where behaviour ends and paint begins, and never letting one hold the other hostage again.