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

Layout

Toolbar

A container for interactive controls with arrow-key navigation.

Rust
use herogpui::components::toolbar::Toolbar;

Toolbar on docs.rsSource at v0.14.0

Usage

Showing Usage, live: HeroGPUI compiled to WebAssembly. The canvas has no accessibility tree; each example's description and code below carry the same information.

Usage

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Button, Size, Toolbar, Variant};
use herogpui::gpui::prelude::*;

Toolbar::new()
    .id("toolbar")
    .child(
        Button::new("toolbar")
            .label("Cut")
            .variant(Variant::Tertiary)
            .size(Size::Sm),
    )
    .child(
        Button::new("toolbar")
            .label("Copy")
            .variant(Variant::Tertiary)
            .size(Size::Sm),
    )
    .child(
        Button::new("toolbar")
            .label("Paste")
            .variant(Variant::Tertiary)
            .size(Size::Sm),
    )

With ButtonGroup

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Button, ButtonGroup, Size, Toolbar, Variant};
use herogpui::gpui::prelude::*;

Toolbar::new()
    .id("toolbar-button-group")
    .child(
        ButtonGroup::new()
            .variant(Variant::Secondary)
            .size(Size::Sm)
            .separators(true)
            .button(Button::new("toolbar-bg-1").label("Left"))
            .button(Button::new("toolbar-bg-2").label("Center"))
            .button(Button::new("toolbar-bg-3").label("Right")),
    )
    .separator()
    .child(
        Button::new("toolbar-bg-4")
            .label("Reset")
            .variant(Variant::Tertiary)
            .size(Size::Sm),
    )

Horizontal

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::{Button, ButtonGroup, Orientation, SelectionMode, Size, ToggleButton, ToggleButtonGroup, Toolbar, Variant};
use herogpui::gpui::prelude::*;

let bar = |key: &str, attached: bool, orientation: Orientation| {
    Toolbar::new()
        // Names the instance: the toolbar's keyed focus state is per
        // id, and three toolbars share this page.
        .id(format!("toolbar-{key}"))
        .is_attached(attached)
        .orientation(orientation)
        .child(
            ToggleButtonGroup::new(format!("toolbar-toggle-{key}"))
                .selection_mode(SelectionMode::Multiple)
                .separators(true)
                .child_toggle(
                    ToggleButton::new(format!("toolbar-{key}-b")).label("B"),
                )
                .child_toggle(
                    ToggleButton::new(format!("toolbar-{key}-i")).label("I"),
                ),
        )
        // The bar crosses its own flow, so the divider needs no
        // orientation here.
        .separator()
        .child(
            ButtonGroup::new()
                .variant(Variant::Tertiary)
                .size(Size::Sm)
                .separators(true)
                .button(Button::new(format!("toolbar-{key}-copy")).label("Copy"))
                .button(Button::new(format!("toolbar-{key}-cut")).label("Cut")),
        )
};

bar("h", false, Orientation::Horizontal)
    .full_width(true)

Attached

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::{Button, ButtonGroup, Orientation, SelectionMode, Size, ToggleButton, ToggleButtonGroup, Toolbar, Variant};
use herogpui::gpui::prelude::*;

let bar = |key: &str, attached: bool, orientation: Orientation| {
    Toolbar::new()
        // Names the instance: the toolbar's keyed focus state is per
        // id, and three toolbars share this page.
        .id(format!("toolbar-{key}"))
        .is_attached(attached)
        .orientation(orientation)
        .child(
            ToggleButtonGroup::new(format!("toolbar-toggle-{key}"))
                .selection_mode(SelectionMode::Multiple)
                .separators(true)
                .child_toggle(
                    ToggleButton::new(format!("toolbar-{key}-b")).label("B"),
                )
                .child_toggle(
                    ToggleButton::new(format!("toolbar-{key}-i")).label("I"),
                ),
        )
        // The bar crosses its own flow, so the divider needs no
        // orientation here.
        .separator()
        .child(
            ButtonGroup::new()
                .variant(Variant::Tertiary)
                .size(Size::Sm)
                .separators(true)
                .button(Button::new(format!("toolbar-{key}-copy")).label("Copy"))
                .button(Button::new(format!("toolbar-{key}-cut")).label("Cut")),
        )
};

bar("attached", true, Orientation::Horizontal)

Vertical

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::{Button, ButtonGroup, Orientation, SelectionMode, Size, ToggleButton, ToggleButtonGroup, Toolbar, Variant};
use herogpui::gpui::prelude::*;

let bar = |key: &str, attached: bool, orientation: Orientation| {
    Toolbar::new()
        // Names the instance: the toolbar's keyed focus state is per
        // id, and three toolbars share this page.
        .id(format!("toolbar-{key}"))
        .is_attached(attached)
        .orientation(orientation)
        .child(
            ToggleButtonGroup::new(format!("toolbar-toggle-{key}"))
                .selection_mode(SelectionMode::Multiple)
                .separators(true)
                .child_toggle(
                    ToggleButton::new(format!("toolbar-{key}-b")).label("B"),
                )
                .child_toggle(
                    ToggleButton::new(format!("toolbar-{key}-i")).label("I"),
                ),
        )
        // The bar crosses its own flow, so the divider needs no
        // orientation here.
        .separator()
        .child(
            ButtonGroup::new()
                .variant(Variant::Tertiary)
                .size(Size::Sm)
                .separators(true)
                .button(Button::new(format!("toolbar-{key}-copy")).label("Copy"))
                .button(Button::new(format!("toolbar-{key}-cut")).label("Cut")),
        )
};

bar("v", false, Orientation::Vertical)

Sized Controls

HeroGPUI extension, not a example (after gpui-kit's toolbar): `Toolbar::size` reaches every control added with `sized_child`, whatever the order of the builder calls, so the whole bar is sized in one place; controls added with `child` keep their own size. `label` names the toolbar (or the group a nested toolbar becomes) for assistive technology. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Button, Size, ToggleButton, Toolbar, Variant};
use herogpui::gpui::prelude::*;

Toolbar::new()
    .id("toolbar-sized")
    .label("Formatting")
    .size(Size::Sm)
    .sized_children([
        Button::new("toolbar-sized-undo").label("Undo").variant(Variant::Tertiary),
        Button::new("toolbar-sized-redo").label("Redo").variant(Variant::Tertiary),
    ])
    .separator()
    .sized_child(
        ToggleButton::new("toolbar-sized-bold").label("Bold"),
    )

Title Bar

HeroGPUI extension, not a example: `TitleBar` is custom window chrome for a frameless window (open it with `TitleBar::window_options()`). Its empty area drags the window and a double-click zooms it; children such as the Share button keep their own presses. `TitleBarControls::Custom` draws minimize, maximize and close on every platform (`Auto` keeps the macOS traffic lights and lets Windows perform its caption buttons). This demo intercepts every action with `on_window_action` and only a drag is performed, so dragging the bar below moves the gallery window. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::{ActiveTheme, Button, Size, TitleBar, TitleBarControls, Toolbar, Variant, WindowAction};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, SharedString, px};
use herogpui::gpui;

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![
        gpui::div()
            .w(px(560.))
            .rounded(px(12.))
            .border_1()
            .border_color(cx.colors().border)
            .overflow_hidden()
            .child(
                TitleBar::new("toolbar-title-bar-chrome")
                    .title("Untitled — HeroGPUI")
                    .controls(TitleBarControls::Custom)
                    .on_window_action(cx.listener(|this, action: &WindowAction, window, cx| {
                        if *action == WindowAction::Move {
                            action.perform(window);
                        }
                        this.title_bar_last = SharedString::from(format!("{action:?}"));
                        cx.notify();
                    }))
                    .child(
                        Button::new("toolbar-title-bar-share")
                            .label("Share")
                            .variant(Variant::Tertiary)
                            .size(Size::Sm),
                    ),
            )
            .child(gpui::div().h(px(96.)).bg(cx.colors().background))
            .into_any_element(),
        para(&format!("Last window action: {}", self.title_bar_last), cx),
    ])

Also calls para (included when you copy).

Helpers used by these examples

Plain functions over the public API, defined once here and copied with each example that calls them.

para
fn para(text: &str, cx: &App) -> gpui::AnyElement {
    let colors = cx.colors();
    gpui::div()
        .text_size(px(14.5))
        .line_height(px(24.))
        .text_color(colors.foreground)
        .max_w(px(720.))
        .child(text.to_owned())
        .into_any_element()
}

Anatomy

Rust types

Toolbar

Toolbar is assembled from these builders. The API reference lists each one.

Customization

Shared appearance belongs on ThemeBuilder::components and .recipe("name"). Typed builders change how Toolbar looks at one call site. Every builder also carries sx for placement, width and flex: it takes GPUI's own styling methods and refines them over the component's root after the theme, so an instance override wins. Behaviour builders live in the API reference below.

Styling

Toolbar styling
MethodValuesDescription
orientation(Orientation)Orientation::Horizontal | Orientation::VerticalThe orientation of the toolbar; the axis picks which arrows walk it.
Rust
// The one slot for caller-owned low-level styling: GPUI's
// styling methods, refined over the root element last.
.sx(|el| {
    el.bg(gpui::rgba(0xffa500ff))             // background
        .text_color(gpui::rgba(0x000000ff))   // text
        .w(gpui::px(13.))                     // size
        .h(gpui::px(12.))
})

API reference

Against HeroUI v3.2.6: 7 entries ported, 1 partial, 4 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.

Builders

Toolbar builders
BuilderValuesDefaultDescription
is_attached(bool)true | falsefalseWhether the toolbar has a surface background with full rounding.
orientation(Orientation)Orientation::Horizontal | Orientation::VerticalOrientation::HorizontalThe orientation of the toolbar; the axis picks which arrows walk it.
Not ported4 HeroUI entries not ported
Toolbar builders not ported
HeroUI v3Why it is not ported
Toolbar.aria-labelAccessible name; gpui has no accessibility tree to name it in.
Toolbar.aria-labelledbyAccessible name by element id; gpui has no accessibility tree.
Toolbar.className; gpui has no class-name surface.
ToolbarRenderProps.orientationHanded to children-as-function; the port's children are plain elements and the orientation is a builder value, so nothing receives it.

Parts

Toolbar parts
PartDescription
ToolbarRoot container; set its orientation before separator() to append a centred divider half the bar's cross size.

States

Toolbar states
StateBuilderDescription
Focused childchild tab stopsFocus lands on the child controls, never the container; each child draws its own ring and the orientation-axis arrows walk the children.
Disabled childchild control is_disabledA disabled child is no tab stop: the arrows skip it in both directions and Tab walks past a toolbar with none enabled.
Last focused childToolbarFocusEdge.last_focusedThe pinned hook records the child the focus left from and restores it when the focus re-enters from outside; keeps the record in keyed state and skips a recorded child whose element has left the frame, exactly as pinned's focus-capture does.
Nested toolbar as groupToolbarScopes registryPinned useToolbar detects a toolbar inside another with parentElement.closest('[role=toolbar]') and the nested one binds no keyboard or focus management of its own — the enclosing toolbar's arrows walk straight across its children and one Tab leaves the whole subtree. Asks the same question of the last rendered frame's dispatch tree: a weak per-window registry of toolbar scopes plus FocusHandle::contains, re-checked at event time against the frame each key dispatched against.