Skip to content

Why we bet on web components for a 9-year-old design system

A framework-agnostic design system was a contrarian choice in 2017. It's the reason the system is still standing in 2026, and why it could absorb generative UI without a rewrite.


The bet

When AXS started, framework discourse looked like this: pick Vue or React or Angular and commit. Web components were “interesting but not ready.” Cross-framework was either a tooling problem you solved with bundlers or a problem you didn’t have because you only used one framework.

We had teams on Angular, on AEM (Sightly/Java), and a growing Vue contingent. Picking one framework would have orphaned the others. Building three component libraries would have ensured none of them stayed in sync.

So we made a different bet: components are standards-based web components, custom elements with shadow DOM. The internal implementation could be Vue. The shipped artifact is a custom element that any framework can render.

That bet looked weird in 2017. It looks obvious in 2026.

What “framework-agnostic” actually means

It’s not a slogan. It’s a set of constraints that compound:

  • Components compile to custom elements. Internal Vue/Lit/React is an implementation detail. Consumers get <axs-button>, not a framework import.
  • Public API is the HTML attribute surface. Props become kebab-case attributes. Events are CustomEvents. Slots are slots. The contract is what HTML supports.
  • State stays inside the component. Shadow DOM, scoped CSS, internal stores. Consumers don’t reach in; they configure via attributes and listen for events.
  • Framework adapters wrap, never replace. axs-vue and axs-angular are thin convenience layers, typed props, declarative event binding, but the underlying tag is the same custom element. A consumer can use raw HTML and get identical behavior.

The contract that holds across all of this: the component’s public API is what the browser already understands.

The trade-offs we accepted

This is not a free architecture. The real costs:

  • Initial complexity is higher. A single-framework library is simpler to build. You don’t need a custom-element wrapper layer, you don’t need to think about shadow DOM piercing, you don’t need framework adapters.
  • State management is harder. Cross-component state has to flow through attributes, events, or external stores. There’s no React-Context shortcut.
  • Styling discipline is non-negotiable. Shadow DOM means a component can’t borrow styles from its host. Tokens have to ship as CSS custom properties because they’re the only thing that pierces the shadow boundary. You design for it from day one or you suffer.
  • Build pipelines are non-trivial. You’re shipping multiple artifacts: the custom element, a Vue adapter, an Angular adapter, IDE metadata, type definitions, design tokens. Each of these is a deliverable.

If you only have one framework and you’re sure you’ll only ever have one framework, this is overkill. If you have more than one framework, or you’re not sure about the next ten years, the math flips.

The dividend, years later

What we got, without anticipating most of it:

Vue → Lit refactor in two months. Because consumers depend on the custom element, not the Vue implementation, we could replace the internal framework without changing a line of consumer code. AI-assisted coding made the speed possible; the architecture made the swap possible at all.

AEM integration as a thin client. AEM templates declare <axs-button>. The CMS doesn’t import anything. It just declares the custom element and gets the same behavior as a Vue app, same component, same styles, same events. The thin-CMS pattern only works because the component is framework-agnostic.

Gen UI without rewriting components. When we built a two-stage Claude tool-use pipeline that picks components and lays them out, the LLM’s job was easy: name a component, fill in attributes. The renderer in Svelte just instantiated the custom elements. Same components, new substrate.

MCP-readable architecture. The component metadata is already standards-based. Generating a web-types JSON schema for AI assistants was a build script away. Framework-specific component libraries would have needed an explicit metadata translation layer.

None of this was on the 2017 roadmap. All of it was made possible by the same architectural choice.

What I’d do differently if starting over

Knowing what I know now, I’d still make the same core bet, and I’d be more aggressive about a few things earlier:

  • Generate the IDE metadata from day one. We added it later. It should have been in the original build pipeline. It’s the prerequisite for every AI integration that came after.
  • Establish the token system before the components. CSS custom properties have to be load-bearing for theming. Retrofitting that into existing components is annoying.
  • Write the adapter contracts as types. Each framework adapter (axs-vue, axs-angular) should have a generated TypeScript surface that mirrors the custom-element attribute schema. We hand-maintained these for too long.
  • Skip the IE11 polyfills entirely. We supported IE11 longer than necessary. Web components in legacy browsers are painful and the audience was small. Be ruthless about cutting it.

When this pattern is wrong

Worth saying explicitly. Framework-agnostic web components are the right bet when:

  • You have, or expect to have, multiple consuming frameworks.
  • The design system needs to outlive specific framework choices.
  • You’re investing in tooling, build infrastructure, and team discipline.
  • You’re willing to design for shadow-DOM constraints from the start.

It’s the wrong bet when you have one framework, a small team, a short horizon, or no appetite for the complexity overhead. Don’t build framework-agnostic infrastructure to solve problems you don’t have. The architecture only pays off across multiple consumers and multiple years.

If you do have those things, or you’re trying to plan for them, web components are still the bet. They’ve gotten better every year since 2017. They’ll keep getting better. And the design system you build on top of them gets to compound while everything else churns.