Kai Jenkins
Writing

July 30, 2026 · 10 min read

Building Framework-Agnostic Component Foundations

A practical architecture for sharing design-system behavior, accessibility, tokens, and contracts across frameworks without collapsing into a lowest-common-denominator UI.

By Kai Jenkins

In this article14 sections

Framework-agnostic does not mean framework-free.

It means the product contract survives a framework boundary.

A shared component foundation should let teams build with different runtimes while preserving the parts that must not drift: semantics, interaction behavior, accessibility, design tokens, state transitions, and release expectations. If every adapter quietly reinterprets those decisions, the organization has several design systems that happen to share a name.

The architecture therefore begins with a contract, not a packaging format.

Decide what must remain invariant

Before choosing custom elements, headless primitives, generated wrappers, or any other implementation strategy, identify the guarantees every consumer should receive.

Those guarantees usually include:

  • the component’s purpose and appropriate use;
  • accessible role, name, state, and keyboard behavior;
  • supported states and transitions;
  • content and composition rules;
  • semantic styling hooks;
  • input and output contracts;
  • responsive behavior;
  • validation and error communication; and
  • lifecycle and migration policy.

These are product decisions. A React prop type or a Vue template is only one expression of them.

Write the contract in language that can be tested independently of a framework. “Pressing Escape closes the topmost dismissible layer and returns focus to its trigger” is portable. “Calls a state setter in this hook” is not.

That distinction prevents the shared layer from becoming an accidental copy of whichever framework was dominant when the system started.

Use the platform as the common denominator, not the lowest one

The web platform is the only runtime every web framework ultimately shares.

Native HTML gives controls their initial semantics, form behavior, keyboard expectations, and interoperability with assistive technology. CSS provides the cascade, inheritance, media and container queries, and custom properties. DOM events define how changes travel across component boundaries.

Start there.

A button foundation should prefer a real button. A navigation link should remain a link. A labeled input should participate in the form and accessibility trees without requiring a client runtime to reconstruct the relationship.

Custom behavior is sometimes necessary, but native semantics reduce the contract a shared library must implement and maintain. They also improve failure behavior when JavaScript loads late, an adapter fails, or a page is rendered on the server.

The goal is not to avoid abstraction. It is to place abstraction above a reliable platform baseline.

Treat custom elements as one delivery mechanism

Web Components provide standardized custom elements, shadow DOM, templates, and slots. They can be a strong fit when the same interactive implementation must run inside several frameworks.

They are not automatically the right boundary for every component.

Custom elements introduce decisions about:

  • when an element upgrades;
  • how attributes and properties synchronize;
  • whether shadow DOM helps or obstructs composition;
  • how server-rendered content behaves before upgrade;
  • how form participation works;
  • how events cross the shadow boundary; and
  • how framework-specific developer ergonomics are restored.

Use them when shared runtime behavior is worth those constraints. For simpler primitives, a portable stylesheet, semantic markup contract, and thin framework implementation may be easier to operate.

Framework-agnostic describes the system outcome, not a mandate that every component use the same technical mechanism.

Design an explicit element contract

Portable components need a small, unsurprising public surface.

Separate the kinds of input:

  • Attributes describe serializable configuration that should be visible in markup.
  • Properties carry richer values and current runtime state.
  • Slots or children provide authored content and composition.
  • Methods expose imperative behavior only when a declarative input cannot express it cleanly.

React’s current custom HTML element guidance makes the attribute and property distinction explicit. Other frameworks expose it differently, which is precisely why the foundation should define the intended channel instead of leaving adapters to guess.

For each input, define:

  • its type and default;
  • whether it reflects to an attribute;
  • whether changing it after connection is supported;
  • how invalid values behave;
  • whether it affects accessibility state; and
  • whether it is safe to serialize on the server.

Avoid a large bag of loosely related flags. A portable API magnifies every ambiguity because each adapter, documentation site, test harness, and consumer must interpret it consistently.

Make events describe domain changes

Outputs should explain what happened, not expose internal implementation.

Prefer events such as “selection changed,” “dialog requested close,” or “value committed” over names tied to a particular internal handler. Define the event payload, cancellation behavior, timing, and whether the event represents an intent or a completed change.

Shadow DOM makes propagation part of the contract. A custom event must bubble and be composed when consumers outside the shadow root are expected to observe it. The platform’s Event.composed behavior is explicit: propagation across a shadow boundary depends on that setting, and normal bubbling still requires bubbles.

Adapters should translate these events into the framework’s conventions without changing their meaning. If the React adapter fires before a state change while the native element fires after it, consumers no longer share one contract.

Keep accessibility in the foundation

Accessibility is not adapter polish.

The shared contract should own:

  • native element selection;
  • accessible names and descriptions;
  • roles, states, and relationships;
  • focus movement and restoration;
  • keyboard interaction;
  • disabled and read-only behavior;
  • error and status announcements;
  • high-contrast behavior; and
  • reduced-motion behavior.

The ARIA Authoring Practices Guide connects interface patterns with their expected semantics and keyboard models. It is useful guidance, but even a well-chosen pattern still has to be tested in the component’s real composition and target browsers.

Adapters can provide ergonomic label props or framework refs. They should not independently decide how a combobox navigates or where focus returns when a dialog closes.

Test the accessibility tree and keyboard sequence at the lowest shared layer, then repeat a focused integration suite through each adapter. This catches both foundation defects and translation defects.

Move design decisions through tokens

Shared components should consume semantic decisions rather than framework-specific theme objects.

The stable Design Tokens Format Module defines typed values, groups, references, and alias resolution. Its value is not limited to a particular file format: it gives a cross-platform pipeline a clear distinction between a raw value and the semantic relationship that value represents.

A useful token flow separates:

  1. Foundation values such as ramps and dimensions.
  2. Semantic roles such as surface, text, border, or focus.
  3. Component roles such as a button’s background or a field’s invalid border.
  4. Context resolution for theme, contrast, density, or platform.
  5. Platform output such as CSS custom properties.

Preserve aliases until the point where a platform requires resolved values. If every output contains only literal colors and dimensions, the semantic relationships disappear and migrations become harder to automate.

The Design Tokens Resolver Module also models contextual resolution for themes and other modes. That reinforces a useful architectural rule: components should consume resolved semantic roles, while the token pipeline decides which context produced them.

Use CSS custom properties as a narrow styling API

CSS custom properties cross many component and framework boundaries cleanly because they participate in the cascade and inheritance. MDN’s custom-property guidance documents this behavior and the additional control available through registered properties.

Expose a deliberate set of semantic properties rather than every internal style.

Good styling hooks represent stable product decisions:

  • control background and foreground;
  • focus indicator;
  • field boundary;
  • spacing density;
  • motion duration; and
  • typography roles.

Avoid exposing internal selectors or dozens of one-off variables that freeze the component’s markup. Consumers need enough control to participate in the product theme, not enough access to fork the component through CSS.

Shadow parts, custom properties, and light-DOM composition each create different levels of control. Choose them based on the intended ownership boundary and document which surface is stable.

Keep adapters thin, but not hostile

A framework adapter should feel native to its consumers.

It may need to:

  • map naming conventions;
  • translate properties and attributes;
  • bridge events into callbacks;
  • expose refs;
  • integrate form libraries;
  • preserve type inference; and
  • accommodate server-rendering conventions.

Thin does not mean zero code. It means product behavior remains in the foundation while ergonomics live in the adapter.

Do not make every consumer understand DOM upgrade timing or manually attach event listeners just to prove the component is framework-neutral. If the shared solution is technically portable but unpleasant in every framework, teams will wrap it inconsistently or avoid it.

The adapter is successful when it feels conventional without becoming a second implementation.

Design for server rendering and delayed upgrade

A framework-agnostic component should have a useful initial state before client code finishes.

For content-oriented components, render meaningful HTML that can be read and navigated without upgrade. For interactive components, ensure the initial markup does not advertise behavior that is unavailable. Avoid layout shifts caused by late token or style registration.

Define:

  • what the server renders;
  • whether declarative shadow DOM is used;
  • what happens before custom-element registration;
  • how initial properties reach the element;
  • how hydration or upgrade avoids duplicate work; and
  • what fallback remains when JavaScript fails.

This is also an accessibility concern. A control that looks interactive before it can receive or process input creates a misleading state even if it becomes correct a second later.

Version the contract across packages

A multi-framework system often ships several artifacts:

  • the component runtime;
  • generated token outputs;
  • adapter packages;
  • type definitions;
  • documentation; and
  • testing utilities.

Their compatibility must be explicit.

Publish a support matrix and automate it. Prevent an adapter from consuming an incompatible foundation version. Include deprecation warnings and migrations at the public contract level, then propagate them through generated types and documentation.

Avoid synchronizing every package version when they do not all change, but make supported ranges unambiguous. Independent versioning without compatibility automation simply moves integration failures to consumers.

Test one contract through every surface

The most valuable test suite is a conformance matrix.

For each component contract, verify:

  • server-rendered structure;
  • upgrade and initialization;
  • input synchronization;
  • event ordering and payloads;
  • keyboard and focus behavior;
  • accessible names, roles, and states;
  • token and theme resolution;
  • responsive behavior;
  • form participation where relevant; and
  • behavior inside each supported adapter.

Run the same behavioral cases against the native delivery and adapter fixtures. Framework-specific tests should cover translation, not redefine the expected result.

Visual regression is useful for resolved themes and layout states, but it should sit beside semantic and behavioral assertions. A matching screenshot cannot prove that an event crossed a shadow root or that focus returned correctly.

Avoid the lowest-common-denominator trap

Portability can become an excuse to remove useful capabilities.

Do not restrict every component to the features that all frameworks express identically. Instead, define a strong shared contract and provide controlled extension points:

  • authored content through slots or children;
  • semantic styling through tokens;
  • framework-native composition around the shared primitive;
  • optional behaviors with explicit capability detection; and
  • escape hatches that remain outside the supported contract.

The shared foundation should standardize high-value decisions while leaving product teams room to compose. A platform becomes more reusable when its boundaries are clear, not when it tries to own every possible interface.

Framework neutrality is an operating model

The durable architecture is not a clever wrapper generator.

It is a system where product semantics, accessibility, events, tokens, versioning, and tests have clear ownership independent of a framework. Web standards provide the common runtime. Adapters provide familiar ergonomics. Conformance tests prove that both tell the same story.

When those layers stay deliberate, teams can change frameworks without renegotiating the product contract—and the design system can evolve without fragmenting into parallel implementations.