Button Group
Group related buttons with a shared variant and merged edges.
use herogpui::components::button_group::ButtonGroup;ButtonGroup 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.
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, Button, ButtonGroup, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![ButtonGroup::new()
.separators(true)
.button(Button::new("button-group-1").label("Merge pull request"))
.button(
Button::new("button-group-2").is_icon_only(true).child(
gpui::svg()
.size(px(16.))
.path(icons::CHEVRON_DOWN)
.text_color(cx.colors().accent.foreground),
),
)
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, ButtonGroup, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![ButtonGroup::new()
.variant(Variant::Secondary)
.separators(true)
.button(Button::new("button-group-1").label("Day"))
.button(Button::new("button-group-2").label("Week"))
.button(Button::new("button-group-3").label("Month"))
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, ButtonGroup, Size, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(Size::ALL
.iter()
.filter_map(|sz| {
let key = format!("bgroup-size-{sz:?}");
Some({
ButtonGroup::new()
.variant(Variant::Secondary)
.size(*sz)
.separators(true)
.button(
Button::new(format!("button-group-{sz:?}-1"))
.label("Left"),
)
.button(
Button::new(format!("button-group-{sz:?}-2"))
.label("Middle"),
)
.button(
Button::new(format!("button-group-{sz:?}-3"))
.label("Right"),
)
})
}))An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, Button, ButtonGroup, Variant, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![ButtonGroup::new()
.variant(Variant::Tertiary)
.separators(true)
.button(
Button::new("button-group-1")
.child(gpui::svg()
.size(px(16.))
.path(icons::COPY)
.text_color(cx.colors().foreground)
.into_any_element())
.child("Fork"),
)
.button(
Button::new("button-group-2")
.child(gpui::svg()
.size(px(16.))
.path(icons::PLUS)
.text_color(cx.colors().foreground)
.into_any_element())
.child("Star"),
)
.button(
Button::new("button-group-3")
.is_icon_only(true)
.child(gpui::svg()
.size(px(16.))
.path(icons::ELLIPSIS)
.text_color(cx.colors().foreground)
.into_any_element()),
)
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, ButtonGroup, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(Variant::GROUP
.iter()
.filter_map(|v| {
let key = format!("bgroup-v-{v:?}");
Some({
ButtonGroup::new()
.variant(*v)
.separators(true)
.button(
Button::new(format!("button-group-{v:?}-1")).label("One"),
)
.button(
Button::new(format!("button-group-{v:?}-2")).label("Two"),
)
})
}))An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, ButtonGroup, Orientation, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![
ButtonGroup::new()
.variant(Variant::Secondary)
.separators(true)
.button(Button::new("button-group-l").label("Left"))
.button(Button::new("button-group-c").label("Center"))
.button(Button::new("button-group-r").label("Right"))
.into_any_element(),
ButtonGroup::new()
.variant(Variant::Secondary)
.orientation(Orientation::Vertical)
.separators(true)
.button(Button::new("button-group-top").label("Top"))
.button(Button::new("button-group-mid").label("Middle"))
.button(Button::new("button-group-bot").label("Bottom"))
.into_any_element(),
])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, ButtonGroup, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![gpui::div()
.w_full()
.child(
ButtonGroup::new()
.variant(Variant::Secondary)
.full_width(true)
.separators(true)
.button(Button::new("button-group-1").label("Cancel"))
.button(Button::new("button-group-2").label("Save draft"))
.button(Button::new("button-group-3").label("Publish")),
)
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, ButtonGroup, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![ButtonGroup::new()
.variant(Variant::Secondary)
// v3: omit the `<ButtonGroup.Separator />` child
// composition — the port's default draws no dividers.
.button(Button::new("button-group-1").label("One"))
.button(Button::new("button-group-2").label("Two"))
.button(Button::new("button-group-3").label("Three"))
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, ButtonGroup, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![ButtonGroup::new()
.variant(Variant::Secondary)
.is_disabled(true)
.separators(true)
.button(Button::new("button-group-1").label("One"))
.button(Button::new("button-group-2").label("Two"))
.into_any_element()])Anatomy
Rust types
Button Group 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 Button Group 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
| Method | Values | Description |
|---|---|---|
variant(Variant) + Button::group_defaults | 'primary' | 'secondary' | 'tertiary' | 'outline' | 'ghost' | 'danger' | 'danger-soft' | Supplies a default variant to direct Button members while preserving explicit child variants; unset members keep Button's own default. The pinned context type is ButtonProps['variant'], so all seven values inherit. |
size(Size) + Button::group_defaults | 'sm' | 'md' | 'lg' | Supplies a default size to direct Button members while preserving explicit child sizes; unset members keep Button's own default. |
orientation(Orientation) | horizontal | vertical | Chooses row or column layout and the corresponding joined-edge geometry. |
full_width(bool) + Button::group_defaults | true | false | The group root fills its parent, and each typed member resolves pinned button.tsx's finalFullWidth = fullWidth ?? Context.fullWidth: unset members inherit, an explicit child false/true overrides, and only resolved-full members take an equal stretch slot. |
// 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: 12 entries ported, 4 partial, 1 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.
Builders
| Builder | Values | Default | Description |
|---|---|---|---|
id( | string | — | Optional GPUI identity. A named group reports role=group; an unnamed one produces no AccessKit node. |
variant( | 'primary' | | — | Supplies a default variant to direct Button members while preserving explicit child variants; unset members keep Button's own default. The pinned context type is ButtonProps['variant'], so all seven values inherit. |
size( | 'sm' | | — | Supplies a default size to direct Button members while preserving explicit child sizes; unset members keep Button's own default. |
orientation( | horizontal | | Orientation:: | Chooses row or column layout and the corresponding joined-edge geometry. |
full_width( | true | | false | The group root fills its parent, and each typed member resolves pinned button.tsx's finalFullWidth = fullWidth ?? Context.fullWidth: unset members inherit, an explicit child false/true overrides, and only resolved-full members take an equal stretch slot. |
is_disabled( | true | | false | Disables direct typed members unless a child explicitly sets isDisabled=false, matching pinned context precedence. |
button(Partial | AnyElement | — | Button(Button) preserves typed direct-child inheritance and joined edges; arbitrary ParentElement children are rendered but cannot receive that context after type erasure, so they take no fullWidth stretch slot either. |
separators(Partial | true | | — | Synthesizes pinned separators through a composition flag rather than accepting browser classes. |
Not ported1 HeroUI entry not ported
| HeroUI v3 | Why it is not ported |
|---|---|
ButtonGroup.className | Browser classes are unavailable. |
Parts
| Part | Description |
|---|---|
ButtonGroup | Orientation, width, direct-child defaults, and joined-edge owner. |
ButtonGroupPartial | Optional hairline drawn by ButtonGroup::separators(bool) before each member after the first; composes one child per member, so per-member placement is not expressible. |
States
| State | Builder | Description |
|---|---|---|
Horizontal | Orientation::Horizontal | Members form a row with start/end outer corners. |
Vertical | Orientation::Vertical | Members form a column with top/bottom outer corners. |
Full width | full_width | The group fills its parent; each member that resolves fullWidth ?? Context.fullWidth shares the width equally, and an explicit child false frees its share. |
Disabled | Button::group_defaults precedence | Unset children inherit disabled; an explicit child false overrides it. |
Pressed | Button group_edge skips anim::pressed | Grouped buttons suppress the standalone press scale. |
Focus visiblePartial | Button ring_if_focused without z-10 | The focus ring is present, but GPUI has no z-index elevation equivalent for overlapping adjacent controls. |