Themes
Three theme variants, no flicker on boot, instant live switching.
The three themes
Set in Settings → Appearance → Theme.
| Theme | When to use |
|---|---|
| System (default) | Follows your OS prefers-color-scheme. Updates live as your OS theme changes. |
| Dark | Always dark. The default visual. |
| Light | Always light. Tuned palette, not just inverted dark. |
| High Contrast | Black 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.