Create your own Look

Start with one control. Reuse its SDK behavior.

Start small

A Look can begin as an application helper. Keep your visual decisions together and return SDK control entities.

Local Look helper · integration snippet
use gpui::{prelude::*, Context, Entity, px};
use xgpui_luma::controls::button::Button;
use xgpui_luma::infra::presenter::HasPresenter;

fn action_button<M: 'static>(cx: &mut Context<M>) -> Entity<Button> {
    Button::new("action")
        .label("Continue")
        .with_template_modifier(|root, _| root.min_w(px(160.0)))
        .spawn(cx)
}

This customizes the fallback template’s geometry. It is a starting seam, not a complete visual system.

Inherit the SDK baseline

The authoritative baseline is authored in Rust and cached by CommonStylesheet::sdk_default(), with monochrome light/dark UI colors. SDK-only applications and custom Looks can clone that baseline to inherit shared tokens, control geometry, icons and semantic state recipes. Configure only your differences, then capture a theme snapshot:

Rust · SDK theme snapshot
use xgpui_luma::{
    color::GamutMapping,
    controls::progress::{DefaultProgressTheme, ProgressTheme},
    theme::{ControlSize, ThemeMode, stylesheet::CommonStylesheet},
};

fn configured_progress_theme() -> Result<DefaultProgressTheme, Box<dyn std::error::Error>> {
    let sheet = CommonStylesheet::sdk_default().clone().with(|sheet| {
        sheet.progress.sizes.md.track_height = Some(9.0);
    });
    let tokens = sheet.theme_tokens(ThemeMode::Light, GamutMapping::CssLocalMinde)?;
    let theme = DefaultProgressTheme::new(tokens);
    assert_eq!(theme.resolve(true, ControlSize::Md).track_height, 9.0);
    Ok(theme)
}

Pass the theme to the existing SDK themed template. Existing snapshots keep their styling; rebuild themes after editing configuration. Missing values inherit, while zero remains an explicit override. Palette, metrics, typography and elevation are configured under common.tokens. The complete control/helper inventory records which layout and interaction constants remain in Rust. See the stylesheet contract for current coverage.

Customize shared styles in Rust

Clone the SDK baseline, then edit its typed fields. Semantic mappings reference palette roles without copying colors.

Rust · custom look differences
fn custom_stylesheet() -> xgpui_luma::theme::stylesheet::CommonStylesheet {
    use xgpui_luma::theme::stylesheet::{CommonStylesheet, PaletteRole};
    CommonStylesheet::sdk_default().clone().with(|sheet| {
        sheet.recipes.0.entry("button".into()).or_default().insert(
            PaletteRole::StateHoverBackground,
            PaletteRole::StateSelectedBackground,
        );
        sheet.row_limits.0.entry("combobox".into()).or_default().max = Some(10);
        sheet.switch.sizes.sm.track_width_scale = Some(32.0 / 28.0);
    })
}

Use floating-point expressions for ratios. Direct edits change the named fields; changing general geometry does not clear inherited size-specific values. Set the relevant size entries when needed. Explicit consumer icons, geometry and row limits override captured defaults. SDK themes capture snapshots; rebuild them after changing configuration. Motion keeps its existing independent snapshot policy.

Grow it when needed

For a worked example of creating a template and adding behavior functions where needed, see How to create a control specialization.

For live light/dark switching, keep mode state in the application and bind themes that resolve the selected SDK snapshot. Changing a mode variable does not update an existing Default*Theme snapshot. Request an owner redraw and preserve control entities so selection, focus, and scroll state survive. Plot Viz demonstrates this with application-local SDK theme adapters; chart series colors live in independent named HSLA palettes.

Add shared palette and metric configuration. Supply theme resolution or your own templates, then expose convenient builders. Keep focus, activation, and events in the SDK.

Template replacement example · A complete Look adapter