Component 10 of 60
Checkbox Group
Groups related checkboxes under shared state.
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 checkbox_group_preview(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let value = window.use_keyed_state("checkbox-group", cx, |_, _| {
vec![SharedString::from("email")]
});
CheckboxGroup::new("notifications")
.aria_label("Notifications")
.value(value.read(cx).clone())
.item(CheckboxGroupItem::new("email", "email").label("Email notifications"))
.item(CheckboxGroupItem::new("push", "push").label("Push notifications"))
.item(
CheckboxGroupItem::new("sms", "sms")
.label("SMS notifications")
.disabled(true),
)
.on_change(move |next, _, cx| {
value.update(cx, |value, cx| {
*value = next;
cx.notify();
})
})
}
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.