Theming
Every built-in component reads its colors from a theme, not from hardcoded literals. A single shared Palette is the source of truth for color; each component derives its own styles from it. Change the palette in one place and the whole tree re-colors consistently. The protocol is always-on (no feature flag, no extra dependency).

The model
Section titled “The model”Theming has two layers:
Palette— a flat set of semantic colors (accent,border,selection,error, …). This is the one thing you usually tune.FooTheme— one per component (BorderTheme,SelectTheme,TableTheme, …). Each derives its style slots from the palette viaComponentTheme::from_palette. You rarely touch these unless you want to re-style one component type.
Each component resolves its theme through a fixed three-level chain, taking the first that applies:
Because FooTheme::default() == FooTheme::from_palette(&Palette::default()) by convention, the last two levels collapse into a single from_palette call: with no PaletteProvider, components fall back to Palette::default().
Palette: the semantic colors
Section titled “Palette: the semantic colors”Palette is #[non_exhaustive] — construct it from Palette::default() and change the fields you care about (new semantic slots can be added later without breaking you):
The slots, grouped by role:
| Group | Slots | Used for |
|---|---|---|
| Text | fg, fg_dim, placeholder | Body text, secondary/disabled text, placeholders |
| Surfaces | bg, surface, overlay | Background, raised panels, modal backdrops |
| Accent | accent, on_accent, selection | Highlights / active borders, text on an accent fill, selection background |
| Borders | border, border_active | Default borders, focused/active borders |
| Semantic | success, warning, error, info | Status colors |
The default palette keeps fg/bg on the terminal default (Color::Reset), uses a cyan accent, dim-gray borders, and green/yellow/red/blue for the semantic slots. Highlights pair on_accent foreground with a selection background.
Apply a theme: PaletteProvider
Section titled “Apply a theme: PaletteProvider”Wrap a subtree in PaletteProvider to give it a palette. It is a transparent-layout node — it does not occupy a layout box.
Every component inside now derives its colors from palette: the border, the select highlight, everything. No component needs to opt in.
Switch themes at runtime
Section titled “Switch themes at runtime”Context reads are passive — reading the palette does not subscribe a component to changes. To re-theme a live app, drive the palette from reactive state: put it in an Atom<Palette> (or use_state) and feed that into PaletteProvider. Writing the atom wakes the tree and the next frame re-colors.
This is the same pattern the theme example uses — press a key to cycle presets.
Per-call overrides: Option<Style>
Section titled “Per-call overrides: Option<Style>”Every component’s style prop is an Option<Style>:
None(the default) — use the theme.Some(style)— patch on top of the theme:theme.patch(style), so the fields you set win and the rest stay themed.Some(Style::reset())— clear the slot back to the terminal default.
You can pass a bare Style too — the element! macro coerces it to Some automatically, so border_style: Style::new().magenta() also works.
Re-style one component type: ThemeOverride
Section titled “Re-style one component type: ThemeOverride”To change the theme of one component type within a subtree — without touching the palette — inject a FooTheme override with ThemeOverride. It is the first level of the resolve chain.
Overrides nest: a ThemeOverride deeper in the tree shadows one above it, exactly like context.
Each component’s theme
Section titled “Each component’s theme”Every built-in component exposes its FooTheme (same feature gate as the component). Always-on: TextTheme, BorderTheme, ModalTheme, ConfirmModalTheme, AlertModalTheme, ShortcutInfoModalTheme, SelectTheme, MultiSelectTheme. Feature-gated: InputTheme / SearchInputTheme (input), TreeSelectTheme (tree), VirtualListTheme (virtual-list), TableTheme (table).
A composed component (like ConfirmModal) resolves its own theme and passes fully-resolved styles down to the primitives it wraps, so the inner Border/Text render faithfully without double-theming. Modal backdrops (DIM) are owned by ModalTheme and delegated.
Theme your own component
Section titled “Theme your own component”Third-party components join the system by implementing ComponentTheme (Clone + Default + 'static) and reading it in update:
Then read the resolved theme inside your component. A function component uses the hook; a hand-written Component uses the updater method (it returns an owned value, so no borrow guard lingers into update_children):
Both #[non_exhaustive] on the theme and the Default == from_palette(&Palette::default()) convention keep your theme forward-compatible and consistent with the resolve chain. Users then re-style your component with ThemeOverride::<GaugeTheme>(...), and it picks up their global Palette for free.
See also
Section titled “See also”- State — a global theme belongs in an
Atom; a subtree theme belongs in a Provider. - Custom provider — how context injection and nearest-layer lookup work.