gpuicn Components

Component 56 of 60

Toggle

A two-state button that can be on or off.

site/src/previews/toggle.rs

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.

Toggle, running in the browser. The second tab shows the preview's own source, which includes its layout and demo state. For the smallest working example, see Usage below. Download the whole showcase for its imports and helpers.

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.