Themes

Three theme variants, no flicker on boot, instant live switching.

The three themes

Set in Settings → Appearance → Theme.

ThemeWhen to use
System (default)Follows your OS prefers-color-scheme. Updates live as your OS theme changes.
DarkAlways dark. The default visual.
LightAlways light. Tuned palette, not just inverted dark.
High ContrastBlack bg, white fg, amber accents. AAA contrast on every interactive element.

The picker is a tile grid — each tile previews the actual theme colors. Hover any tile to live-preview it without committing; click to commit.

No flicker on boot

A short inline <script> in <head> reads localStorage["kf-theme"], resolves "system" via prefers-color-scheme, and writes <html data-theme="…"> before any CSS loads. Result: no FOUC (flash of unstyled content) when refreshing.

Live switching

Theme changes are applied via CSS custom properties on :root. No page reload, no animation (instant swap to avoid nausea).

JS surfaces register a callback if they need to react:

window.KFTheme.onChange(({ theme, resolved }) => { … });

Drawers, charts, and Monaco re-render on the change.

Cross-tab sync

Theme is written to localStorage. Other open tabs receive a storage event and update immediately. Useful when you have multiple workspace tabs open.

Color tokens

All colors are defined as CSS variables in static/css/base.css. Custom themes (self-host only) extend by adding a data-theme="custom" block:

[data-theme="custom"] {
  --bg-0: #0c0a18;
  --ink-0: #eee;
  --accent: #ff66cc;
  …
}

Reduced motion

prefers-reduced-motion: reduce disables all transitions, animations, and transforms across the app. Theme switches stay instant either way.

Focus rings

Every theme has its own --focus-ring:

  • Dark / Light: 1px outline with the accent color.
  • High Contrast: 2px outline, always visible, never suppressed.