Skip to content

Theming

Airframe uses tokens as CSS variables. Override them directly in CSS—no build step required.

Need to map an existing design system? Use Theme Studio to upload Tokens Studio / DTCG / CSS / Figma Variables, preview on real patterns, lint unknown tokens, and export theme.css.

CLI equivalent (via @airframeui/build):

npx af theme generate --from ./tokens.json -o theme.css
npx af theme lint theme.css

AI / MCP: install @airframeui/mcp and use generate_themelint_theme. See @airframeui/theme/rules and Build Tool.

Override hex primitives (--af-base-*) in :root for simple, global brand color changes. This is the easiest approach and changes everything that uses that color:

:root {
  --af-base-primary: #018183;
  --af-base-secondary: #6b7280;
  --af-base-success: #10b981;
  --af-base-danger: #ef4444;
  --af-base-warning: #f59e0b;
  --af-base-info: #3b82f6;
}

When to use: Simple brand color swaps, global changes, when you want the same color across all themes.

Limitation: Primitives are shared across all themes, so you can’t have different colors for light/dark/high-contrast.

Override semantic tokens (--af-color-*) with theme suffixes (--dark, --hc-light, --hc-dark) for per-theme control:

:root {
  /* Different primary color per theme */
  --af-color-primary--dark: #38bdf8;  /* Lighter blue in dark mode */
  --af-color-action--hc-light: #0066cc;  /* Stronger blue for high contrast */
}

When to use: Different colors per theme, accessibility adjustments (like stronger colors for high-contrast), fine-grained control.

See: Dark Mode and High Contrast sections for complete examples.

Interaction states and soft/hard variants are derived via color-mix():

:root {
  --af-hover-mix: 12%;
  --af-active-mix: 18%;
  --af-soft-mix: 12%;
  --af-hard-mix: 45%; /* Dark enough for AA text on soft intent surfaces */
}

Badges, chips, and KPI deltas use *-hard text on *-soft backgrounds. Keep --af-hard-mix high enough that success/warning/info hard text clears 4.5:1 on soft.

Airframe ships with dark tokens and follows system preference by default.

When you import the core CSS, dark mode follows prefers-color-scheme automatically. No extra configuration is required.

Dark mode follows system preference by default. To override the system preference, set dark mode on the root element using one of these methods:

  • data-theme attribute: Standard data attribute
  • .dark class: CSS class
<!-- Force dark mode using data-theme -->
<html data-theme="dark">
  <body>
    ...
  </body>
</html>
<!-- Force dark mode using .dark class -->
<html class="dark">
  <body>
    ...
  </body>
</html>
<!-- Force light mode -->
<html data-theme="light">
  <body>
    ...
  </body>
</html>

Override dark mode colors using the --dark suffix pattern on semantic tokens (--af-color-*). This works regardless of how dark mode is applied (media query, class, or data attribute):

:root {
  /* Override dark mode colors - works with any dark mode implementation */
  /* Base surfaces */
  --af-color-background--dark: #0a0a0a;
  --af-color-surface-primary--dark: #0a0a0a;
  /* Text */
  --af-color-text--dark: #ffffff;
  /* Borders */
  --af-color-border--dark: #333333;
  /* Intent (primary) */
  --af-color-primary--dark: #38bdf8;
}

Note: You can also override primitives (--af-base-primary) for a global change, but semantic tokens with --dark suffix give you theme-specific control. See Color Tokens for when to use each approach.

Accessibility is foundational to Airframe. High-contrast themes ensure your UI is accessible to users with low vision, color blindness, or who need higher contrast for readability.

High-contrast tokens are applied automatically when a user enables high contrast in their OS settings. If both dark mode and high contrast are enabled, the high-contrast dark tokens apply.

Airframe detects the system prefers-contrast: more or prefers-contrast: high preference and automatically applies high-contrast semantic tokens. Override these tokens to customize colors while maintaining accessibility.

Override high contrast colors using the --hc-light and --hc-dark suffix patterns on semantic tokens (--af-color-*). You can also override primitives (--af-base-primary--hc-light / --af-base-primary--hc-dark) for brand color changes that apply across all semantic tokens.

:root {
  /* High contrast light mode - override semantic tokens */
  --af-color-background--hc-light: #ffffff;
  --af-color-surface-primary--hc-light: #ffffff;
  --af-color-text--hc-light: #000000;
  --af-color-text-muted--hc-light: #333333;
  --af-color-border--hc-light: #000000;
  --af-color-border-strong--hc-light: #000000;

  /_ Override action/primary colors for high contrast _/
  --af-color-action--hc-light: #0066cc; /_ Stronger blue for better contrast _/
  --af-color-on-action--hc-light: #ffffff;

  /_ High contrast dark mode - override semantic tokens _/
  --af-color-background--hc-dark: #000000;
  --af-color-surface-primary--hc-dark: #1a1a1a;
  --af-color-text--hc-dark: #ffffff;
  --af-color-text-muted--hc-dark: #cccccc;
  --af-color-border--hc-dark: #ffffff;
  --af-color-border-strong--hc-dark: #ffffff;

  /_ Override action/primary colors for high contrast dark _/
  --af-color-action--hc-dark: #60a5fa; /_ Lighter blue for dark backgrounds _/
  --af-color-on-action--hc-dark: #000000;
}

Important: Always verify contrast ratios meet WCAG 2.2 AA requirements (4.5:1 for normal text, 3:1 for large text and UI components). Use a contrast checker before deploying.

TokenHC Light (--hc-light)HC Dark (--hc-dark)
Surface
background--af-color-background--hc-light--af-color-background--hc-dark
surface-primary--af-color-surface-primary--hc-light--af-color-surface-primary--hc-dark
surface-secondary--af-color-surface-secondary--hc-light--af-color-surface-secondary--hc-dark
surface-tertiary--af-color-surface-tertiary--hc-light--af-color-surface-tertiary--hc-dark
Text
text--af-color-text--hc-light--af-color-text--hc-dark
text-muted--af-color-text-muted--hc-light--af-color-text-muted--hc-dark
text-inverse--af-color-text-inverse--hc-light--af-color-text-inverse--hc-dark
Border
border--af-color-border--hc-light--af-color-border--hc-dark
border-strong--af-color-border-strong--hc-light--af-color-border-strong--hc-dark
Action / primary
action--af-color-action--hc-light--af-color-action--hc-dark
on-action--af-color-on-action--hc-light--af-color-on-action--hc-dark
Intent (on-)
on-primary--af-color-on-primary--hc-light--af-color-on-primary--hc-dark
on-secondary--af-color-on-secondary--hc-light--af-color-on-secondary--hc-dark
on-success--af-color-on-success--hc-light--af-color-on-success--hc-dark
on-warning--af-color-on-warning--hc-light--af-color-on-warning--hc-dark
on-danger--af-color-on-danger--hc-light--af-color-on-danger--hc-dark
on-info--af-color-on-info--hc-light--af-color-on-info--hc-dark

Enable high contrast in your OS (Windows: Settings → Ease of Access → High contrast) or use browser DevTools to simulate prefers-contrast: more. Verify all text meets WCAG 2.2 AA (4.5:1 for normal text, 3:1 for large text) and test in both light and dark modes.

Typography is driven by semantic tokens so brands can override safely:

:root {
  /* Override semantic font tokens */
  --af-font-primary: 'Inter', 'Roboto', sans-serif;
  --af-font-heading: 'Playfair Display', serif;
  --af-font-body: var(--af-font-primary);
  --af-font-code: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
:root {
  /* Override type scale, leading (line-height), and weights */
  --af-text-lg: 1.125rem;
  --af-leading-normal: 1.5;
  --af-weight-medium: 500;
}

Component tokens provide override-friendly sizing for buttons, inputs, and other controls:

:root {
  /* Shared control tokens */
  --af-control-min-height: 3rem;
  --af-control-radius: var(--af-radius-md);

  /_ Button-specific _/
  --af-btn-gap: 0.75rem;
  --af-btn-padding-x: var(--af-space-4);

  /_ Input-specific _/
  --af-input-placeholder-color: rgba(0, 0, 0, 0.45);
}

Motion tokens provide consistent timing for transitions and animations, with automatic reduced motion support:

:root {
  /* Duration */
  --af-duration-fast: 120ms;
  --af-duration-md: 200ms;

  /_ Easing _/
  --af-ease-standard: cubic-bezier(.2, .0, .2, 1);
  --af-ease-emphasized: cubic-bezier(.2, .0, 0, 1);
}
:root {
  /* Colors */
  --af-base-primary: #0ea5e9;  /* Global brand color */
  --af-hover-mix: 12%;
  --af-hard-mix: 45%;  /* Hard text on soft surfaces (AA) */
  /* Override dark mode colors */
  --af-color-background--dark: #0a0a0a;
  --af-color-primary--dark: #38bdf8;  /* Different primary in dark mode */

  /_ Typography _/
  --af-font-heading: ui-sans-serif, system-ui, sans-serif;
  --af-text-lg: 1.1875rem;

  /_ Components _/
  --af-control-min-height: 2.5rem;
  --af-btn-radius: var(--af-radius-lg);
}