Skip to content

Separate design from behaviour

Design systems get stuck when they own too many layers at once.

They pick React. Then they pick a primitive library. Then the visual language is whatever those primitives look like after a theme pass. Swap the engine, or swap Radix for Base UI, and the “design system” has to be rebuilt.

The useful split is:

  • Design — tokens, layout, pattern look, focus rings, contrast.
  • Behaviour — keyboard map, ARIA widgets CSS cannot express, focus trap, typeahead.
  • Engine — React, Angular, Vue, HTML.

Airframe is the design layer. CSS-only. Zero runtime. Your framework provides the engine. A primitive library, or native HTML, provides behaviour.

Native HTML is the default behaviour layer

Section titled “Native HTML is the default behaviour layer”

Buttons, links, inputs, checkboxes, <dialog>, <details>. Use them. Airframe styles them.

<button type="button" class="af-btn" onclick="document.getElementById('confirm').showModal()">Save</button>
<dialog class="af-dialog" id="confirm">
  <form method="dialog">
    <div class="af-dialog-header">
      <h2 class="af-dialog-title">Discard draft?</h2>
    </div>
    <div class="af-dialog-footer">
      <button class="af-btn">Close</button>
    </div>
  </form>
</dialog>

You still call showModal(). That is a few lines in the app or kit. It is not a reason to adopt a full widget runtime.

When native is enough, stop. Do not wrap a checkbox in a primitive library “for consistency.”

Menus with arrow keys. Combobox. Date picker. Tabs that are not links. A drawer that traps focus.

Those are behaviour problems. Good tools already exist:

EngineBehaviour you might sit on Airframe
ReactReact Aria, Base UI, Zag, Radix
AngularCDK, your own FocusTrap, overlay services
VueHeadless UI, Ark, Reka
AnyNative <dialog>, plus a small kit helper

Airframe does not replace those. It should not grow an @airframeui/react-aria either. The primitive stays swappable. The look does not.

shadcn moving toward interchangeable Base UI / React Aria foundations is the same split: do not make the design system own the primitive layer. Airframe and shadcn.

The kit owns the widget. The widget renders Airframe classes. The primitive owns state and keyboard.

React (shape, not a required dependency)

// Primitive handles open / focus / ARIA.
// Airframe handles the surface.
export function Dialog({ children, ...props }) {
return (
  <Primitive.Root {...props}>
    <Primitive.Backdrop className="af-backdrop" />
    <Primitive.Panel className="af-dialog">{children}</Primitive.Panel>
  </Primitive.Root>
);
}

Angular

The overlay service stays yours. The panel is still af-dialog. Tokens are still --af-*. Product templates never see a second palette.

Page layout stays Airframe either way: af-stack, af-inline, af-grid. Do not let the primitive library introduce a layout language.

CSS pretends to be JS.
A “menu” class with no keyboard support, advertised as accessible. Airframe documents pattern contracts and native behaviour. It does not claim a shared keyboard map across frameworks. That claim belongs to widget kits. Framework-agnostic UI is not a design system.

JS pretends to be the design system.
Every product looks like the primitive’s demo until someone writes another theme. Tokens leak into style={{}}. Spacing is magic numbers. Angular cannot come along for the ride.

If you already have the primitive, keep it. Map paint to --af-*. Wrap the trigger in af-btn. Do not restyle a cousin.

Airframe builds in what CSS can own: visible focus, reduced motion, contrast-minded tokens, high-contrast themes, RTL-ready layout, semantic HTML defaults.

What CSS cannot own: focus trap, aria-controls wiring, typeahead. Your content and your JS still matter. Aim for WCAG 2.2 AA. Do not treat the CSS package as a compliance certificate.

Pattern-level notes: Accessibility, Pattern contracts.

Tokens only: map --af-*, keep your current Dialog.
Layouts: structure pages, keep your widgets.
Patterns: af-btn / af-card as the look inside those widgets.

Optional a11y JS (focus trap and similar) is later, as a layer on Airframe, not a rewrite of core. Roadmap.