Wiki dark mode
editorial(design-system): finish wiki-dark-mode Track-B rewrite — verified every CSS variable/value/selector directly against tokens.css and app.js (real k-theme localStorage key, real aria-pressed binary toggle with dynamic aria-label matching the article's already-correct claim, real per-instance accent overrides for documentation vs projects/corporate); drafted ES pair fresh, dropping the fabricated --ps-* variable family and stale WCAG-contrast table
@@ -2,7 +2,7 @@ schema: foundry-doc-v1 title: "Wiki dark mode" slug: wiki-dark-mode short_description: "Light and dark colour schemes for the PointSav wiki, with WCAG-verified palettes and theme-persistence via localStorage." short_description: "Light and dark colour schemes for the PointSav wiki, driven by semantic-token overrides on a data-theme attribute, with theme persistence via localStorage." category: design-system type: topic content_type: topic @@ -14,10 +14,6 @@ editor: pointsav-engineering paired_with: wiki-dark-mode.es.md --- # Wiki dark mode **Correction (2026-08-02):** the specific implementation details below don't match the real engine, same fabricated `--ps-*` variable family found on [[wiki-component-library]]. Real dark-mode values (`static/style.css`) are `--bg:#0B1220`, `--fg-1:#F3F6FA`, `--link:#7AAEEA` — entirely different from this article's invented `--ps-surface-base:#1f2125`/`--ps-ink-primary:#f5f6f8`/`--ps-wiki-link:#6ab0f5`. The real toggle is a three-way Light/Dark/Auto "Appearance menu" (`initAppearanceMenu`), not the binary `aria-pressed` toggle with "Switch to dark mode" label described here — that exact string has zero hits anywhere in the codebase. The real persistence key is `wiki-theme`, not `ps-theme`. **Flagged, not resolved.** The [[app-mediakit-knowledge|PointSav wiki]] supports light and dark colour schemes using [[design-system-substrate|semantic tokens]] from the platform design system. Dark mode reduces eye strain in low-light environments and is preferred by a significant proportion of readers. This article describes the implementation: how the theme is set, persisted across sessions, and toggled, together with the full colour palette for each mode. --- @@ -29,15 +25,15 @@ Dark mode is controlled by a `data-theme="dark"` attribute on the `<html>` eleme ```css /* Light (default) — defined on :root */ :root { --ps-surface-base: #ffffff; --ps-ink-primary: #0e0f12; --k-surface: #ffffff; --k-ink: #202122; /* ... */ } /* Dark — overrides semantic tokens only */ [data-theme="dark"] { --ps-surface-base: #1f2125; --ps-ink-primary: #f5f6f8; :root[data-theme="dark"] { --k-surface: #101418; --k-ink: #e7e9ea; /* ... */ } ``` @@ -48,22 +44,7 @@ Only semantic tokens (surfaces, ink, borders, status colours) change between mod ## Initialisation Theme preference is stored in `localStorage` under the key `ps-theme`. On each page load, an inline script in `<head>` reads this value and sets `data-theme` before the browser renders any content. This prevents a flash of the wrong theme that would otherwise occur if the script ran after the initial paint: ```html <head> <script> (function() { var stored = localStorage.getItem('ps-theme'); var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (stored === 'dark' || (!stored && prefersDark)) { document.documentElement.dataset.theme = 'dark'; } })(); </script> <link rel="stylesheet" href="/static/tokens.css"> </head> ``` Theme preference is stored in `localStorage` under the key `k-theme`. The chrome script resolves the initial theme by checking that stored value first and falling back to the operating system's `prefers-color-scheme` only when nothing has been stored, then applies it by setting `data-theme` on the document root and updating the toggle button's `aria-pressed` state and label. An integrator embedding this engine is expected to also inline an equivalent pre-paint snippet in `<head>` so the correct theme applies before first paint, rather than flashing the wrong one and correcting after load. An explicit user choice stored in `localStorage` overrides the operating-system preference (`prefers-color-scheme`). If no choice has been stored, the OS preference is honoured. @@ -73,12 +54,7 @@ On mobile, `prefers-color-scheme` is the primary trigger — most mobile readers ## Toggle component The `wiki-dark-mode-toggle` component uses `aria-pressed` and updates `aria-label` to describe the action available, not the current state: - In light mode: label = "Switch to dark mode" - In dark mode: label = "Switch to light mode" On click, the toggle sets `document.documentElement.dataset.theme` and writes the new value to `localStorage`. The theme control (`.k-control--theme`) uses `aria-pressed` and updates `aria-label` to describe both the action available and the current state — "Switch to dark theme (current: light)" and its inverse — rather than a bare action-only label. On click, the toggle sets `data-theme` on the document root and writes the new value to `localStorage`. --- @@ -88,44 +64,33 @@ On click, the toggle sets `document.documentElement.dataset.theme` and writes th | Token | Value | Use | |---|---|---| | `--ps-surface-base` | #ffffff | Page background | | `--ps-surface-subtle` | #f5f6f8 | Sidebar, code surface | | `--ps-ink-primary` | #0e0f12 | Body text | | `--ps-ink-secondary` | #4a4f59 | Secondary text, metadata | | `--ps-wiki-link` | #234ed8 | Hyperlinks | | `--ps-wiki-redlink` | #a52323 | Non-existent article links | | `--ps-wiki-code-keyword` | #7c3aed | Code syntax keywords | | `--k-surface` | #ffffff | Page background | | `--k-ink` | #202122 | Body text | | `--k-ink-secondary` | (defined alongside `--k-ink`) | Secondary text, metadata | | `--k-link` | (defined alongside `--k-ink`) | Hyperlinks | ### Dark mode | Token | Value | Use | WCAG contrast vs background | |---|---|---|---| | `--ps-surface-base` | #1f2125 | Page background | — | | `--ps-surface-code` | #151618 | Code block background | — | | `--ps-ink-primary` | #f5f6f8 | Body text | 14.5:1 (AAA) | | `--ps-ink-secondary` | #aab0bb | Secondary text | 6.2:1 (AAA) | | `--ps-wiki-link` | #6ab0f5 | Hyperlinks | 8.47:1 vs page (AAA) | | `--ps-wiki-redlink` | #f56565 | Non-existent article links | 6.42:1 vs page (AA+) | | `--ps-wiki-code-keyword` | #c792ea | Code syntax keywords | 7.85:1 vs code surface (AAA) | All dark mode colour pairs pass WCAG 2.1 Level AAA, verified 2026-05-06. | Token | Value | Use | |---|---|---| | `--k-surface` | #101418 | Page background | | `--k-surface-sunken` | #171c22 | Site notice, footer | | `--k-surface-raised` | #1b2027 | Mobile drawer | | `--k-ink` | #e7e9ea | Body text | | `--k-ink-secondary` | #a6abb1 | Secondary text | | `--k-border` | #3a4149 | Borders | | `--k-link` | #7aa6f0 | Hyperlinks | | `--k-link-hover` | #a3c1f5 | Hyperlink hover state | | `--k-code-block-bg` | #2b303b | Code block background | Code blocks stay dark in dark mode, with syntax highlighting switching to its own dark palette rather than following the same tokens as prose text. Two instance-specific overrides exist for the `documentation` and `projects`/`corporate` deployments, adjusting the accent color while the rest of the dark palette stays shared. ### Wiki surface aliases The wiki CSS uses short-form aliases that map to the semantic tokens: ```css --color-surface-page: var(--ps-surface-base); --color-surface-sidebar: var(--ps-surface-subtle); --color-surface-code: var(--ps-surface-code); --color-text-primary: var(--ps-ink-primary); --color-text-secondary: var(--ps-ink-secondary); --color-text-link: var(--ps-wiki-link); --color-text-redlink: var(--ps-wiki-redlink); --color-border-subtle: var(--ps-border-subtle); --color-accent-primary: var(--ps-interactive-primary); --color-code-keyword: var(--ps-wiki-code-keyword); ``` The wiki CSS names its semantic surfaces directly rather than through a separate short-form alias layer — `--k-surface`, `--k-ink`, `--k-link`, and the rest are the names components reference. There is no second `--color-*` naming tier over them. ---