Skip to content

Theming

Every visual value in the library’s own CSS (BASE_CSS, the drag ghost and drop zone, the command palette) is a custom property from one small, documented set: --wa-*. Override any of them and the library follows.

<style id="wa-base"></style>
<script type="module">
import { BASE_CSS } from "@johnhenry/window-algebra/css";
document.getElementById("wa-base").textContent = BASE_CSS; // THEME_CSS + the rules
</script>
<style>
:root { --wa-color-accent: #d6336c; --wa-radius-md: 14px; --wa-space-md: 16px; }
/* or theme one stage only: */
#inspector { --wa-titlebar-bg: #1b2b20; --wa-titlebar-fg: #d8f3dc; }
</style>

BASE_CSS is THEME_CSS (the default theme) followed by RULES_CSS (the rules that read the tokens, including CHROME_CSS for the opt-in window chrome). Use RULES_CSS alone to supply every token yourself. The defaults are declared inside :where(), so they have zero specificity and a plain :root { --wa-color-accent: hotpink } wins wherever it appears.

Setting BASE_CSS as a <style>’s textContent (the snippet above), and the <style data-wm-palette-style> that createPalette injects, are inline styles, which style-src 'self' blocks. Everything else the library does to the DOM goes through the CSSOM (element.style.setProperty), which a CSP allows, so the fix is only about these two sheets: write BASE_CSS and PALETTE_CSS (both exported; PALETTE_CSS from @johnhenry/window-algebra/browser) to a .css file at build time, link it, and pass injectStyles: false to createPalette (or to <wa-palette>’s configure). Nothing else needs 'unsafe-inline'. The archived workbench app did exactly this under require-trusted-types-for 'script'.

  • With nothing set, the OS preference (prefers-color-scheme) picks light or dark.
  • data-theme="light" or "dark" on <html>, or on any element wrapping a stage, forces that scheme.
  • Under @media (prefers-contrast: more) the high-contrast value replaces the scheme’s value for tokens that have one: black on white (or white on black), strong borders, no shadows, thicker focus rings and splitter lines.
  • Under @media (forced-colors: active) the focus ring uses Highlight and splitter lines CanvasText.

The defaults are checked in test/theme.test.mjs against WCAG contrast ratios (4.5:1 for text, 7:1 under prefers-contrast: more, 3:1 for the focus ring) in all four combinations.

Colours (--wa-color-*, --wa-border-width), radii (--wa-radius-*), spacing (--wa-space-*), the focus ring (--wa-focus-ring-*), splitters (--wa-splitter-*), the title bar (--wa-titlebar-*, used by the tab strip and the built-in window chrome), the window chrome’s sizes (--wa-chrome-*: bar height, button size, touch target, grip sizes), shadows (--wa-shadow-*), the drag preview (--wa-ghost-*, --wa-zone-*), type (--wa-font*) and motion (--wa-transition-*). THEME_TOKENS is the whole table as data ({ [name]: { description, light, dark, hc? } }) for a theme editor. The values for each token are in Theming.

The compiled render tree carries only layout in its inline styles, so theming never needs a re-render. Try it in demo/theming.html.