Skip to content

Themes

Kittox ships with a built-in theme system that controls the global look-and-feel of an application (colors, fonts, icons, light/dark mode). Everything is configured from a single Theme: node in Config.yaml.

Overview

The Theme node has the mode as its value (Light, Dark or Auto) and optional sub-nodes for fine-grained customization:

yaml
Theme: Auto
  Primary-Color: SteelBlue
  Font-Family: Segoe UI
  Font-Size: 13px
  IconStyle: outlined
  IconSize: Medium

If the Theme node is not specified at all, Kittox uses Auto with the default palette, system font and filled icons at Medium size.

Sub-nodeDefaultDescription
(value)AutoTheme mode: Light, Dark, or Auto (follows the OS preference via prefers-color-scheme)
Primary-Color(none)Base accent color — any CSS color name or #rrggbb value. Drives the toolbar, status bar and accent palette via CSS color-mix()
Font-Familysystem UI stackFont family used by the whole application
Font-Size13pxBase font size (any CSS size unit)
IconStylefilledMaterial Design Icon style: filled, outlined, round, sharp, two-tone
IconSizeMediumDefault icon size: Small (1em), Medium (1.4em), Large (1.8em)

Theme mode

The value of the Theme node selects the base palette:

ModeBehavior
LightForces the light palette. Rendered as <html data-theme="light">
DarkForces the dark palette. Rendered as <html data-theme="dark">
AutoNo data-theme attribute is emitted; CSS @media (prefers-color-scheme: dark) decides at runtime based on the user's OS setting
yaml
# Always light
Theme: Light

# Always dark
Theme: Dark

# Follow the OS (default)
Theme: Auto

TIP

Auto is the recommended default — it respects each user's OS/browser preference and switches dynamically when the user toggles dark mode without reloading the page.

Primary-Color

Setting Primary-Color generates a full chrome and accent palette from a single color. Internally, Kittox uses CSS color-mix() to derive:

  • toolbar and panel header background (--kx-chrome)
  • darker variant for status bar and hover states (--kx-chrome-dark)
  • lighter variants for splitters and group headers (--kx-chrome-light, --kx-chrome-mid)
  • accent color for focus rings, selected rows, active buttons (--kx-accent, --kx-accent-bg, --kx-accent-ring)
  • chrome text color — automatically chosen between light and dark based on the perceived luminance (ITU-R BT.601) of the primary color

Both theme-name values (FireBrick, SteelBlue, Green…) and hex values (#2c3e50) are supported.

yaml
# Ethea red
Theme: Light
  Primary-Color: FireBrick

# Corporate blue, dark mode
Theme: Dark
  Primary-Color: "#1e3a8a"

# Auto mode with a neutral accent
Theme: Auto
  Primary-Color: Teal

TIP

In Light mode the page text colors are also derived from Primary-Color (dark shades), giving a cohesive monochromatic look. In Dark mode only the chrome/accent overrides apply — page text stays on the default dark palette to preserve readability.

Font-Family and Font-Size

Font-Family and Font-Size override the CSS variables --kx-font and --kx-font-size on :root. They affect the whole application — forms, grids, toolbars, menus.

yaml
Theme: Light
  Font-Family: Inter
  Font-Size: 14px
yaml
Theme: Auto
  Font-Family: Comic Sans MS
  Font-Size: 12px

When omitted, Kittox uses the platform's native UI font stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif.

IconStyle

Kittox icons are Material Design Icons rendered via CSS mask-image. IconStyle picks one of the five Material variants — available for every icon:

ValueDescription
filled (default)Solid/filled glyphs
outlinedOutline-only glyphs, thinner look
roundRounded corner variant
sharpAngular, geometric variant
two-toneTwo-tone glyphs with primary and secondary shades
yaml
Theme: Light
  IconStyle: outlined

Because icons use CSS mask-image, their color follows the surrounding text color and automatically adapts to the active theme — no per-theme icon sets needed.

See Labels and Icons for the complete icon name map and per-view overrides.

IconSize

Sets the default icon size throughout the application:

ValueSize
Small1em
Medium (default)1.4em
Large1.8em
yaml
Theme: Light
  IconSize: Small

Individual icons can still override the default with the CSS classes kx-icon-sm, kx-icon-md, kx-icon-lg.

User-selectable theme

Theme: Auto is the recommended default precisely because it lets each user see the app in their preferred OS palette. Kittox can also expose a small three-button toggle (Light / Auto / Dark) to the end user, with the choice persisted in the browser. Two ingredients:

  1. Opt-in in Config.yaml — add UserSelection: True under Theme: Auto:

    yaml
    Theme: Auto
      UserSelection: True          # default False
      Primary-Color: SteelBlue

    The flag is honoured only when Theme: Auto. With Theme: Light or Theme: Dark the admin has fixed the palette and the switcher is suppressed — a server-side decision must not be silently overridden by a client-side widget.

  2. Place the switcher in any view via the ThemeSwitcher controller — typical spots are the login page and a topbar of the home view:

    yaml
    # LoginView.yaml — switcher below the form
    Controller:
      BorderPanel:
        NorthView:
          Controller: HtmlPanel
            Height: 110
            Html: ...
        SouthView:
          Controller: ThemeSwitcher
    yaml
    # Home.yaml — switcher in a thin topbar
    Controller: BorderPanel
      NorthView:
        Height: 44
        Controller: ThemeSwitcher
      CenterController: TabPanel
        ...

    The controller renders three small icon-buttons (☀ / ⊙ / 🌙). When UserSelection: True is not in effect the controller emits nothing — the container collapses (or, for fixed-height regions, shows empty). Drop it in defensively; it stays invisible until the admin opts in.

How it works under the hood

WhereWhat happens
Server (<head>)When Theme: Auto + UserSelection: True, Kittox emits a tiny inline script in <head> that runs synchronously before CSS paints, reads localStorage['kx_theme:<AppName>'] and sets <html data-theme="..."> accordingly. This anti-FOUC step is what guarantees no theme flash on first paint or reload.
Client (kxtheme.js)Public API: kxTheme.set(appName, mode) writes localStorage and updates data-theme. kxTheme.get(appName) returns the saved value (or 'auto'). A kx-theme-changed event is dispatched on window so any other widget can react.
PersistenceKey is kx_theme:<AppName>per-app scope. Two Kittox applications running in the same browser keep independent preferences. Value is 'light', 'dark', or removed ('auto' = follow OS).
Switch from "Auto"When the user picks Auto, the JS removes data-theme from <html>; the CSS @media (prefers-color-scheme: dark) rule takes over again.

Persistence scope

Persistence is per browser + per app (localStorage), not per server-side session — the choice survives logout, login as a different user, JWT expiry, and re-issue cookies. It is browser-private: clearing browser data resets it. To enforce a fixed palette regardless of user choice, set Theme: Light (or Dark) explicitly — the switcher disappears even if dropped in a view.

Live transitions

Switching theme is instant — no page reload. The CSS rules cascade on [data-theme] and the existing CSS custom-properties palette swaps over without any JavaScript orchestration beyond the attribute change. HTMX swaps that arrive after a theme change keep the chosen theme because data-theme lives on <html>, outside any HTMX target.

Charts and live theme refresh

Chart.js renders text on a <canvas> and reads Chart.defaults.color (a JS value) at the moment each chart instance is created — it does not re-read from CSS on every paint. Kittox integrates so that charts:

  • Match the active theme at creation time: before instantiating a chart, kxChart.init probes var(--kx-tree-leaf-text) via a temporary DOM element and reads getComputedStyle(probe).color. The probe forces the browser to resolve any functional CSS color notation (color-mix(), oklch(), …) into a plain rgb() string, which is the only form Chart.js's internal color parser understands.

  • React to the user's switch live: kxtheme.js dispatches a kx-theme-changed event on window whenever the user clicks the ThemeSwitcher. kxChart listens for it, re-resolves the text color in a requestAnimationFrame (giving the browser one frame to apply the new data-theme attribute), then writes the colour explicitly into every active chart instance — chart.options.color, plugins.legend.labels.color, plugins.title.color, scales.*.ticks.color, scales.*.title.color — and calls chart.update('none'). Without this, only freshly created charts pick up the new theme; existing ones would stay frozen on the colour cached at their new Chart() time.

Cascade specificity in dark mode

The server emits a <style> block in <head> that derives the chrome palette from Primary-Color via color-mix(). With Primary-Color: Green and Theme: Auto, the server's :root rule lands on the same specificity (0,1,0) as the CSS file's default [data-theme="dark"] selector — and wins by source order, leaking the green-derived light-mode colours into dark mode. Two defences are in place:

LayerDefence
kittox.cssThe dark theme block uses the type-qualified selector html[data-theme="dark"] (specificity 0,1,1), so it wins over the server's :root (0,1,0) regardless of source order.
Server <style>The dark block also emits explicit neutral resets for --kx-text, --kx-text-secondary, --kx-text-muted, --kx-tree-folder-text, --kx-tree-leaf-text at html[data-theme="dark"] (0,1,1), beating the server's own :root (0,1,0). This makes the fix robust even if the browser still has the previous version of kittox.css cached.

--kx-accent-bg is similarly split: light theme keeps color-mix(Primary, white 85%) (pastel on a light surface), dark theme switches to color-mix(Primary, transparent 88%) (translucent over a dark surface). The two values are emitted in their respective scopes, not in the shared chrome block.

Real-world examples

HelloKitto — branded red + custom font

HelloKitto

From Examples/HelloKitto/Home/Metadata/Config.yaml:

yaml
Theme: Light
  Primary-Color: FireBrick
  Font-Family: Comic Sans MS
  Font-Size: 12px
  IconStyle: filled

Produces a playful, strongly-branded UI with red chrome/accent and a casual font.

KEmployee — minimalist outlined light

KEmployee

From Examples/KEmployee/Home/Metadata/Config.yaml:

yaml
Theme: Light
  IconStyle: outlined

Defaults for everything else — clean corporate look.

TasKitto — dark mode with small two-tone icons

TasKitto

From Examples/TasKitto/Home/Metadata/Config.yaml:

yaml
Theme: Dark
  Font-Size: 12px
  IconStyle: two-tone
  IconSize: medium

A compact dark UI; two-tone icons provide gentle color cues against the dark chrome.

CSS custom properties

Every theme value is applied through a CSS variable on :root (light / auto) or [data-theme="dark"] (dark). The most relevant ones:

VariableRole
--kx-font, --kx-font-sizeBase font family and size
--kx-bgPage background
--kx-surface, --kx-surface-altPanel and card surfaces
--kx-text, --kx-text-secondary, --kx-text-mutedPrimary text tones
--kx-chrome, --kx-chrome-dark, --kx-chrome-hover, --kx-chrome-mid, --kx-chrome-lightToolbar / header palette
--kx-chrome-text, --kx-chrome-btn-hoverText and button hover on chrome
--kx-accent, --kx-accent-bg, --kx-accent-ringFocus rings, selected rows, active buttons
--kx-status-bg, --kx-status-text, --kx-status-borderStatus bar
--kx-input-bg, --kx-input-borderForm input fields
--kx-border, --kx-border-lightSeparators and rules
--kx-error, --kx-error-surface, --kx-error-borderValidation / danger colors
--kx-overlay, --kx-shadowModal overlay and drop shadows

The complete list lives in Home/Resources/css/kittox.css. To override individual variables (without re-declaring the whole theme), see CSS Theming — it covers application.css, the custom-stylesheet file that is picked up automatically by every Kittox application.

See also

Released under Apache License, Version 2.0.