Skip to content

Design System with Lit

Because Lit components are real custom elements, they're a natural fit for a shared design system usable across multiple frameworks. This lesson covers the key architectural decisions involved.

Why Lit Suits Framework-Agnostic Design Systems

A design system's components typically need to be usable from many different teams' codebases — some on React, some on Angular, some on plain server-rendered HTML. Because a Lit component compiles down to a genuine custom element with no framework runtime dependency, it can be consumed identically from any of these, avoiding the need to maintain parallel component implementations per framework.

This is exactly why several major companies' internal and public design systems (and some open-source UI libraries) are built with Lit or plain Web Components: the investment in building and maintaining one canonical component implementation pays off across every consuming team's technology choice.

// button.ts — one implementation, usable everywhere
@customElement('ds-button')
export class DsButton extends LitElement {
  @property() variant: 'primary' | 'secondary' = 'primary';
  static styles = css`:host { display: inline-block; }`;
  render() {
    return html`<button class=${this.variant}><slot></slot></button>`;
  }
}

// Usable identically in React JSX, Angular templates, Vue templates, or plain HTML:
// <ds-button variant="primary">Save</ds-button>

The exact same compiled <ds-button> works across every consuming framework without a separate wrapper implementation per framework.

Core Design System Architecture Decisions

1. Theming: CSS custom properties as the primary theming API
2. Distribution: published npm package(s), versioned semantically
3. Documentation: a living style guide (e.g. Storybook) showing every component/variant
4. Testing: visual regression + accessibility checks per component
5. Governance: a clear process for proposing new components/variants
  • Design tokens (colors, spacing, typography) are best expressed as CSS custom properties, shared across every component via the patterns from the styling lessons.
  • Publish components as a versioned npm package so consuming teams can pin to a specific, stable version.
  • A documented Storybook (or similar) instance doubles as both documentation and a visual testing surface.
  • Framework-specific wrapper packages (thin React/Angular/Vue wrappers around the core Lit components) are sometimes added for a more idiomatic per-framework developer experience, without duplicating the actual component logic.

Design System Building Blocks

The pieces a Lit-based design system typically needs.

Piece Purpose
Design tokens (CSS custom properties) Shared, themeable visual values
Component library (Lit components) The actual reusable UI building blocks
Documentation/style guide Shows every component, its props, and usage examples
Accessibility guidelines per component Ensures consistent, correct a11y across the system
Versioned npm distribution Lets consuming teams adopt updates deliberately
Optional framework wrappers More idiomatic per-framework integration, if needed

Theming an Entire System, Not Just One Component

For a full design system, define a shared, documented set of top-level design tokens (colors, spacing scale, typography scale) as CSS custom properties set at :root, and have every individual component read from those shared tokens with sensible component-specific fallbacks — this keeps the whole system's visual language consistent while still allowing per-component overrides where genuinely needed.

:root {
  --ds-color-primary: #2563eb;
  --ds-space-md: 1rem;
  --ds-radius-md: 8px;
}

/* Inside ds-button's static styles */
button {
  background: var(--ds-button-bg, var(--ds-color-primary));
  padding: var(--ds-button-padding, var(--ds-space-md));
  border-radius: var(--ds-radius-md);
}

Do You Need Framework-Specific Wrappers?

Lit components work directly in React, Vue, and Angular without wrappers for basic usage, but some frameworks (notably older React versions before full custom-elements property binding support) have rough edges passing complex properties (arrays, objects, callbacks) to custom elements through their own prop/attribute systems. A thin wrapper package can smooth over these specific integration gaps without duplicating the underlying component's actual logic.

Common Mistakes

  • Building parallel, duplicate component implementations per framework instead of investing in one shared, framework-agnostic Lit implementation.
  • Skipping documented design tokens and letting individual components hardcode their own colors/spacing, fragmenting the visual language over time.
  • Not versioning the component package, making it hard for consuming teams to adopt breaking changes deliberately and safely.
  • Assuming framework wrappers are always necessary — many teams consume Lit-based design systems directly with no wrapper at all.

Key Takeaways

  • Lit's standards-based components are naturally suited to a design system consumed across multiple frameworks.
  • CSS custom properties are the primary theming mechanism for a shared, consistent visual language.
  • Semantic versioning and clear documentation (often via Storybook) are essential for a design system's adoption and trust.
  • Framework-specific wrappers are optional, added only to smooth over specific integration gaps, not required by default.

Pro Tip

Start a design system with a genuinely small set of components (a button, an input, a card) built to a high, consistent standard, rather than trying to cover every possible UI pattern upfront — a design system's credibility comes from consistency and polish, not initial breadth.