Never load a theme file with a static <link> in addition to registering it.
ThemeManager only ever swaps the href of #wexa-theme — it does
not remove a second, separately linked copy. If that file is also needed for page-specific rules that
must always be present (layout, custom classes for one document), split it in two: the page-specific
file stays statically linked, and only the color/variable file is registered as a theme.
Themes
What is a theme?
A theme is a standalone CSS file. It redefines CSS variables and decoration rules — colors, gradients, borders, typography accents — without touching structure or accessibility layers.
Themes are mutually exclusive: each theme replaces the previous one entirely.
They never stack. This is enforced by ThemeManager, which manages a single
<link id="wexa-theme"> element whose href is swapped on every
theme switch.
Unlayered colors, layered accents
Most color variables (--bg-color, --border-color, --nav-bg-color,
--buttons-bg-color…) are declared unlayered in a theme file — no
@layer wrapper. An unlayered rule always wins over any @layer rule,
regardless of selector specificity or load order, so this guarantees the active theme's colors beat
wexa.css's own @layer theme defaults every time.
--custom-color1 and --custom-color2 (the accent pair used for chapter titles
in book.css and slide headings in slides.css) are the exception: they are
declared inside @layer theme. This lets a single document write its own
unlayered override for a specific element — see wexa_theme_highcontrast.css's flat
.slide h1 border — without a specificity fight against the theme file.
Integration
Two requirements make theme switching work correctly:
-
The default theme's
<link>element must carryid="wexa-theme". Without it,ThemeManagercreates a second link element and themes accumulate instead of replacing each other. -
The default theme must be registered with
ThemeManagerand declared viasetDefault().
Add id="wexa-theme" to the default theme link in <head>.
<link id="wexa-theme" rel="stylesheet"
href="wexa_statics/css/themes/wexa_theme.css" />
The active theme name is persisted in the URL parameter wexa_theme and propagated
to all internal links via setUrlWithParameters().
const themes = new ThemeManager();
themes.register('wexa_theme', 'wexa_statics/css/themes/wexa_theme.css');
themes.register('aurora', 'wexa_statics/css/themes/wexa_theme_aurora.css');
themes.register('highcontrast', 'wexa_statics/css/themes/wexa_theme_highcontrast.css');
themes.setDefault('wexa_theme');
window.themes = themes;
Available themes
wexa_theme
Default theme. Navy and teal palette, animated gradient underline on links, subtle background gradients.
aurora
Cool blues and greens. Inspired by northern lights. Soft glow on interactive elements.
highcontrast
OS-style high contrast. White/black with dark-blue or yellow links (WCAG AAA). No gradients, no border-radius, flat colors throughout. Designed for maximum legibility.
Creating a custom theme
Redefine only the variables you need — all others fall back to wexa.css defaults.
Keep colors unlayered; put --custom-color1/--custom-color2 inside
@layer theme, matching the built-in themes.
Register it the same way as built-in themes. The .contrast class is managed by AccessibilityManager — a theme must not override colors in contrast mode.
/* my_theme.css */
:root:not(.dark) {
--bg-color: rgb(255, 250, 240);
--a-color: rgb(180, 60, 0);
}
.dark {
--bg-color: rgb(20, 15, 10);
--a-color: rgb(255, 160, 80);
}
@layer theme {
:root:not(.dark) {
--custom-color1: rgb(80, 20, 0);
--custom-color2: rgb(180, 60, 0);
}
.dark {
--custom-color1: rgb(255, 220, 180);
--custom-color2: rgb(255, 160, 80);
}
}