Skip to content
PorabuildHeroGPUI component library
v0.14.0 · Apache-2.0

Customization

Create a named HeroGPUI theme by overriding semantic colors, layout tokens, and component recipes.

Start from a light or dark Theme and override the semantic colors, layout values, or ThemeBuilder::components recipes your application needs. Theme::builder(id, base) names the result, and derived color values follow the base token they came from. Empty component styles keep stock HeroUI metrics.

The builder

custom theme
use gpui::px;
use herogpui::core::{oklch, Color, FieldVariant};
use herogpui::theme::{
    snow, ButtonStyle, ComponentTheme, ComponentThemes, SelectStyle, SliderStyle, Theme,
};

let violet = Theme::builder("violet", Theme::light())
    .accent(oklch(0.55, 0.23, 295.0))   // hover / soft / focus all derive
    .role(Color::Success, oklch(0.73, 0.19, 150.0), snow())
    .radius(px(6.))                     // field_radius follows at 1.5x
    .components(
        ComponentThemes::default()
            .slider(ComponentTheme::new(SliderStyle::default().radius(px(9999.))))
            .select(
                ComponentTheme::new(SelectStyle::default().variant(FieldVariant::Secondary))
                    .recipe("compact", SelectStyle::default().height(px(28.))),
            )
            .button(ComponentTheme::new(ButtonStyle::default()).recipe(
                "compact",
                ButtonStyle::default().style(|el| el.h(px(28.)).px(px(10.))),
            )),
    )
    .build();

herogpui::theme::set_theme(violet, cx);
Button::new("ok").label("OK").recipe("compact");

Every method the builder exposes:

Theme builder methods
MethodWhat it sets
id(id)Names the theme — the id `use_theme(id, cx)` activates it by.
appearance(appearance)Light or dark — decides which shadow set and hairline the layout tokens use.
radius(px)`--radius`. `--field-radius` follows at 1.5× unless overridden afterwards.
field_radius(px)`--field-radius` on its own.
border_width(px)`--border-width`, the weight a separator draws.
disabled_opacity(f32)`--disabled-opacity`.
background / foreground / mutedThe base page tokens.
border / separator / focus / link / backdropThe remaining base tokens, each set on its own.
surface(bg, fg)`--surface` / `--surface-foreground` — cards, accordions, disclosure groups.
surface_levels(secondary, tertiary)`--surface-secondary` and `--surface-tertiary` together.
overlay(bg, fg)`--overlay` — tooltips, popovers, modals, menus.
segment(bg, fg)`--segment` — the selected segment of a segmented control.
role(Color, color, foreground)Any role's base value and foreground, by the typed `Color` role. `Color::Default` also re-seeds `field.background`.
accent(color)`--accent` and a foreground derived for readability when you do not supply one. `--focus` tracks it.
field(bg, fg) / field_placeholder / field_borderThe field tokens individually.
components(ComponentThemes)Typed component defaults and named recipes. Resolved live from ThemeProvider each frame. Not a ThemeDocument JSON key.
build()Returns the `Theme`.

Overriding a base token

Override a base token and its derived values follow. The override replaces the input while preserving the mix weights: a role's hover and soft ratios carry over, --focus keeps tracking --accent, and a foreground override flows into soft_foreground at render time without rebuilding the theme.

Rust
// Override one base token; every derived value follows.
let violet = Theme::builder("violet", Theme::light())
    .accent(oklch(0.55, 0.23, 295.0))
    .build();

// `accent.hover()` and `accent.soft()` are the same color-mix
// expressions, so they move with the base color.

Registering the result

set_theme(theme, cx) registers the theme under its id, activates it, and schedules every open window to repaint. The built-ins stay registered, so use_theme("light", cx) switches back — and a theme registered with set_theme is thereafter switchable to by id like any other. See Dark Mode for the switching rules. Renderers resolve Theme.components from the active provider each frame, so a later set_theme updates every .recipe("name") call site without rebuilding those builders.

Colour maths

The color functions used by the theme crate are public in herogpui-core. Use them to create OKLCH values, mix colors in Oklab and choose readable foregrounds:

Rust
use herogpui::core::{
    oklch, oklcha, mix_oklab, soft_mix, with_alpha, readable_color,
};

let brand    = oklch(0.55, 0.23, 295.0);   // lightness, chroma, hue
let translucent = oklcha(0.55, 0.23, 295.0, 0.6);
let hover    = mix_oklab(brand, foreground, 0.10);  // 10% toward the foreground
let soft     = soft_mix(brand, 0.15);               // 15% over transparent
let faint    = with_alpha(brand, 0.4);
let label    = readable_color(brand);               // a readable foreground for it