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

Controls

Switch

Toggle a single setting on or off.

Rust
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.

Usage

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>.

Rust
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(),
    ])

Label Size

`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.

Rust
use herogpui::prelude::Switch;
use herogpui::gpui::px;

Switch::new("switch-label-size")
    .default_selected(true)
    .text_size(px(16.))
    .label("Larger label")

Sizes

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

Rust
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()
        }))

With Icons

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

Rust
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(),
    ])

Without Label

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

Rust
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(),
    ])

With Description

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

Rust
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()])

Default Selected

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

Rust
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()])

Controlled

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, 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).

Label Position

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

Rust
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(),
    ])

Group

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::{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()])

Group Horizontal

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

Rust
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()])

Form Integration

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, 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).

Render Props

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::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()])

Disabled

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

Rust
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.

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

SwitchSwitchGroup

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

Switch styling
MethodValuesDescription
size(Size)Size::Sm | Size::Md | Size::LgTrack and thumb size.
orientation(Orientation)Orientation::Horizontal | Orientation::VerticalDirection of the items container.
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: 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

Switch builders
BuilderValuesDefaultDescription
size(Size)Size::Sm | Size::Md | Size::LgSize::MdTrack and thumb size.
is_selected(bool)true | falsefalseCurrent selected state (controlled).
default_selected(bool)true | falsefalseInitial selected state (uncontrolled).
is_disabled(bool)true | falsefalseDims the whole field and removes it from interaction and form data.
is_invalid(bool)true | falsefalseMarks the field invalid and blocks native form submission.
is_read_only(bool)true | falsefalseKeeps the field focusable while preventing changes.
is_required(bool)true | falsefalseRequires selection under native form validation.
validate(callback)Fn(value: bool) -> ValidationError | true | null | undefined—Computes field validity from the current selection.
validation_behavior(ValidationBehavior)ValidationBehavior::Native | ValidationBehavior::AriaValidationBehavior::NativeChooses blocking native validation or non-blocking ARIA-style reporting.
name(value) + form_field()string—Submitted form field name.
value(value) + form_field()string'on'Submitted value when selected.
on_change(callback)Fn(isSelected: bool) -> ()—Reports the next selection state.
on_press(callback)PartialFn(event: ClickEvent) -> ()—Reports the next boolean.
label(element), thumb_icons(off, on), content(render)PartialAnyElement | Fn(values: SwitchRenderProps) -> AnyElement—Label, control content and a state-driven content closure are supported.
content(render)true | false—Current selected state supplied to custom content.
orientation(Orientation)Orientation::Horizontal | Orientation::VerticalOrientation::VerticalDirection of the items container.
child(element) / ParentElementAnyElement—Independent Switch children.
Not ported3 HeroUI entries not ported
Switch builders not ported
HeroUI v3Why it is not ported
Switch.renderOverrides the root element.
SwitchRenderProps.stateBrowser state object.
SwitchGroup.classNameAdditional browser styles.

Parts

Switch parts
PartDescription
SwitchField root carrying selection, validation, form and disabled state.
SwitchPartialClickable row containing the control and label in caller-selected order.
SwitchFocusable track and interaction surface.
SwitchState-positioned thumb with optional content.
SwitchCentered off/on icon inside the thumb.
SwitchOptional label before or after the control.
SwitchSize-aligned help text outside the clickable row.
SwitchSize-aligned validation text outside the clickable row.
SwitchGroupLayout root for independent switches.
SwitchGroupHorizontal or vertical items container.

States

Switch states
StateBuilderDescription
SelectedcheckedAccent track and end-positioned foreground thumb.
Hoveredhover styleTrack changes to the hover token and custom content sees isHovered.
Pressedactive styleTrack uses the pressed token and custom content sees isPressed.
Focus visiblering_if_focusedKeyboard focus rings the track.
Disabledis_disabledWhole field is dimmed, inert, untabbable and omitted from form data.
Read onlyis_read_onlyField stays focusable but pointer and keyboard activation do nothing.
Invalidvalidation::resolveResolved validation is rendered and blocks native form submission.
Requiredis_requiredRequired marker and native empty-selection validation.
Vertical groupSwitchGroup::orientation(Orientation::Vertical)Items stack in a column.
Horizontal groupSwitchGroup::orientation(Orientation::Horizontal)Items flow in a row.