← Retour au blog
design-systemsangulartailwind-csstokensthemingcomponent-library

Design Systems with Angular + Tailwind: Beyond Components, Master Tokens and Theming

A mature design system transcends a simple reusable component library. It is a cohesive ecosystem where tokens, CSS variables, naming conventions, and Angular architecture work together to deliver a smooth developer experience and guaranteed visual consistency. Many teams start with "let's create reusable buttons and cards," then find themselves drowning in overrides, exceptions, and orphaned styles after six months. The real difference? Treating the design system as a single source of truth, driven by design tokens rather than Tailwind classes written ad-hoc in every component.

Design Tokens: The Foundation

Design tokens are atomic values—colors, spacing, typography, radii, shadows—defined once and reused everywhere. Unlike a standard CSS class, a token is an abstract concept: instead of coding text-blue-600 directly, you define color-primary which *can* be blue in light mode and purple in dark mode. This abstraction allows the design system to evolve without touching component code. Tailwind, by default, is rigid on this front: its classes are generated from a static tailwind.config.js file. For true dynamic theming, you must enrich this approach by defining tokens as custom CSS variables and configuring Tailwind to consume them.

Concretely, start by structuring tokens in a hierarchy: primitive tokens (raw values), system tokens (semantic), and component tokens. Example: --color-blue-500 (primitive) → --color-primary (system) → --button-bg-color (component). In SCSS or plain CSS, it looks like this:

:root {
  --color-neutral-100: #ffffff;
  --color-neutral-900: #0f0f0f;
  --color-primary: var(--color-blue-600);
  --color-secondary: var(--color-indigo-600);
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --font-family-sans: 'Inter', sans-serif;
  --font-size-base: 1rem;
  --font-weight-regular: 400;
  --font-weight-bold: 700;
}

[data-theme="dark"] {
  --color-primary: var(--color-blue-400);
  --color-neutral-100: var(--color-neutral-900);
  --color-neutral-900: var(--color-neutral-100);
}

This structure lets you swap themes by changing the data-theme attribute on the root element, without recompiling Tailwind.

Tailwind + CSS Variables Integration

Tailwind offers native integration with CSS variables via the theme() function in its config. The idea is to replace hardcoded values with token references. Here's how to configure tailwind.config.js :

export default {
  theme: {
    colors: {
      primary: 'var(--color-primary)',
      secondary: 'var(--color-secondary)',
      neutral: {
        100: 'var(--color-neutral-100)',
        900: 'var(--color-neutral-900)',
      },
    },
    spacing: {
      xs: 'var(--spacing-xs)',
      sm: 'var(--spacing-sm)',
      md: 'var(--spacing-md)',
    },
    fontFamily: {
      sans: 'var(--font-family-sans)',
    },
    fontSize: {
      base: 'var(--font-size-base)',
    },
    fontWeight: {
      regular: 'var(--font-weight-regular)',
      bold: 'var(--font-weight-bold)',
    },
  },
};

Now your Tailwind classes like bg-primary , text-neutral-100 , and p-md consume CSS variables directly. This means changing --color-primary at runtime instantly affects all components without recompilation.

Angular Components and Token Composition

In your Angular components, you can focus on logic and structure rather than visual details. A typical button component:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-button',
  template: `
    <button
      [class]="'px-md py-sm font-bold rounded-md transition-colors ' + variantClass"
      [disabled]="disabled"
    >
      <ng-content></ng-content>
    </button>
  `,
  styles: [`
    :host button {
      background-color: var(--button-bg-color);
      color: var(--button-text-color);
      border: var(--button-border);
    }
    :host button:hover {
      background-color: var(--button-bg-color-hover);
    }
  `],
})
export class ButtonComponent {
  @Input() variant: 'primary' | 'secondary' | 'ghost' = 'primary';
  @Input() disabled = false;

  get variantClass(): string {
    return {
      primary: 'bg-primary text-white',
      secondary: 'bg-secondary text-white',
      ghost: 'bg-transparent text-primary border border-primary',
    }[this.variant];
  }
}

This component contains no hardcoded color or spacing values. Everything comes from tokens, making theme updates trivial.

Dynamic Theming in Angular

To swap themes at runtime, create an Angular service that manages the data-theme attribute and persists user preference:

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class ThemeService {
  private currentTheme = 'light';

  constructor() {
    const saved = localStorage.getItem('theme') || 'light';
    this.setTheme(saved);
  }

  setTheme(theme: 'light' | 'dark'): void {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
    this.currentTheme = theme;
  }

  toggleTheme(): void {
    this.setTheme(this.currentTheme === 'light' ? 'dark' : 'light');
  }
}

In your main layout, inject this service and let it initialize the theme at app startup. Developers no longer need to manage visual states: the token system handles it.

Common Pitfalls: Consistency and Documentation

The first pitfall is mixing tokens with arbitrary Tailwind classes. You often see developers writing bg-blue-400 directly in a template instead of using bg-primary . This creates islands of uncontrolled styles and makes theme changes unpredictable. Solution: enforce a strict rule via ESLint or team convention: "only Tailwind classes that consume tokens are allowed." Second, do not document your tokens once and forget them. Maintaining an up-to-date Storybook with theme variants, states (hover, disabled, focus), and real use cases is non-negotiable. Tokens without documentation are just orphaned CSS variables.

Operational Conclusion

A solid Angular + Tailwind design system rests on three pillars: well-structured tokens, Tailwind integration that consumes them, and a component architecture that leaves semantics to the system rather than visual details. Start by defining your primitive and semantic tokens in a single CSS file, configure Tailwind to consume them, then build your Angular components without thinking about colors or spacing. With this discipline, you gain predictable maintainability, faster developer onboarding, and the ability to pivot the entire design in seconds.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️