Toggle Button
Toggle between a selected and unselected state.
use herogpui::components::toggle_button::{ToggleButton, ToggleButtonGroup};ToggleButton 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.
`radius` sets the corners; `sx` squares the top-left corner, including while pressed. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ToggleButton, ToggleButtonGroup};
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![ToggleButton::new("toggle-button")
.radius(px(8.))
.sx(|el| el.rounded_tl(px(0.)))
.label("Bold")
.into_any_element()])`hover_bg` follows the Button resting-endpoint contract: the fade runs from the resting background to the named hover fill. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, ToggleButton, ToggleButtonGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let accent = cx.colors().accent.color;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![ToggleButton::new("toggle-button-hover-bg")
.label("Hover")
.hover_bg(accent)
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Size, ToggleButton, ToggleButtonGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(Size::ALL
.iter()
.filter_map(|sz| {
let key = format!("toggle-size-{sz:?}");
Some({
ToggleButton::new(format!("toggle-button-sz-{sz:?}"))
.label(sz.label())
.size(*sz)
.default_selected(true)
})
}))An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, ToggleButton, ToggleButtonGroup, 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![
ToggleButton::new("toggle-button-1")
.is_icon_only(true)
.default_selected(true)
.child(gpui::svg()
.size(px(16.))
.path(icons::EYE)
.text_color(cx.colors().foreground)
.into_any_element())
.into_any_element(),
ToggleButton::new("toggle-button-2")
.is_icon_only(true)
.child(gpui::svg()
.size(px(16.))
.path(icons::COPY)
.text_color(cx.colors().foreground)
.into_any_element())
.into_any_element(),
ToggleButton::new("toggle-button-3")
.is_icon_only(true)
.child(gpui::svg()
.size(px(16.))
.path(icons::SEARCH)
.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::{ToggleButton, ToggleButtonGroup};
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![
ToggleButton::new("toggle-button-dis-1")
.label("Off")
.is_disabled(true)
.into_any_element(),
ToggleButton::new("toggle-button-dis-2")
.label("On")
.is_selected(true)
.is_disabled(true)
.into_any_element(),
])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, ToggleButton, ToggleButtonGroup, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
let liked = self.toggle_like;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ToggleButton::new("toggle-button-like")
.label(if liked { "Liked" } else { "Like" })
.is_selected(liked)
.child(gpui::svg()
.size(px(16.))
.path(if liked {
icons::HEART_FILL
} else {
icons::HEART
})
.text_color(cx.colors().foreground)
.into_any_element())
.on_change(cx.listener(|this, v: &bool, _, cx| {
this.toggle_like = *v;
cx.notify();
}))
.into_any_element(),
para(
if liked {
"Status: selected"
} else {
"Status: not selected"
},
cx,
),
])Also calls para (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{SelectionMode, ToggleButton, ToggleButtonGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
let single = self.toggle_single.clone();
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![ToggleButtonGroup::new("toggle-button-single")
.selection_mode(SelectionMode::Single)
.separators(true)
.selected_keys(single.into_iter().collect::<Vec<_>>())
.child_toggle(ToggleButton::new("toggle-button-left").key("left").label("Left"))
.child_toggle(
ToggleButton::new("toggle-button-center")
.key("center")
.label("Center"),
)
.child_toggle(ToggleButton::new("toggle-button-right").key("right").label("Right"))
.on_selection_change(cx.listener(|this, keys: &[SharedString], _, cx| {
this.toggle_single = keys.first().cloned();
cx.notify();
}))
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{SelectionMode, ToggleButton, ToggleButtonGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
let multiple = self.toggle_multiple.clone();
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![ToggleButtonGroup::new("toggle-button-multiple")
.selection_mode(SelectionMode::Multiple)
.separators(true)
.selected_keys(multiple.iter().cloned().collect::<Vec<_>>())
.child_toggle(ToggleButton::new("toggle-button-bold").key("bold").label("Bold"))
.child_toggle(
ToggleButton::new("toggle-button-italic")
.key("italic")
.label("Italic"),
)
.child_toggle(
ToggleButton::new("toggle-button-underline")
.key("underline")
.label("Underline"),
)
.on_change(cx.listener(|this, keys: &[SharedString], _, cx| {
this.toggle_multiple = keys.iter().cloned().collect();
cx.notify();
}))
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ToggleButton, ToggleButtonGroup, ToggleVariant};
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![
ToggleButton::new("toggle-button-default")
.label("Default")
.default_selected(true)
.into_any_element(),
ToggleButton::new("toggle-button-ghost")
.label("Ghost")
.variant(ToggleVariant::Ghost)
.default_selected(true)
.into_any_element(),
])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Orientation, ToggleButton, ToggleButtonGroup};
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![ToggleButtonGroup::new("toggle-button-orientation-horizontal")
.separators(true)
.child_toggle(ToggleButton::new("toggle-button-h-1").label("Day"))
.child_toggle(ToggleButton::new("toggle-button-h-2").label("Week"))
.child_toggle(ToggleButton::new("toggle-button-h-3").label("Month"))
.into_any_element(),
ToggleButtonGroup::new("toggle-button-orientation-vertical")
.orientation(Orientation::Vertical)
.separators(true)
.child_toggle(ToggleButton::new("toggle-button-1").label("Day"))
.child_toggle(ToggleButton::new("toggle-button-2").label("Week"))
.child_toggle(ToggleButton::new("toggle-button-3").label("Month"))
.into_any_element(),
])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ToggleButton, ToggleButtonGroup};
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(
ToggleButtonGroup::new("toggle-button-full-width")
.full_width(true)
.separators(true)
.child_toggle(ToggleButton::new("toggle-button-1").label("Left"))
.child_toggle(ToggleButton::new("toggle-button-2").label("Center"))
.child_toggle(ToggleButton::new("toggle-button-3").label("Right")),
)
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ToggleButton, ToggleButtonGroup};
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![ToggleButtonGroup::new("toggle-button-without-separator")
// v3: omit the `<ToggleButtonGroup.Separator />` child
// composition — the port's default draws no dividers.
.child_toggle(ToggleButton::new("toggle-button-1").label("One"))
.child_toggle(ToggleButton::new("toggle-button-2").label("Two"))
.child_toggle(ToggleButton::new("toggle-button-3").label("Three"))
.into_any_element()])Single: exactly one member stays selected. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, SelectionMode, ToggleButton, ToggleButtonGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ToggleButtonGroup::new("toggle-button-selection-single")
.selection_mode(SelectionMode::Single)
.separators(true)
.child_toggle(ToggleButton::new("toggle-button-1").key("a").label("A"))
.child_toggle(ToggleButton::new("toggle-button-2").key("b").label("B"))
.child_toggle(ToggleButton::new("toggle-button-3").key("c").label("C"))
.into_any_element(),
para("Multiple: any number of members can be selected.", cx),
ToggleButtonGroup::new("toggle-button-selection-multiple")
.selection_mode(SelectionMode::Multiple)
.separators(true)
.child_toggle(ToggleButton::new("toggle-button-m-1").key("a").label("A"))
.child_toggle(ToggleButton::new("toggle-button-m-2").key("b").label("B"))
.child_toggle(ToggleButton::new("toggle-button-m-3").key("c").label("C"))
.into_any_element(),
])Also calls para (included when you copy).
Uncontrolled: `defaultSelectedKeys` seeds the group's own selection, and the group keeps ownership from there — clicking a member still toggles it. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, SelectionMode, ToggleButton, ToggleButtonGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ToggleButtonGroup::new("toggle-button-default-single")
.selection_mode(SelectionMode::Single)
.separators(true)
.default_selected_keys(["center"])
.child_toggle(
ToggleButton::new("toggle-button-left").key("left").label("Left")
)
.child_toggle(
ToggleButton::new("toggle-button-center")
.key("center")
.label("Center"),
)
.child_toggle(
ToggleButton::new("toggle-button-right")
.key("right")
.label("Right"),
)
.into_any_element(),
para("Multiple: any number of members can be selected.", cx),
ToggleButtonGroup::new("toggle-button-default-multiple")
.selection_mode(SelectionMode::Multiple)
.separators(true)
.default_selected_keys(["bold", "underline"])
.child_toggle(
ToggleButton::new("toggle-button-m-bold").key("bold").label("Bold")
)
.child_toggle(
ToggleButton::new("toggle-button-m-italic")
.key("italic")
.label("Italic"),
)
.child_toggle(
ToggleButton::new("toggle-button-m-underline")
.key("underline")
.label("Underline"),
)
.into_any_element(),
])Also calls para (included when you copy).
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Orientation, ToggleButton, ToggleButtonGroup};
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![
ToggleButtonGroup::new("toggle-button-vertical")
.orientation(Orientation::Vertical)
.separators(true)
.child_toggle(ToggleButton::new("toggle-button-1").label("Top"))
.child_toggle(ToggleButton::new("toggle-button-2").label("Bottom"))
.into_any_element(),
])`is_detached` separates the buttons with gaps instead of connecting them; the attached icon group above the detached one shows the default for contrast. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, ToggleButton, ToggleButtonGroup, icons};
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![
ToggleButtonGroup::new("toggle-button-attached")
.separators(true)
.child_toggle(
ToggleButton::new("toggle-button-1")
.is_icon_only(true)
.child(gpui::svg()
.size(px(16.))
.path(icons::HEART)
.text_color(cx.colors().foreground)
.into_any_element()),
)
.child_toggle(
ToggleButton::new("toggle-button-2")
.is_icon_only(true)
.child(gpui::svg()
.size(px(16.))
.path(icons::GEAR)
.text_color(cx.colors().foreground)
.into_any_element()),
)
.child_toggle(
ToggleButton::new("toggle-button-3")
.is_icon_only(true)
.child(gpui::svg()
.size(px(16.))
.path(icons::MAIL)
.text_color(cx.colors().foreground)
.into_any_element()),
)
.into_any_element(),
ToggleButtonGroup::new("toggle-button-detached")
.is_detached(true)
.child_toggle(
ToggleButton::new("toggle-button-1")
.is_icon_only(true)
.child(gpui::svg()
.size(px(16.))
.path(icons::HEART)
.text_color(cx.colors().foreground)
.into_any_element()),
)
.child_toggle(
ToggleButton::new("toggle-button-2")
.is_icon_only(true)
.child(gpui::svg()
.size(px(16.))
.path(icons::GEAR)
.text_color(cx.colors().foreground)
.into_any_element()),
)
.child_toggle(
ToggleButton::new("toggle-button-3")
.is_icon_only(true)
.child(gpui::svg()
.size(px(16.))
.path(icons::MAIL)
.text_color(cx.colors().foreground)
.into_any_element()),
)
.into_any_element(),
])Helpers used by these examples
Plain functions over the public API, defined once here and copied with each example that calls them.
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
Toggle Button 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 Toggle Button 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(ToggleVariant) | ToggleVariant::Default | ToggleVariant::Ghost | Visual style variant |
size(Size) | Size::Sm | Size::Md | Size::Lg | Size of the toggle button |
is_icon_only(bool) | true | false | Whether the button contains only an icon |
orientation(SelectionOrientation) | SelectionOrientation::Horizontal | SelectionOrientation::Vertical | Layout direction |
full_width(bool) | true | false | Whether the group fills available width |
// 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: 34 entries ported, 11 partial, 2 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 |
|---|---|---|---|
variant( | ToggleVariant:: | ToggleVariant:: | Visual style variant |
size( | Size:: | Size:: | Size of the toggle button |
is_icon_only( | true | | false | Whether the button contains only an icon |
is_selected( | true | | — | Controlled selected state |
default_selected( | true | | false | Default selected state (uncontrolled) |
is_disabled( | true | | false | Whether the toggle button is disabled |
on_change( | Fn( | — | Handler called when selection changes |
on_press(Partial | Fn( | — | Handler called when the button is pressed |
label(Partial | AnyElement | | — | Button content or render prop |
content( | true | | — | Whether the button is currently selected |
selection_mode( | SelectionMode:: | SelectionMode:: | Whether one or multiple buttons can be selected |
selected_keys(Partial | iterable of keys | — | Controlled selection state; accepts SharedString keys only. |
default_selected_keys(Partial | iterable of keys | — | Default selected keys (uncontrolled); accepts SharedString keys only. |
on_selection_change(Partial | Fn( | — | Called when selection changes; reports SharedString keys only. |
disallow_empty_selection( | true | | false | Prevents clearing all selections |
orientation( | SelectionOrientation:: | SelectionOrientation:: | Layout direction |
is_detached( | true | | false | Whether buttons are visually separated with gaps |
full_width( | true | | false | Whether the group fills available width |
Not ported2 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
ToggleButtonGroup.className | |
ToggleButtonGroup.Separator.className |
Parts
| Part | Description |
|---|---|
ToggleButton | Focusable toggle root that owns selected, press and disabled state. |
ToggleButtonGroup | Selection and layout root; separators are explicit composition requested with separators(true). |
ToggleButtonGroupPartial | Optional explicit divider composition; uses separators(true), and custom style is unavailable. |
States
| State | Builder | Description |
|---|---|---|
Selected | is_selected | Accent-soft background and foreground show the selected state. |
HoveredPartial | is_hovered | Variant-specific hover background, including selected accent-soft-hover. |
PressedPartial | is_pressed | Pressed feedback applies the size-specific scale and pressed background. |
Focus visible | tab_stop_handle | Keyboard focus shows the status-focused ring. |
Disabled | is_disabled | Disabled toggles are dimmed, inert and removed from the tab order. |
Controlled selection | selected_keys(keys) | The caller owns the selected key set and receives selection callbacks. |
Uncontrolled selection | default_selected_keys(keys) | The group owns its selected key set after the initial seed. |
Single selection | selection_mode(SelectionMode::Single) | Selecting a member replaces the previous selection. |
Multiple selection | selection_mode(SelectionMode::Multiple) | Members can be selected independently. |
Disallow empty selection | disallow_empty_selection(bool) | The last selected member cannot be cleared in single-selection mode. |
Horizontal group | orientation(SelectionOrientation::Horizontal) | Members flow along the horizontal axis. |
Vertical group | orientation(SelectionOrientation::Vertical) | Members stack along the vertical axis. |
Full width group | full_width(true) | The group fills its parent and stretches each member. |
Attached group | group_edge | Adjacent members share an edge and keep only the outer corners rounded. |
Detached group | is_detached(true) | Members regain full radii and are separated by a 4px gap. |
Group pressed | is_grouped gates anim::pressed | Attached members do not scale and therefore cannot open gaps at the seam. |
Group focus visiblePartial | ring_if_focused(offset = false) | Removes the outer offset but GPUI has no inset box-shadow equivalent. |