Theme architecture

Krysalicss ships three themes (default, dark, high-contrast) — each one targeting a different surface context. This page explains why each theme exists and which trigger activates it. For the lookup-shaped contrast table see the Themes reference; to author your own, start at Author a custom theme.

default

The light, OS-default theme. Emits at :root with no media-query gate, so it's the fallback whenever no other theme has opted in. White surface (#ffffff) and near-black ink (#27282b) over a deep-accent palette (sage, moss, amber, coral) with a single light tint (mint). Every accent hits WCAG 2.2 AAA body text (≥ 7:1) against its white foreground and WCAG 1.4.11 (≥ 3:1) against the white page so each component's edge stays legible. The link token reuses sage (#1a6347, 7.2:1) so brand identity, primary surface and link colour share the same single source.

bg #fffffffg #27282blink #1a6347primary-bg #247d59success-bg #525a22warning-bg #7a4f10danger-bg #8c351afocus-ring #1a6347

dark

The dark counterpart, hue-aligned with default so brand-feel carries across themes. Two activation triggers are wired: the prefers-color-scheme: dark media query (auto-switching when the OS is in dark mode) and the explicit .theme-dark class (manual override regardless of OS preference). Either path resolves to the same declaration block.

bg #121417fg #e6e8eblink #6bd1a7primary-bg #1f6e51success-bg #5c6529warning-bg #b8991edanger-bg #a2481afocus-ring #6bd1a7

high-contrast

Opt-in only: not auto-switched via prefers-contrast: more, because forcing this aggressive palette on every user with a system-level high-contrast preference is a worse default than leaving the choice explicit. Every (bg, fg) pair clears WCAG 2.2 AAA (7:1+) and border radius is zeroed so component edges stay legible at low vision.

bg #000000fg #fffffflink #ffd500primary-bg #00d0ffsuccess-bg #00ff84warning-bg #ffd500danger-bg #ff8585focus-ring #ffd500

Why three themes — and why opt-in only for the third

The bundled set is deliberately small. Each entry earns its place by targeting a context the other two can't cover:

  • default is the unconditional fallback. Removing it would mean every consumer has to opt into something before getting a working page — a worse first-run experience than shipping a neutral light baseline.
  • dark rides on the most common runtime cue (prefers-color-scheme) and adds an explicit class override for users who want to overrule their OS. Auto-switching keeps the framework usable on every modern device without a JS theme switcher.
  • high-contrast exists for low-vision and high-glare contexts. It is not auto-switched: forcing AAA-contrast colours on every user with prefers-contrast: more swings too hard for many of them. The framework leaves that choice to consumers and to in-page toggles.

The shipped themes are a starting kit, not a closed set. Community themes ride on the same theme() mixin and the same required-keys contract. The path from a custom palette to a published npm package is covered end-to-end in Author and publish a theme: palette validation against WCAG, the theme() call shape, packaging, and pnpm publish.

Further reading

  • Themes reference — every shipped theme's (bg, fg) pairs and per-pair contrast ratio, plus forced-colors interaction.
  • Theme contract reference — the required keys, the theme() mixin signature, and the error semantics for incomplete maps.
  • Variable layers — the declarative / semantic / runtime split that lets themes re-tune everything without touching component source.
  • Author a custom theme — the in-app, single-consumer recipe.
  • Author and publish a theme — the full publish flow for community themes.