# How Archify's Theme Toggle System Works with CSS Custom Properties and the data-theme Attribute

> Discover how Archify's theme toggle system instantly switches between dark and light modes using CSS custom properties and the data-theme attribute, no page reloads needed.

- Repository: [tt-a1i/archify](https://github.com/tt-a1i/archify)
- Tags: internals
- Published: 2026-07-14

---

**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`](https://github.com/tt-a1i/archify/blob/main/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`](https://github.com/tt-a1i/archify/blob/main/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.

```css
<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`](https://github.com/tt-a1i/archify/blob/main/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:

1. **URL query parameter** (`?theme=light` or `?theme=dark`) – Enables deterministic screenshots or forced themes via shareable links
2. **`localStorage`** key `archify-theme` – Persists the user's last explicit choice across sessions
3. **`prefers-color-scheme`** media query – Falls back to the OS-level setting when no explicit preference exists

```javascript
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`](https://github.com/tt-a1i/archify/blob/main/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()`:

```javascript
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`](https://github.com/tt-a1i/archify/blob/main/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.

```javascript
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`](https://github.com/tt-a1i/archify/blob/main/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`](https://github.com/tt-a1i/archify/blob/main/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, then `localStorage`, then system preferences.
- **State Persistence** – The `toggle()` function writes choices to `localStorage` under the key `archify-theme`.
- **OS Sync** – A `matchMedia` listener reacts to `prefers-color-scheme` changes when no explicit user preference exists.
- **Export Safety** – The `resolveVars` mechanism 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.