Skip to main content

dioxus_tw_components/components/
checkbox.rs

1use crate::components::icon::*;
2use dioxus::prelude::*;
3
4#[derive(Clone, PartialEq, Props)]
5pub struct CheckboxProps {
6    #[props(extends = button, extends = GlobalAttributes)]
7    attributes: Vec<Attribute>,
8
9    /// Form name. Empty = the checkbox does not participate in form submission.
10    #[props(optional, into, default = String::new())]
11    name: String,
12
13    /// Value submitted when checked.
14    #[props(optional, into, default = String::from("on"))]
15    value: String,
16
17    #[props(optional)]
18    default_checked: bool,
19
20    #[props(optional)]
21    checked: Signal<bool>,
22
23    /// Return value determines if the event should stop propagation (false by default).
24    #[props(optional)]
25    onchange: Callback<bool, bool>,
26}
27
28#[component]
29pub fn Checkbox(mut props: CheckboxProps) -> Element {
30    let default_classes = "checkbox";
31    crate::setup_class_attribute(&mut props.attributes, default_classes);
32
33    let mut checked = use_signal(|| props.default_checked);
34    let mut prev_default = use_signal(|| props.default_checked);
35
36    // Re-sync only when the prop itself changes; comparing against `checked` would clobber user clicks.
37    if *prev_default.peek() != props.default_checked {
38        *prev_default.write() = props.default_checked;
39        *checked.write() = props.default_checked;
40    }
41
42    let id = crate::use_unique_id();
43
44    // Styled button is the interactive surface; hidden input carries name/checked for form submission and a11y.
45    rsx! {
46        button {
47            type: "button",
48            role: "checkbox",
49            "data-checked": if *checked.read() { "checked" } else { "unchecked" },
50            onclick: move |event| {
51                let new_checked = !checked();
52                checked.set(new_checked);
53                props.checked.set(new_checked);
54                if props.onchange.call(new_checked) {
55                    event.stop_propagation();
56                }
57            },
58
59            // Aria says only spacebar can change state of checkboxes.
60            onkeydown: move |e| {
61                if e.key() == Key::Enter {
62                    e.prevent_default();
63                }
64            },
65            ..props.attributes,
66            span { class: "checkbox-indicator",
67                if *checked.read() {
68                    Icon {
69                        icon: Icons::Check
70                    }
71                }
72            }
73        }
74        input {
75            id,
76            type: "checkbox",
77            name: props.name,
78            value: props.value,
79            checked: *checked.read(),
80            aria_hidden: "true",
81            tabindex: "-1",
82            position: "absolute",
83            pointer_events: "none",
84            opacity: "0",
85            margin: "0",
86            transform: "translateX(-100%)",
87        }
88    }
89}