How Archify's Theme Toggle System Works with CSS Custom Properties and the data-theme Attribute
Archify implements a lightweight theme toggle using CSS custom properties and a data-theme attribute on the <html> element to switch between dark and light modes instantly without page reloads.
Archify is an open-source diagram visualization tool that ships with a self-contained, zero-dependency theme toggle implementation. The system leverages CSS custom properties (variables) and a data-theme attribute to provide cascade-free visual switching, with all logic embedded directly in examples/web-app.html.
CSS Variable Architecture
The theming engine relies on two scoped CSS variable blocks that define color values for every UI element.
Dark and Light Variable Blocks
In examples/web-app.html, the styles define variables under two selectors:
:root, [data-theme="dark"]– Contains the default dark-mode palette (e.g.,--bg: #020617,--text: #ffffff,--toolbar-bg)[data-theme="light"]– Overrides those variables with light-mode values (e.g.,--bg: #f8fafc,--text: #0f172a)
All components consume these variables via var(--name). When JavaScript swaps the data-theme attribute on the <html> element, every dependent style updates automatically through CSS inheritance.
<style>
:root,
[data-theme="dark"] {
--bg: #020617;
--text: #ffffff;
--toolbar-bg: #1e293b;
/* additional dark variables */
}
[data-theme="light"] {
--bg: #f8fafc;
--text: #0f172a;
--toolbar-bg: #e2e8f0;
/* additional light variables */
}
</style>
Source: examples/web-app.html (lines 40-84)
Theme Resolution Strategy
Before the first paint, an IIFE named Archify.theme resolves the initial theme through a three-tier priority system.
Priority Order: URL, localStorage, then System Preference
The resolveInitial() function checks sources in this strict sequence:
- URL query parameter (
?theme=lightor?theme=dark) – Enables deterministic screenshots or forced themes via shareable links localStoragekeyarchify-theme– Persists the user's last explicit choice across sessionsprefers-color-schememedia query – Falls back to the OS-level setting when no explicit preference exists
function resolveInitial() {
const fromUrl = urlOverride();
if (fromUrl) return fromUrl;
const saved = readStored();
if (saved === 'light' || saved === 'dark') return saved;
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
}
The resolved value is immediately applied to document.documentElement via html.setAttribute('data-theme', theme).
Source: examples/web-app.html (lines 13-44)
Toggle Implementation
The toolbar button (#btn-theme) exposes a toggle() function that flips the current state and persists the choice.
The apply() and toggle() Functions
The apply(theme) function writes the attribute, updates the button's icon (☾/☼) and label text, and sets aria-pressed for accessibility. The toggle() function reads the current data-theme value, computes the inverse, and triggers apply():
function toggle() {
const next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
apply(next);
writeStored(next);
}
Persistence with localStorage
The writeStored(v) function wraps localStorage.setItem('archify-theme', v) in a try-catch block to prevent crashes in privacy mode or sandboxed environments. Similarly, readStored() safely retrieves the value, returning null if storage is inaccessible.
Source: examples/web-app.html (lines 45-56)
OS-Level Theme Synchronization
When the user has not set an explicit preference (no URL parameter and no stored value), the system listens for live OS theme changes.
Listening to prefers-color-scheme
The script attaches a change listener to window.matchMedia('(prefers-color-scheme: light)'). If the media query matches, the system applies the light theme; otherwise, it applies dark. This listener only executes when urlOverride() and readStored() both return null, ensuring user overrides take precedence.
const media = window.matchMedia('(prefers-color-scheme: light)');
media.addEventListener('change', e => {
if (urlOverride() || readStored()) return;
apply(e.matches ? 'light' : 'dark');
});
Source: examples/web-app.html (lines 60-70)
SVG Export Compatibility
Exported diagrams must retain theme fidelity even when viewed outside the Archify application.
Self-Theming Exported Assets
During SVG export, the script probes the computed styles for both themes using a temporary DOM element. It extracts the full set of custom properties, then injects two CSS blocks into the exported SVG. This allows the exported file to respond to prefers-color-scheme on external hosts, ensuring colors remain correct in both dark and light viewing contexts.
The resolveVars logic captures current variable values and writes them into <style> tags within the SVG, making the asset self-contained and theme-aware.
Source: examples/web-app.html (lines 68-84)
Summary
- CSS Custom Properties – All UI colors reference variables defined under
:root(dark) and[data-theme="light"]selectors, enabling instant updates via attribute changes. - Triple-Tier Resolution – The
resolveInitial()function checks URL parameters first, thenlocalStorage, then system preferences. - State Persistence – The
toggle()function writes choices tolocalStorageunder the keyarchify-theme. - OS Sync – A
matchMedialistener reacts toprefers-color-schemechanges when no explicit user preference exists. - Export Safety – The
resolveVarsmechanism embeds both theme definitions into exported SVGs for standalone theme switching.
Frequently Asked Questions
How does the data-theme attribute trigger the theme change?
The data-theme attribute acts as a CSS selector hook. When JavaScript sets data-theme="light" on the <html> element, the [data-theme="light"] CSS block becomes active, overriding the :root variables. Because all UI elements reference these variables, the visual update happens instantly without JavaScript manipulating individual styles.
What happens if localStorage is disabled or private browsing mode is active?
The readStored() and writeStored() functions wrap all localStorage calls in try-catch blocks. If storage is inaccessible, the system silently falls back to URL parameters or system preferences, ensuring the theme toggle still functions without throwing errors.
Can I force a specific theme via URL for sharing diagrams?
Yes. Appending ?theme=dark or ?theme=light to the URL overrides both localStorage and system preferences. The urlOverride() function checks URLSearchParams early in the resolution chain, making it useful for generating screenshots or sharing links with a locked visual style.
How does the exported SVG know which theme to display?
The export process captures the computed values of all CSS custom properties for both themes and embeds them as <style> blocks inside the SVG. The exported file includes prefers-color-scheme media queries, allowing it to self-theme based on the viewer's OS settings when displayed in browsers or image viewers that support CSS.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →