Skip to content

Framework-agnostic UI is not a design system

“Framework-agnostic” is doing too much work in this category.

People searching it usually want one of two products. They are not the same product.

1. Widget portability I want a Dialog (or Checkbox, or Combobox) that works in React, Vue, and Angular. Same DOM. Same ARIA. Same keyboard map. Same theming hooks. One implementation, several bindings.

2. Design-system portability I want one look and one layout language across products. React this year. Angular in the other app. HTML in a marketing page. An agent emitting screens next quarter. The brand should not fork.

A kit that solves (1) can still leave you with five design systems. A CSS contract that solves (2) will not give you a shared JS keyboard map.

They share implementation.

That is valuable when the hard part is behaviour: typeahead, focus trap, disabled dates, virtualised lists. Native HTML does not cover it. You want one well-tested primitive and thin wrappers.

The cost is the component model. You adopt their DOM, their props, their theming API, their versioning. The design system becomes “whatever that kit looks like when we theme it.” Page layout is still ad hoc. Tokens may or may not match Figma. Angular and React stay consistent only where you used their widgets.

Airframe is not racing that. If you need that Dialog, use it. Put it on a visual contract. Do not ask it to be the contract.

Not the widget class. The invariants.

InvariantTypical home
Colour, space, type, radius, motionTokens
Page structureLayout grammar (stack, inline, grid)
Button / card / input lookPatterns or wrappers over those patterns
Focus, contrast, reduced motion defaultsTokenised CSS, not a React context
How agents are allowed to name thingsRules and a class reference

Those can live in CSS. Then every engine is a consumer.

Your design system

CSS contract (tokens, layouts, patterns)

Angular · React · Vue · HTML · AI

The markup is the design intent. Framework files only bind.

<header class="af-inline af-justify-between">
  <h1>Team</h1>
  <button class="af-btn">Invite</button>
</header>

React uses className. Angular uses class. Vue uses class. There is no @airframeui/react. That absence is the architecture. How to keep one design system across React and Angular.

Airframe ships 57 CSS patterns. Behaviour is native HTML: <button>, <dialog>, <details>, form controls.

That means:

  • You get the same look without a JS runtime.
  • You do not get “the same keyboard map” for a menu in every framework from @airframeui/core.
  • Toasts, comboboxes, and rich overlays still need kit JS or a primitive library.

Teams that wanted a portable component will bounce if the site pretends otherwise. Teams that wanted a portable design system will bounce if you ship three framework packages and call that the product.

Separate design from behaviour.

shadcn is a distribution model: own the React source in your repo. Often on Radix, Base UI, or React Aria.

That is still problem (1), in one engine, with a better ownership story. It does not give Angular a shared grammar. You can wrap af-btn inside a shadcn-style file. Airframe and shadcn.

Tailwind shares utilities. The design system is whatever you compose this week. Two apps will not compose the same way.

Bootstrap shares components in CSS (and some JS). Closer to a look. The layout language is still mixed utilities and components, and the brand is Bootstrap until you override it.

Airframe starts with structure: named layout, tokens, patterns. Comparison: Airframe vs Bootstrap vs Tailwind.

You can take tokens and ignore patterns. You can take layouts and keep your own buttons. You can wrap af-btn and never see a class in product templates.

You cannot take a three-framework widget kit and assume the design system problem is solved. You also cannot take Airframe and assume the combobox problem is solved.

One source of look. One grammar. Engines and behaviour libraries stay swappable. Use as much as you need.