Design System Theming with CSS Custom Properties
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:
- No
data-mode— follows the operating system’s preference data-mode="light"— forces light modedata-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.