Styling

Style HeroGPUI components with typed props, theme tokens, slots, and render closures.

Use documented builders for variants, theme tokens for shared values, GPUI's styling methods for elements you own, and render closures for state-aware content.

Variants carry the intent

Use the documented builder first. Variants, sizes and colors are typed values, so the component API makes the available choices explicit. Use the hierarchy below to compare the meaning of each button variant:

Rust
1// Variants and sizes are enums, checked at compile time.
2Button::new("edit")
3    .label("Edit")
4    .variant(Variant::Secondary)
5    .size(Size::Lg)

How to style

Use a documented builder for variants, a theme token for shared values, and GPUI's styling methods for elements you own. There are no class strings to pass through.

Styling routes
RouteRustWhen
Layoutfull_width(true)Use the builder the component documents, or wrap it in a styled div you own.
Colourcx.role(Color::Accent)Read a theme token so the value follows light and dark instead of pinning a shade.
Radiusutil::soft_radius(cx)One helper per radius step — see the table below. Each component names its own radius.
Spacing and type.px(px(12.)).text_sm()GPUI's Styled methods on the element you own. Inside a component, they stay there.

The radius helpers

Each component uses a specific radius step, so util exposes one helper per step rather than a single universal radius:

Corner radius helpers
HelperStepUsed by
util::control_radius(cx)3xl (24px)button, toggle button, avatar
util::soft_radius(cx)2xl (16px)chip, menu and list rows, colour area
util::small_radius(cx)xl (12px)close button, tag, link, tooltip
util::key_radius(cx)lg (8px)Kbd
util::hairline_radius(cx)sm (4px)separator, skeleton
util::field_radius(cx)12pxevery form field
util::container_radius(cx)min(32px, 3xl)cards, the table and every floating panel. Surface carries none — `.surface` declares no radius.

State-based styling

Components expose hover, press and disabled state through the Rust API. Use .hover() for an element you own, the built-in animation helpers for presses, and is_disabled for disabled controls.

Rust
1// .hover() styles an element you own; components do the same
2// internally with anim::hover_fade, and a press is anim::pressed.
3let colors = cx.colors();
4let resting = colors.surface.background;
5let hovered = colors.default.soft();
6
7div()
8    .id("row")
9    .bg(resting)
10    .hover(move |s| s.bg(hovered))

Render closures

Render closures let you draw a component part from the state or value the component already computed. The closure receives that value, so the caller does not need to re-derive it.

Rust
1// The closure is handed the value the component computed.
2Slider::new("volume", 50.)
3    .thumb(|index, value| {
4        div().child(format!("thumb {index}: {value}")).into_any_element()
5    })

Wrapper components

To standardize a set of props, return a configured builder from a function. Builders are plain Rust values, so the caller can still set every remaining option.

Rust
1/// A save button, everywhere the same.
2fn save_button(id: impl Into<ElementId>) -> Button {
3    Button::new(id).variant(Variant::Primary).child("Save")
4}
5
6// Still a `Button`, so the caller keeps every other prop.
7save_button("save").is_pending(saving).full_width()

Style through the Rust API

The Button struct and its variant method select the look, part builders such as CardHeader place the pieces, and theme tokens supply the values.

Control heights and widths

Desktop control heights are 32/36/40 for sm/md/lg. A labelled button has no minimum width: it hugs its content.