Component 56 of 60
Toggle
A two-state button that can be on or off.
Starting the preview. GPUI compiles to WebAssembly, so the first load takes a few seconds. This browser has neither WebGPU nor WebGL 2, so the live preview cannot run. The code on this page still works. The preview did not start.
fn toggle_preview(&self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity().downgrade();
let icon_color = UiTheme::read(cx).colors.foreground;
div()
.flex()
.items_center()
.gap(px(8.0))
.child(
Toggle::new("preview.toggle")
.pressed(self.pressed)
.variant(ToggleVariant::Outline)
.aria_label("Bold")
.on_change(move |pressed, _, _, cx| {
view.update(cx, |this, cx| {
this.pressed = pressed;
cx.notify();
})
.ok();
})
.child(
lucide(LucideIcon::Bold)
.size(px(16.0))
.text_color(icon_color),
),
)
.child(
Toggle::new("preview.toggle.pressed")
.pressed(true)
.aria_label("Italic")
.child(
lucide(LucideIcon::Italic)
.size(px(16.0))
.text_color(icon_color),
),
)
.child(
Toggle::new("preview.toggle.disabled")
.disabled(true)
.aria_label("Underline")
.child(
lucide(LucideIcon::Underline)
.size(px(16.0))
.text_color(icon_color),
),
)
}
Installation
After the one-time setup, run the command from your app root. The CLI
writes the modules and keeps src/ui/mod.rs in step.
gpuicn add toggle
If you prefer to copy the files by hand, declare them yourself:
// src/ui/mod.rs
pub mod theme;
pub mod toggle;
// src/main.rs
mod ui;
Usage
Put this in a view module and return example(pressed) from its render method.
use crate::ui::toggle::*;
use gpui_kit::{IntoElement, ParentElement};
fn example(pressed: bool) -> impl IntoElement {
Toggle::new("bold")
.aria_label("Bold")
.pressed(pressed)
.child("B")
.on_change(|pressed, _, _, _| println!("Bold: {pressed}"))
}
API reference
pub fn new(id: impl Into<ElementId>) -> Self
Creates a `Toggle` with a stable caller-owned ID.
pub fn pressed(mut self, pressed: bool) -> Self
Sets the caller-owned pressed state.
pub fn disabled(mut self, disabled: bool) -> Self
Disables interaction and applies the disabled appearance.
pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self
Sets the accessible name of the control.
pub fn variant(mut self, variant: ToggleVariant) -> Self
Selects the Nova surface style.
pub fn size(mut self, size: ToggleSize) -> Self
Selects the Nova control dimensions.
pub fn on_change(
mut self,
handler: impl Fn(bool, &ClickEvent, &mut Window, &mut App) + 'static,
) -> Self
Reports a requested value change; retain the next value in the owning view.
Installed source, 166 lines
//! Nova-styled controlled Toggle using GPUI Kit.
use std::rc::Rc;
use gpui_kit::base::Toggle as BaseToggle;
use gpui_kit::{
AnyElement, App, ClickEvent, ElementId, InteractiveElement as _, IntoElement, ParentElement,
RenderOnce, SharedString, Styled, Window, prelude::FluentBuilder as _,
};
use super::theme::{
UiTheme,
primitives::{control, focus},
};
type ChangeHandler = Rc<dyn Fn(bool, &ClickEvent, &mut Window, &mut App) + 'static>;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
/// Nova toggle surface styles.
pub enum ToggleVariant {
#[default]
/// The default Nova presentation.
Default,
/// Draw a border around the toggle.
Outline,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
/// Nova toggle dimensions.
pub enum ToggleSize {
/// Compact dimensions.
Sm,
#[default]
/// The default Nova presentation.
Default,
/// Large dimensions.
Lg,
}
#[derive(IntoElement)]
/// A controlled button that represents a pressed state.
pub struct Toggle {
style: gpui_kit::StyleRefinement,
id: ElementId,
pressed: bool,
disabled: bool,
aria_label: Option<SharedString>,
variant: ToggleVariant,
size: ToggleSize,
children: Vec<AnyElement>,
on_change: Option<ChangeHandler>,
}
impl Toggle {
/// Creates a `Toggle` with a stable caller-owned ID.
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
style: gpui_kit::StyleRefinement::default(),
id: id.into(),
pressed: false,
disabled: false,
aria_label: None,
variant: ToggleVariant::Default,
size: ToggleSize::Default,
children: Vec::new(),
on_change: None,
}
}
/// Sets the caller-owned pressed state.
pub fn pressed(mut self, pressed: bool) -> Self {
self.pressed = pressed;
self
}
/// Disables interaction and applies the disabled appearance.
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
/// Sets the accessible name of the control.
pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
self.aria_label = Some(label.into());
self
}
/// Selects the Nova surface style.
pub fn variant(mut self, variant: ToggleVariant) -> Self {
self.variant = variant;
self
}
/// Selects the Nova control dimensions.
pub fn size(mut self, size: ToggleSize) -> Self {
self.size = size;
self
}
/// Reports a requested value change; retain the next value in the owning view.
pub fn on_change(
mut self,
handler: impl Fn(bool, &ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl ParentElement for Toggle {
fn extend(&mut self, children: impl IntoIterator<Item = AnyElement>) {
self.children.extend(children);
}
}
impl RenderOnce for Toggle {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = UiTheme::read(cx).clone();
let spacing = theme.spacing.unit;
let variant = self.variant;
let control = theme.control();
let (height, radius) = match self.size {
ToggleSize::Sm => (28., theme.radius.md),
ToggleSize::Default => (32., theme.radius.lg),
ToggleSize::Lg => (36., theme.radius.lg),
};
let pressed = self.pressed;
let disabled = self.disabled;
let base = BaseToggle::new(self.id).pressed(pressed).disabled(disabled);
let base = base
.flex()
.items_center()
.justify_center()
.gap(spacing * 1_f32)
.min_w(spacing * (height / 4.))
.h(spacing * (height / 4.))
.px(spacing * 2.5_f32)
.rounded(radius)
.map(|base| {
control::toggle(
base,
&theme,
pressed,
disabled,
variant == ToggleVariant::Outline,
)
})
.focus_visible({
let theme = theme.clone();
move |style| focus::ring(style, &theme)
})
.when(!disabled, |base| {
base.hover(move |style| style.bg(control.hover))
});
let mut toggle = super::theme::apply_style(base, &self.style).children(self.children);
if let Some(label) = self.aria_label {
toggle = toggle.accessibility_label(label);
}
if let Some(handler) = self.on_change {
toggle = toggle.on_change(move |pressed, details, window, cx| {
handler(pressed, details, window, cx)
});
}
toggle
}
}
impl gpui_kit::Styled for Toggle {
fn style(&mut self) -> &mut gpui_kit::StyleRefinement {
&mut self.style
}
}
Platform notes
Visual reference: shadcn/ui 4.19.0, Neutral Nova. Implementation:
crates/ui/registry/toggle/toggle.rs, using GPUI Kit 0.6.4.
The application passes pressed state and handles on_change. Kit supplies the toggle primitive; gpuicn adds Nova default/outline variants and focus and disabled styles.
The Rust source and Usage example define the supported API. Browser DOM/CSS behavior, exact exit animations and cross-platform screen-reader parity are not implied by the native component. See migration qualification for the checks completed on this revision.