MultronHub

Design System Theming with CSS Custom Properties

Multron Team ·

The Challenge

Multron runs multiple UI frameworks — React, SolidJS, and Astro — on the same pages. A design system that’s tightly coupled to one framework’s component library won’t work here. We need styling that’s framework-agnostic.

CSS custom properties (aka CSS variables) solve this perfectly. They work everywhere CSS works, regardless of which framework rendered the DOM.

The ax-* Token System

All design values in Multron flow through CSS custom properties prefixed with ax-:

:root {
  /* Semantic colors */
  --ax-bg: #ffffff;
  --ax-bg-secondary: #f6f8fa;
  --ax-text: #1f2328;
  --ax-text-muted: #656d76;
  --ax-accent: #0969da;

  /* Typography */
  --ax-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --ax-font-size-base: 1rem;
  --ax-font-size-sm: 0.875rem;

  /* Spacing */
  --ax-space-sm: 0.5rem;
  --ax-space-md: 1rem;
  --ax-space-lg: 1.5rem;

  /* Borders & Shapes */
  --ax-border: #d0d7de;
  --ax-radius-md: 6px;
}

Components reference these tokens instead of hard-coded values. A button looks the same whether it’s an Astro component, a React component, or a SolidJS component — because they all read from the same CSS variables.

Theme Switching

Multron ships three themes: default, cyberpunk, and cyberpunk-readable. Each theme is a separate CSS file that redefines the ax-* tokens:

/* tokens-cyberpunk.css */
[data-theme="cyberpunk"] {
  --ax-bg: #0a0a0f;
  --ax-text: #00ff41;
  --ax-accent: #ff00ff;
  --ax-font-family: "Courier New", monospace;
}

Switching themes is a single attribute change on <html>:

document.documentElement.setAttribute('data-theme', 'cyberpunk')

Every component on the page instantly re-renders with the new token values. No framework state updates, no re-renders, no prop drilling — just CSS doing what CSS does best.

Light and Dark Modes

Color mode is controlled separately from theme via data-mode:

/* Light mode (default) */
:root { --ax-bg: #ffffff; --ax-text: #1f2328; }

/* Dark mode — system preference */
@media (prefers-color-scheme: dark) {
  :root:not([data-mode="light"]) {
    --ax-bg: #0d1117;
    --ax-text: #c9d1d9;
  }
}

/* Dark mode — explicit override */
[data-mode="dark"] {
  --ax-bg: #0d1117;
  --ax-text: #c9d1d9;
}

This three-tier approach gives users full control:

  1. No data-mode — follows the operating system’s preference
  2. data-mode="light" — forces light mode
  3. data-mode="dark" — forces dark mode

Preventing Flash of Wrong Theme

A common problem with theme systems is the “Flash of Wrong Theme” (FOWT) — the page briefly renders with the wrong theme before JavaScript loads and corrects it.

Multron solves this by inlining the theme-selector script in the <head>, before any CSS loads. This tiny script reads the user’s saved preference from localStorage and sets the data-theme and data-mode attributes synchronously, before the browser paints.

Code Highlighting Integration

The blog uses Shiki for syntax highlighting with dual themes that respect the mode toggle:

@media (prefers-color-scheme: dark) {
  .astro-code,
  .astro-code span {
    color: var(--shiki-dark) !important;
    background-color: var(--shiki-dark-bg) !important;
  }
}

[data-mode="dark"] .astro-code,
[data-mode="dark"] .astro-code span {
  color: var(--shiki-dark) !important;
  background-color: var(--shiki-dark-bg) !important;
}

Shiki’s dual-theme output embeds both light and dark colors as CSS variables. We just need to tell it which set to use, and the data-mode attribute handles the switch.

Visit the about page to see the theme controls in action, or toggle the Mode and Theme buttons in the header above.