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:
| Variant | Resolved theme |
|---|
Light | Theme::light() |
VibrantLight | Theme::light() |
Dark | Theme::dark() |
VibrantDark | Theme::dark() |
Loading from JSON or TOML
You can author your full design system in a text file and parse it at startup.
JSON
TOML
File path (auto-detect)
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
}
}
let toml = include_str!("../themes/brand.toml");
let theme = Theme::from_toml_str(toml)?;
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
// Accepts both .json and .toml; format is inferred from the extension.
let theme = Theme::from_path("themes/brand.toml")?;
cx.set_global(theme);
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.