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

Styling

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

Use documented builders for variants, ThemeBuilder::components for shared component appearance, theme tokens for colours, 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
// Variants and sizes are enums, checked at compile time.
Button::new("edit")
    .label("Edit")
    .variant(Variant::Secondary)
    .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
Shared lookThemeBuilder::components / .recipe("name")Put height, padding, radius and semantic colours on the theme. Instance sx is for placement, width and flex.
Override.sx(|el| el.w(..))One slot per component for caller-owned placement and one-off sizing; it refines the root element last, so it wins.
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.
Radiusextend::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 sx slot

Every component builder carries one sx slot for caller-owned low-level styling: GPUI's styling methods, refined over the component's root element after every value the variant and the theme chose, so an override wins. State-driven layers the component draws itself — a hover fade, a press scale — read the override where they can.

Rust
// Every component carries one sx slot: GPUI's styling methods,
// refined over the root element after the theme's values.
Button::new("save")
    .label("Save")
    .sx(|el| el.bg(gpui::rgba(0xffa500ff)).text_color(gpui::rgba(0x000000ff)))

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
extend::control_radius(cx)3xl (24px)button, toggle button, avatar
extend::soft_radius(cx)2xl (16px)chip, menu and list rows, colour area
extend::small_radius(cx)xl (12px)close button, tag, link, tooltip
extend::key_radius(cx)lg (8px)Kbd
extend::hairline_radius(cx)sm (4px)separator, skeleton
extend::field_radius(cx)12pxevery form field
extend::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
// .hover() styles an element you own; components do the same
// internally with anim::hover_fade, and a press is anim::pressed.
let colors = cx.colors();
let resting = colors.surface.background;
let hovered = colors.default.soft();

div()
    .id("row")
    .bg(resting)
    .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
// The closure is handed the value the component computed.
Slider::new("volume", 50.)
    .thumb(|index, value| {
        div().child(format!("thumb {index}: {value}")).into_any_element()
    })

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
/// A save button, everywhere the same.
fn save_button(id: impl Into<ElementId>) -> Button {
    Button::new(id).variant(Variant::Primary).child("Save")
}

// Still a `Button`, so the caller keeps every other prop.
save_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.