Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Augani/kael/llms.txt

Use this file to discover all available pages before exploring further.

Kael’s theming system is built around a single Theme struct that holds every visual token your application needs — colors, typography, spacing, corner radii, and box shadows. You can start from the built-in light or dark theme, load a fully custom theme from a JSON or TOML file, and hot-reload it automatically during development. Once registered as a global, any component can read the current theme through the App context.

The Theme struct

#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Theme {
    pub colors:     ThemeColors,
    pub typography: ThemeTypography,
    pub spacing:    ThemeSpacing,
    pub radii:      ThemeRadii,
    pub shadows:    ThemeShadows,
}
Theme implements Global, so it lives in Kael’s app context and can be updated from anywhere.

Built-in themes

impl Theme {
    pub fn light() -> Self
    pub fn dark() -> Self
    pub fn for_appearance(window: &Window) -> Self
}
Theme::for_appearance() reads the operating system’s current window appearance and returns the matching built-in theme:
fn build_window(&mut self, window: &Window, cx: &mut App) {
    let theme = Theme::for_appearance(window);
    cx.set_global(theme);
}
WindowAppearance has four variants:
VariantResolved theme
LightTheme::light()
VibrantLightTheme::light()
DarkTheme::dark()
VibrantDarkTheme::dark()

Loading from JSON or TOML

You can author your full design system in a text file and parse it at startup.
let json = include_str!("../themes/brand.json");
let theme = Theme::from_json_str(json)?;
cx.set_global(theme);
{
  "colors": {
    "background": "#0f0f11",
    "surface":    "#1a1a1f",
    "primary":    "#7c3aed",
    "foreground": "#e5e5ea",
    "border":     "#2c2c34"
  },
  "typography": {
    "ui_font_family": "Inter",
    "ui_font_size": 14,
    "ui_line_height": 20
  }
}

Registering as a global

Call Theme::init(cx) once at startup. This installs a sync subscription that keeps Kael’s internal color defaults in sync with your theme whenever you update it, and ensures all windows refresh automatically.
fn main() {
    App::new().run(|cx| {
        Theme::init(cx);
        // Theme::light() is set as the default if none is registered yet

        // Override with your brand theme
        let theme = Theme::from_path("themes/brand.toml").unwrap();
        cx.set_global(theme);
    });
}
To update the theme at runtime — for example when the user toggles dark mode — use cx.update_global:
cx.update_global::<Theme, _>(|theme, _cx| {
    *theme = Theme::dark();
});

Hot-reload with observe_theme_file

During development you can watch a theme file on disk and reload it automatically whenever it changes. Call cx.observe_theme_file() on the App context:
cx.observe_theme_file("themes/brand.toml", |theme, cx| {
    cx.set_global(theme);
})?;
observe_theme_file loads the theme immediately on startup, then watches the file’s parent directory for changes. It supports both .json and .toml files and logs errors without panicking when the file is temporarily invalid (e.g. mid-save).
observe_theme_file resolves the path relative to the current working directory when a relative path is provided. Use an absolute path in production to avoid surprises.

Design token reference

ThemeColors

Semantic color tokens. All values are Hsla.
pub struct ThemeColors {
    pub background:    Hsla,  // primary app background
    pub surface:       Hsla,  // elevated or contained surfaces
    pub primary:       Hsla,  // interactive emphasis / accent
    pub accent:        Hsla,  // secondary accent for links and details
    pub muted:         Hsla,  // disabled / muted foreground
    pub foreground:    Hsla,  // default readable text
    pub border:        Hsla,  // shared border color
    pub separator:     Hsla,  // divider color
    pub selected_text: Hsla,  // text on selected/primary surfaces
    pub error:         Hsla,  // error status
    pub warning:       Hsla,  // warning status
    pub success:       Hsla,  // success status
    pub custom: BTreeMap<SharedString, Hsla>, // project-specific tokens
}
Add your own semantic tokens in the custom map:
[colors.custom]
brand-gradient-start = "#7c3aed"
brand-gradient-end   = "#2563eb"

ThemeTypography

pub struct ThemeTypography {
    pub ui_font_family:   SharedString, // default ".SystemUIFont"
    pub ui_font_weight:   FontWeight,   // default NORMAL
    pub ui_font_size:     Pixels,       // default 14 px
    pub ui_line_height:   Pixels,       // default 20 px
    pub code_font_family: SharedString, // default "Menlo"
    pub code_font_size:   Pixels,       // default 13 px
}

ThemeSpacing

A six-step spacing scale used for gaps, padding, and margins:
pub struct ThemeSpacing {
    pub xs:  Pixels, // 4 px
    pub sm:  Pixels, // 8 px
    pub md:  Pixels, // 12 px
    pub lg:  Pixels, // 16 px
    pub xl:  Pixels, // 24 px
    pub xxl: Pixels, // 32 px
}
div().p(theme.spacing.md).gap(theme.spacing.sm)

ThemeRadii

Corner radius tokens for surfaces, buttons, and pills:
pub struct ThemeRadii {
    pub sm:   Pixels, // 4 px
    pub md:   Pixels, // 8 px
    pub lg:   Pixels, // 12 px
    pub xl:   Pixels, // 16 px
    pub pill: Pixels, // 999 px — fully rounded
}

ThemeShadows

Three elevation levels expressed as BoxShadow values:
pub struct ThemeShadows {
    pub sm: BoxShadow, // 0 1px 2px rgba(0,0,0,0.10)
    pub md: BoxShadow, // 0 8px 24px -8px rgba(0,0,0,0.14)
    pub lg: BoxShadow, // 0 16px 40px -12px rgba(0,0,0,0.18)
}
div()
    .rounded(theme.radii.lg)
    .shadow(theme.shadows.md)
    .bg(theme.colors.surface)

Reading the theme in components

Because Theme is a Global, read it inside any render method through the App context:
impl Render for MyView {
    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        let theme = cx.global::<Theme>();

        div()
            .bg(theme.colors.background)
            .text_color(theme.colors.foreground)
            .p(theme.spacing.lg)
    }
}
Clone only the fields you need to avoid holding a reference to the full theme across async boundaries. ThemeColors, ThemeSpacing, etc. all implement Clone.

Build docs developers (and LLMs) love