gpuicn Components

Component 10 of 60

Checkbox Group

Groups related checkboxes under shared state.

site/src/previews/checkbox_group.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.

Checkbox Group, 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 checkbox-group

If you prefer to copy the files by hand, declare them yourself:

// src/ui/mod.rs
pub mod theme;
pub mod checkbox_group;

// src/main.rs
mod ui;

Usage

Put this in a view module and return example(window, cx) from its render method.

use crate::ui::checkbox_group::*;
use gpui_kit::{App, IntoElement, Window};

fn example(window: &mut Window, cx: &mut App) -> impl IntoElement {
    let value = window.use_keyed_state("notifications", cx, |_, _| {
        vec![gpui_kit::SharedString::from("email")]
    });
    CheckboxGroup::new("notifications")
        .aria_label("Notifications")
        .value(value.read(cx).clone())
        .item(CheckboxGroupItem::new("email", "email").label("Email"))
        .item(CheckboxGroupItem::new("push", "push").label("Push"))
        .on_change(move |next, _, cx| {
            value.update(cx, |value, cx| {
                *value = next;
                cx.notify();
            })
        })
}

API reference

pub fn new(id: impl Into<ElementId>, value: impl Into<SharedString>) -> Self

Creates a `CheckboxGroupItem` with a stable caller-owned ID.

pub fn disabled(mut self, value: bool) -> Self

Disables interaction and applies the disabled appearance.

pub fn label(mut self, label: impl Into<SharedString>) -> Self

Sets the visible label.

pub fn new(id: impl Into<ElementId>) -> Self

Creates a `CheckboxGroup` with a stable caller-owned ID.

pub fn value(mut self, values: impl IntoIterator<Item = impl Into<SharedString>>) -> Self

Sets the caller-owned value displayed by this control.

pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self

Sets the accessible name of the control.

pub fn disabled(mut self, value: bool) -> Self

Disables interaction and applies the disabled appearance.

pub fn item(mut self, item: CheckboxGroupItem) -> Self

Appends an option to the group.

pub fn on_change(
        mut self,
        handler: impl Fn(Vec<SharedString>, &mut Window, &mut App) + 'static,
    ) -> Self

Reports a requested value change; retain the next value in the owning view.

Installed source, 164 lines
//! Controlled checkbox sets with clickable labels.
use super::theme::{
    UiTheme, apply_style,
    primitives::{control, disabled, focus, text},
};
use gpui_kit::{
    App, ElementId, InteractiveElement as _, IntoElement, ParentElement as _, RenderOnce, Role,
    SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div,
    prelude::FluentBuilder as _,
};
use std::rc::Rc;

type ChangeHandler = Rc<dyn Fn(Vec<SharedString>, &mut Window, &mut App)>;
/// One checkbox option with a stable ID and selected value.
pub struct CheckboxGroupItem {
    id: ElementId,
    value: SharedString,
    label: SharedString,
    disabled: bool,
    style: StyleRefinement,
}
impl CheckboxGroupItem {
    /// Creates a `CheckboxGroupItem` with a stable caller-owned ID.
    pub fn new(id: impl Into<ElementId>, value: impl Into<SharedString>) -> Self {
        let value = value.into();
        Self {
            id: id.into(),
            label: value.clone(),
            value,
            disabled: false,
            style: Default::default(),
        }
    }
    /// Disables interaction and applies the disabled appearance.
    pub fn disabled(mut self, value: bool) -> Self {
        self.disabled = value;
        self
    }
    /// Sets the visible label.
    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
        self.label = label.into();
        self
    }
}
impl Styled for CheckboxGroupItem {
    fn style(&mut self) -> &mut StyleRefinement {
        &mut self.style
    }
}
#[derive(IntoElement)]
/// A named collection of controlled checkboxes.
pub struct CheckboxGroup {
    id: ElementId,
    value: Vec<SharedString>,
    label: Option<SharedString>,
    disabled: bool,
    items: Vec<CheckboxGroupItem>,
    on_change: Option<ChangeHandler>,
    style: StyleRefinement,
}
impl CheckboxGroup {
    /// Creates a `CheckboxGroup` with a stable caller-owned ID.
    pub fn new(id: impl Into<ElementId>) -> Self {
        Self {
            id: id.into(),
            value: Vec::new(),
            label: None,
            disabled: false,
            items: Vec::new(),
            on_change: None,
            style: Default::default(),
        }
    }
    /// Sets the caller-owned value displayed by this control.
    pub fn value(mut self, values: impl IntoIterator<Item = impl Into<SharedString>>) -> Self {
        self.value = values.into_iter().map(Into::into).collect();
        self
    }
    /// Sets the accessible name of the control.
    pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
        self.label = Some(label.into());
        self
    }
    /// Disables interaction and applies the disabled appearance.
    pub fn disabled(mut self, value: bool) -> Self {
        self.disabled = value;
        self
    }
    /// Appends an option to the group.
    pub fn item(mut self, item: CheckboxGroupItem) -> Self {
        self.items.push(item);
        self
    }
    /// Reports a requested value change; retain the next value in the owning view.
    pub fn on_change(
        mut self,
        handler: impl Fn(Vec<SharedString>, &mut Window, &mut App) + 'static,
    ) -> Self {
        self.on_change = Some(Rc::new(handler));
        self
    }
}
impl Styled for CheckboxGroup {
    fn style(&mut self) -> &mut StyleRefinement {
        &mut self.style
    }
}
impl RenderOnce for CheckboxGroup {
    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
        let theme = UiTheme::read(cx).clone();
        let colors = theme.colors;
        let children = self
            .items
            .into_iter()
            .map(|item| {
                let checked = self.value.contains(&item.value);
                let disabled = self.disabled || item.disabled;
                let focus = window
                    .use_keyed_state((item.id.clone(), "focus"), cx, |_, cx| cx.focus_handle())
                    .read(cx)
                    .clone();
                let focused = focus.is_focused(window) && !disabled;
                let change = self.on_change.clone();
                let mut next = self.value.clone();
                next.retain(|value| value != &item.value);
                if !checked {
                    next.push(item.value);
                }
                let square = focus::shown(
                    control::checkbox_box(div(), &theme, checked, false),
                    &theme,
                    focused,
                );
                let checkbox = gpui_kit::base::Checkbox::new(item.id)
                    .checked(checked)
                    .disabled(disabled)
                    .accessibility_label(item.label.clone())
                    .track_focus(&focus)
                    .flex()
                    .items_center()
                    .gap(theme.space(2.))
                    .text_color(colors.foreground)
                    .child(square)
                    .child(gpui_kit::Text::new_inaccessible(item.label))
                    .when_some(change, |checkbox, handler| {
                        checkbox
                            .on_change(move |_, _, window, cx| handler(next.clone(), window, cx))
                    });
                let checkbox = disabled::interactive(text::sm(checkbox, &theme), disabled);
                apply_style(checkbox, &item.style)
            })
            .collect::<Vec<_>>();
        let root = div()
            .id(self.id)
            .role(Role::Group)
            .flex()
            .flex_col()
            .gap(theme.space(3.))
            .when_some(self.label, |root, label| root.aria_label(label))
            .children(children);
        apply_style(root, &self.style)
    }
}

Platform notes

Visual reference: shadcn/ui 4.19.0, Neutral Nova. Implementation: crates/ui/registry/checkbox_group/checkbox_group.rs, using GPUI Kit 0.6.4.

The application owns the selected values and handles on_change. Each item has a stable ID, value, visible label and disabled flag. The group composes Kit checkbox controls under a named group.

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.