Switch
Toggle a single setting on or off.
use herogpui::components::switch::Switch;Switch 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.
Content uses 14px text with 20px lines; the built-in label uses 16px text with 24px lines. 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, Switch};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let (a, b) = (self.switch_a, self.switch_b);
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
Switch::new("switch-a")
.is_selected(a)
.hover_bg(cx.colors().accent.soft_hover())
.label(gpui::div().child("Enable notifications"))
.on_change(cx.listener(|this, v: &bool, _, cx| {
this.switch_a = *v;
cx.notify();
}))
.into_any_element(),
Switch::new("switch-b")
.is_selected(b)
.label(gpui::div().child("Share usage data"))
.on_change(cx.listener(|this, v: &bool, _, cx| {
this.switch_b = *v;
cx.notify();
}))
.into_any_element(),
])`text_size` replaces the label's `text-sm`; a 12/14/16px size takes v3's 16/20/24 leading. The track and thumb keep their size. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::Switch;
use herogpui::gpui::px;
Switch::new("switch-label-size")
.default_selected(true)
.text_size(px(16.))
.label("Larger label")An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Size, Switch};
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()
.map(|s| {
let key = format!("sw-size-{s:?}");
Switch::new(format!("switch-{s:?}"))
.default_selected(true)
.size(*s)
.into_any_element()
}))An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, Switch, 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![
Switch::new("switch-icon-1")
.default_selected(true)
.thumb_icons(gpui::svg()
.size(px(16.))
.path(icons::MOON)
.text_color(cx.colors().foreground)
.into_any_element(), gpui::svg()
.size(px(16.))
.path(icons::SUN)
.text_color(cx.colors().foreground)
.into_any_element())
.label(gpui::div().child("Appearance"))
.into_any_element(),
Switch::new("switch-icon-2")
.thumb_icons(gpui::svg()
.size(px(16.))
.path(icons::EYE_OFF)
.text_color(cx.colors().foreground)
.into_any_element(), gpui::svg()
.size(px(16.))
.path(icons::EYE)
.text_color(cx.colors().foreground)
.into_any_element())
.label(gpui::div().child("Show preview"))
.into_any_element(),
])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::Switch;
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![
Switch::new("switch-nolabel-1")
.default_selected(true)
.into_any_element(),
Switch::new("switch-nolabel-2").into_any_element(),
])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::Switch;
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![Switch::new("switch-desc")
.default_selected(true)
.label(gpui::div().child("Sync across devices"))
.description("Changes are pushed to every signed-in device.")
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::Switch;
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![Switch::new("switch-default")
.default_selected(true)
.label(gpui::div().child("On by default"))
.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, Switch};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
let controlled = self.demo_flag("sw-controlled", false);
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
Switch::new("switch-controlled")
.is_selected(controlled)
.label(gpui::div().child("Notifications"))
.on_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("sw-controlled", *v);
cx.notify();
}))
.into_any_element(),
para(
if controlled {
"Status: selected"
} else {
"Status: not selected"
},
cx,
),
])Also calls para (included when you copy).
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::Switch;
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![
Switch::new("switch-lp-after")
.label(gpui::div().child("Label after"))
.into_any_element(),
Switch::new("switch-lp-before")
.label_first(true)
.label(gpui::div().child("Label before"))
.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::{Orientation, Switch, SwitchGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let wifi = self.demo_flag("sw-group-wifi", true);
let bluetooth = self.demo_flag("sw-group-bt", false);
let airplane = self.demo_flag("sw-group-air", false);
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![SwitchGroup::new()
.orientation(Orientation::Vertical)
.child(
Switch::new("switch-g-wifi")
.is_selected(wifi)
.label(gpui::div().child("Wi-Fi"))
.on_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("sw-group-wifi", *v);
cx.notify();
})),
)
.child(
Switch::new("switch-g-bt")
.is_selected(bluetooth)
.label(gpui::div().child("Bluetooth"))
.on_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("sw-group-bt", *v);
cx.notify();
})),
)
.child(
Switch::new("switch-g-air")
.is_selected(airplane)
.label(gpui::div().child("Airplane mode"))
.on_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("sw-group-air", *v);
cx.notify();
})),
)
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Orientation, Switch, SwitchGroup};
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![SwitchGroup::new()
.orientation(Orientation::Horizontal)
.child(
Switch::new("switch-gh-1")
.default_selected(true)
.label(gpui::div().child("Email")),
)
.child(Switch::new("switch-gh-2").label(gpui::div().child("SMS")))
.child(Switch::new("switch-gh-3").label(gpui::div().child("Push")))
.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, Button, Form, FormData, Switch};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
let terms = self.demo_flag("sw-form", false);
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
{
let switch = Switch::new("switch-form")
.name("terms")
// `value` is what a checked switch submits.
.value("accepted")
.is_selected(terms)
.is_required(true)
.label(gpui::div().child("Accept the terms"))
.is_invalid(!terms)
.on_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("sw-form", *v);
cx.notify();
}));
let form = Form::new()
.field(switch.form_field().expect("named switch field"))
.on_submit(cx.listener(|this, data: &FormData, _, cx| {
this.input_submitted =
format!("terms={}", data.text("terms").unwrap_or_default());
cx.notify();
}))
.on_invalid(cx.listener(|this, _: &FormData, _, cx| {
this.input_submitted =
"Accept the terms before submitting".to_owned();
cx.notify();
}));
let submit = form.submit_handler();
form.child(switch)
.child(
Button::new("switch-form-submit")
.label("Submit")
.on_press(move |_, window, cx| submit(window, cx)),
)
.into_any_element()
},
para(
if self.input_submitted.is_empty() {
"Toggle the required switch, then submit the live form field."
} else {
&self.input_submitted
},
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::Switch;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let controlled = self.demo_flag("sw-controlled", false);
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![Switch::new("switch-render")
.is_selected(controlled)
.on_change(cx.listener(|this, v: &bool, _, cx| {
this.set_demo_flag("sw-controlled", *v);
cx.notify();
}))
// v3's children-as-a-function: the closure is handed
// `isSelected`, `isHovered`, `isPressed`, `isFocused` and
// `isFocusVisible` and draws the label from them.
.content(|state| {
let mut parts = Vec::new();
if state.is_selected {
parts.push("selected");
}
if state.is_hovered {
parts.push("hovered");
}
if state.is_pressed {
parts.push("pressed");
}
if state.is_focus_visible {
parts.push("focus-visible");
}
gpui::div()
.child(if parts.is_empty() {
"idle".to_owned()
} else {
parts.join(" + ")
})
.into_any_element()
})
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::Switch;
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![
Switch::new("switch-d-off")
.is_disabled(true)
.into_any_element(),
Switch::new("switch-d-on")
.is_selected(true)
.is_disabled(true)
.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
Switch 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 Switch 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 |
|---|---|---|
size(Size) | Size::Sm | Size::Md | Size::Lg | Track and thumb size. |
orientation(Orientation) | Orientation::Horizontal | Orientation::Vertical | Direction of the items container. |
// 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: 42 entries ported, 3 partial, 3 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 |
|---|---|---|---|
size( | Size:: | Size:: | Track and thumb size. |
is_selected( | true | | false | Current selected state (controlled). |
default_selected( | true | | false | Initial selected state (uncontrolled). |
is_disabled( | true | | false | Dims the whole field and removes it from interaction and form data. |
is_invalid( | true | | false | Marks the field invalid and blocks native form submission. |
is_read_only( | true | | false | Keeps the field focusable while preventing changes. |
is_required( | true | | false | Requires selection under native form validation. |
validate( | Fn( | — | Computes field validity from the current selection. |
validation_behavior( | ValidationBehavior:: | ValidationBehavior:: | Chooses blocking native validation or non-blocking ARIA-style reporting. |
name( | string | — | Submitted form field name. |
value( | string | 'on' | Submitted value when selected. |
on_change( | Fn( | — | Reports the next selection state. |
on_press(Partial | Fn( | — | Reports the next boolean. |
label(Partial | AnyElement | | — | Label, control content and a state-driven content closure are supported. |
content( | true | | — | Current selected state supplied to custom content. |
orientation( | Orientation:: | Orientation:: | Direction of the items container. |
child( | AnyElement | — | Independent Switch children. |
Not ported3 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
Switch.render | Overrides the root element. |
SwitchRenderProps.state | Browser state object. |
SwitchGroup.className | Additional browser styles. |
Parts
| Part | Description |
|---|---|
Switch | Field root carrying selection, validation, form and disabled state. |
SwitchPartial | Clickable row containing the control and label in caller-selected order. |
Switch | Focusable track and interaction surface. |
Switch | State-positioned thumb with optional content. |
Switch | Centered off/on icon inside the thumb. |
Switch | Optional label before or after the control. |
Switch | Size-aligned help text outside the clickable row. |
Switch | Size-aligned validation text outside the clickable row. |
SwitchGroup | Layout root for independent switches. |
SwitchGroup | Horizontal or vertical items container. |
States
| State | Builder | Description |
|---|---|---|
Selected | checked | Accent track and end-positioned foreground thumb. |
Hovered | hover style | Track changes to the hover token and custom content sees isHovered. |
Pressed | active style | Track uses the pressed token and custom content sees isPressed. |
Focus visible | ring_if_focused | Keyboard focus rings the track. |
Disabled | is_disabled | Whole field is dimmed, inert, untabbable and omitted from form data. |
Read only | is_read_only | Field stays focusable but pointer and keyboard activation do nothing. |
Invalid | validation::resolve | Resolved validation is rendered and blocks native form submission. |
Required | is_required | Required marker and native empty-selection validation. |
Vertical group | SwitchGroup::orientation(Orientation::Vertical) | Items stack in a column. |
Horizontal group | SwitchGroup::orientation(Orientation::Horizontal) | Items flow in a row. |