@momoi-labs/kiso 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/kiso/AGENTS.md +50 -0
  4. package/kiso/README.md +62 -0
  5. package/kiso/docs/accessibility.md +87 -0
  6. package/kiso/docs/brand.md +95 -0
  7. package/kiso/docs/components/README.md +58 -0
  8. package/kiso/docs/components/alert.md +158 -0
  9. package/kiso/docs/components/badge.md +135 -0
  10. package/kiso/docs/components/breadcrumb.md +66 -0
  11. package/kiso/docs/components/button.md +168 -0
  12. package/kiso/docs/components/card.md +154 -0
  13. package/kiso/docs/components/checkbox.md +91 -0
  14. package/kiso/docs/components/command-palette.md +165 -0
  15. package/kiso/docs/components/drawer.md +79 -0
  16. package/kiso/docs/components/dropdown-menu.md +178 -0
  17. package/kiso/docs/components/empty-state.md +142 -0
  18. package/kiso/docs/components/form-field.md +115 -0
  19. package/kiso/docs/components/header.md +79 -0
  20. package/kiso/docs/components/helper-text.md +86 -0
  21. package/kiso/docs/components/icon-button.md +161 -0
  22. package/kiso/docs/components/input.md +99 -0
  23. package/kiso/docs/components/label.md +88 -0
  24. package/kiso/docs/components/link.md +152 -0
  25. package/kiso/docs/components/modal-dialog.md +82 -0
  26. package/kiso/docs/components/navigation.md +68 -0
  27. package/kiso/docs/components/page-header.md +70 -0
  28. package/kiso/docs/components/pagination.md +129 -0
  29. package/kiso/docs/components/popover.md +74 -0
  30. package/kiso/docs/components/search.md +147 -0
  31. package/kiso/docs/components/select.md +105 -0
  32. package/kiso/docs/components/sidebar.md +74 -0
  33. package/kiso/docs/components/skeleton.md +140 -0
  34. package/kiso/docs/components/spinner.md +125 -0
  35. package/kiso/docs/components/switch.md +92 -0
  36. package/kiso/docs/components/table.md +255 -0
  37. package/kiso/docs/components/tabs.md +69 -0
  38. package/kiso/docs/components/textarea.md +91 -0
  39. package/kiso/docs/components/toast.md +80 -0
  40. package/kiso/docs/components/tooltip.md +162 -0
  41. package/kiso/docs/components/validation-message.md +96 -0
  42. package/kiso/docs/data-interfaces.md +309 -0
  43. package/kiso/docs/evolution.md +35 -0
  44. package/kiso/docs/patterns/README.md +40 -0
  45. package/kiso/docs/patterns/application-shell.md +106 -0
  46. package/kiso/docs/patterns/command-palette.md +142 -0
  47. package/kiso/docs/patterns/confirmations.md +158 -0
  48. package/kiso/docs/patterns/crud.md +139 -0
  49. package/kiso/docs/patterns/dashboard.md +102 -0
  50. package/kiso/docs/patterns/destructive-actions.md +137 -0
  51. package/kiso/docs/patterns/developer-oriented-interfaces.md +162 -0
  52. package/kiso/docs/patterns/empty-states.md +76 -0
  53. package/kiso/docs/patterns/errors.md +93 -0
  54. package/kiso/docs/patterns/filtering.md +147 -0
  55. package/kiso/docs/patterns/keyboard-shortcuts.md +155 -0
  56. package/kiso/docs/patterns/large-data-tables.md +182 -0
  57. package/kiso/docs/patterns/list-detail.md +118 -0
  58. package/kiso/docs/patterns/loading.md +80 -0
  59. package/kiso/docs/patterns/login-authentication.md +101 -0
  60. package/kiso/docs/patterns/onboarding.md +94 -0
  61. package/kiso/docs/patterns/pagination.md +121 -0
  62. package/kiso/docs/patterns/permission-denied.md +84 -0
  63. package/kiso/docs/patterns/search.md +150 -0
  64. package/kiso/docs/patterns/settings.md +100 -0
  65. package/kiso/docs/patterns/sorting.md +121 -0
  66. package/kiso/docs/principles.md +122 -0
  67. package/kiso/docs/tokens.md +95 -0
  68. package/kiso/docs/voice-and-tone.md +154 -0
  69. package/package.json +42 -0
  70. package/tokens/build/tokens.css +143 -0
  71. package/tokens/build/tokens.d.ts +160 -0
  72. package/tokens/build/tokens.json +88 -0
  73. package/tokens/build/tokens.scss +89 -0
@@ -0,0 +1,74 @@
1
+ # Sidebar
2
+
3
+ A persistent navigation region for products with several destinations or
4
+ collapsible groups.
5
+
6
+ ## Purpose
7
+
8
+ Sidebar makes a broad product structure scannable without turning the Header
9
+ into a menu bar. It is a layout container whose items remain Links; disclosure
10
+ controls only expand or collapse sections.
11
+
12
+ ## Anatomy
13
+
14
+ ```
15
+ Sidebar
16
+ ├── label (accessible, may be visually hidden)
17
+ ├── Navigation
18
+ │ ├── Link(s)
19
+ │ └── section(s)
20
+ │ ├── disclosure Button
21
+ │ └── Link(s)
22
+ └── collapse control (optional IconButton)
23
+ ```
24
+
25
+ Surface uses `--color-surface`, divider `--color-border`, primary text
26
+ `--color-foreground`, and current items `--color-primary`. Layout uses
27
+ `--spacing-sm` between items, `--spacing-md` section gaps, and the five
28
+ property-qualified label typography tokens.
29
+
30
+ ## Variants
31
+
32
+ No visual variants. Sidebar has one navigation treatment; expanded and
33
+ collapsed are states of the same component, not separate variants.
34
+
35
+ ## Sizes
36
+
37
+ No `sm` / `md` / `lg` sizes. Width belongs to the host layout, and internal
38
+ spacing uses semantic tokens without changing Link or disclosure-control sizes.
39
+
40
+ ## States
41
+
42
+ | State | Behavior |
43
+ | --- | --- |
44
+ | default | Expanded groups and their current state are visible. |
45
+ | hover | Interactive children own hover feedback. |
46
+ | focus | Links and disclosure controls show `--color-focus`. |
47
+ | active | Current destination has `aria-current="page"`; expanded disclosures have `aria-expanded="true"`. |
48
+ | disabled | Sidebar is not disabled; omit unavailable destinations or explain them adjacent to a disabled child. |
49
+ | collapsed | Sections reduce to an explicit compact navigation; essential labels must remain available without Tooltip. |
50
+
51
+ ## Accessibility
52
+
53
+ Wrap the links in a labelled `<nav>`. Use real Buttons with `aria-expanded`
54
+ and `aria-controls` for section disclosures. Collapsing the whole Sidebar must
55
+ not strand focus in hidden content; move it to the collapse control. Keep DOM
56
+ and visual order aligned. `Tab` moves among controls; `Enter`/`Space` toggles a
57
+ focused disclosure; Links retain native behavior.
58
+
59
+ ## When to use
60
+
61
+ - Products with several stable top-level destinations or grouped sections.
62
+ - Dense technical tools where persistent wayfinding reduces context switching.
63
+
64
+ ## When NOT to use
65
+
66
+ - A handful of global destinations that fit in [Header](header.md).
67
+ - A hierarchical path to the current page; use [Breadcrumb](breadcrumb.md).
68
+ - Temporary controls unrelated to navigation.
69
+
70
+ ## Radix/shadcn mapping
71
+
72
+ No Radix Sidebar primitive. shadcn Sidebar is a structural reference, but Kiso
73
+ keeps native nav/Link semantics and semantic tokens. Disclosure behavior may
74
+ use Radix Collapsible / shadcn Collapsible.
@@ -0,0 +1,140 @@
1
+ # Skeleton
2
+
3
+ A layout-preserving loading placeholder. It occupies the space of the
4
+ content that will arrive, so the page does not jump.
5
+
6
+ ## Purpose
7
+
8
+ Skeleton is for *known* structure and *unknown* data: the table will have
9
+ rows, the Card will have a title and two lines, the form will have four
10
+ fields. Draw that shape now; swap in the real content when it exists.
11
+
12
+ User story #22: [Spinner](spinner.md) is indeterminate; Skeleton preserves
13
+ layout.
14
+
15
+ Table/DataTable (data slice) composes Skeleton for loading rows, together
16
+ with [EmptyState](empty-state.md) (no data) and [Pagination](pagination.md)
17
+ (known data). This doc defines the
18
+ placeholder; it does not define the table.
19
+
20
+ ## Anatomy
21
+
22
+ ```
23
+ Skeleton (one placeholder)
24
+ └── shape (text line | block | circle)
25
+
26
+ Region (the thing that is loading)
27
+ ├── aria-busy="true"
28
+ ├── accessible name / live status ("Loading replicas")
29
+ └── one or more Skeletons matching the eventual layout
30
+ ```
31
+
32
+ - **Shape.** Geometric stand-in. No letters, no shimmering brand mark, no
33
+ fake data.
34
+ - **Region.** The parent is the accessible loading state. Individual
35
+ Skeletons are decorative.
36
+
37
+ ## Variants
38
+
39
+ Variants are shapes, not colors.
40
+
41
+ | Variant | Shape tokens | Stands in for |
42
+ | --- | --- | --- |
43
+ | `text` | Height of the target role's `--type-role-body-line-height`, `--type-role-label-line-height`, or `--type-role-heading-line-height`; width a fraction of the column; radius `--radius-sm` | Titles, labels, table cells, descriptions. |
44
+ | `block` | Radius `--radius-md` (or `--radius-lg` when replacing a Card) | Cards, images, chart frames, table bodies as a whole. |
45
+ | `circle` | Radius `--radius-full`, equal width and height | Avatars and circular IconButtons. |
46
+
47
+ Fill is `--color-border` on the surrounding `--color-surface` or
48
+ `--color-background` (whichever the real content sits on). Do not use
49
+ `--color-primary` or status colors — loading is not a status.
50
+
51
+ Pulse (if any) interpolates opacity between the fill and `--color-surface`
52
+ using `--motion-duration-normal` and `--motion-easing-standard`.
53
+
54
+ **Reduced motion:** no pulse. Static `--color-border` placeholders. The
55
+ tokens already zero out `--motion-duration-fast` and
56
+ `--motion-duration-normal`; do not add a second
57
+ animation that ignores them.
58
+
59
+ ## Sizes
60
+
61
+ Skeleton has no independent size scale. It **matches the content it
62
+ replaces**:
63
+
64
+ | Replacing | Skeleton |
65
+ | --- | --- |
66
+ | Body line | `text` at `--type-role-body-line-height`, width ~ ⅔ of the column |
67
+ | Label / heading | `text` at that role's height, shorter width |
68
+ | Button | `block` with the Button size's padding box |
69
+ | Table row | A row of `text` cells aligned to column widths |
70
+ | Card | Header `text` + Content `block` or stacked `text` |
71
+
72
+ Approximate widths are layout choices, not new tokens. Do not specify
73
+ placeholder geometry in raw pixels; size against type roles and spacing.
74
+
75
+ ## States
76
+
77
+ | State | Behavior |
78
+ | --- | --- |
79
+ | default | Visible placeholder while `aria-busy` is true. |
80
+ | hover / focus / active | N/A. Skeletons are not controls and must not be in the tab order. |
81
+ | disabled | N/A. |
82
+ | loading | Skeleton *is* the loading presentation. |
83
+ | error | Remove Skeletons; show [Alert](alert.md) (and optionally a retry Button). Do not leave placeholders up after failure. |
84
+
85
+ When data arrives, replace the Skeleton region with the real content in
86
+ one swap. Do not leave a Skeleton sitting next to the loaded widget.
87
+
88
+ ## Accessibility
89
+
90
+ - Each Skeleton graphic is `aria-hidden="true"`.
91
+ - The **region** communicates loading: `aria-busy="true"` and a polite
92
+ status (`role="status"` or `aria-live="polite"`) with a short label
93
+ ("Loading queries"). Announce once, not once per row.
94
+ - Do not put focus inside the placeholder. Focus stays on the control that
95
+ triggered the load, or on the page heading for initial page load.
96
+ - When loading finishes, set `aria-busy="false"` and let the status
97
+ announce completion only if the person would otherwise miss it (e.g.
98
+ the region was empty). Prefer the content itself over a "Done loading"
99
+ toast.
100
+ - Skeleton is not a progressbar.
101
+
102
+ ### Keyboard
103
+
104
+ No keymap. Placeholders are skipped.
105
+
106
+ ## When to use
107
+
108
+ - First load of a list, table, Card, or form whose structure is known.
109
+ - Pagination and refetch of Table/DataTable rows (compose Skeleton rows
110
+ in place of data rows; keep table chrome).
111
+ - Replacing a known block inside a Card while the rest of the page stays
112
+ put.
113
+
114
+ ## When NOT to use
115
+
116
+ - **Indeterminate, structure-unknown waits** (submit, connect, a one-off
117
+ action). [Spinner](spinner.md), usually inside the Button.
118
+ - **Empty results.** That is EmptyState, not an eternal Skeleton.
119
+ - **Errors.** Alert, not a grey box.
120
+ - **Content that is already on screen.** Do not skeletonize a field the
121
+ person is editing.
122
+ - **Fake completed UI.** Never put real-looking numbers or names in a
123
+ Skeleton; placeholders stay empty shapes.
124
+
125
+ ## Radix/shadcn mapping
126
+
127
+ No Radix Skeleton primitive.
128
+
129
+ | Kiso | Reference |
130
+ | --- | --- |
131
+ | Placeholder primitive | shadcn [Skeleton](https://ui.shadcn.com/docs/components/skeleton) |
132
+ | Table rows | shadcn Skeleton "Table" example — restyle to `--color-border` / `--color-surface` and type-role heights |
133
+ | Card / text / avatar | shadcn Card / Text / Avatar examples as shape references only |
134
+
135
+ Do not copy shadcn examples that set raw pixel widths. Map height to type
136
+ roles and padding to one of `--spacing-xs`, `--spacing-sm`, `--spacing-md`,
137
+ `--spacing-lg`, or `--spacing-xl`, matching the content being replaced.
138
+
139
+ Compose with Table/DataTable in the data slice: loading → Skeleton rows;
140
+ empty → EmptyState; error → Alert; populated → rows + Pagination.
@@ -0,0 +1,125 @@
1
+ # Spinner
2
+
3
+ An indeterminate loading indicator. It tells the person that work is
4
+ happening when the shape or duration of the result is not yet known.
5
+
6
+ ## Purpose
7
+
8
+ Spinner is for *unknown* progress: a request has started, there is nothing
9
+ honest to draw yet, and the person must wait.
10
+
11
+ User story #22: Spinner is indeterminate; [Skeleton](skeleton.md) is
12
+ layout-preserving. If you already know the layout of what will appear
13
+ (table rows, a Card body, a form), use Skeleton. If you do not (a
14
+ submission, a reconnect, a short inline wait inside a Button), use Spinner.
15
+
16
+ ## Anatomy
17
+
18
+ ```
19
+ Spinner
20
+ ├── graphic (required)
21
+ └── label (required in accessible name; visible when the wait is the
22
+ primary thing on screen)
23
+ ```
24
+
25
+ - **Graphic.** A circular (or otherwise looping) indicator. Color
26
+ `--color-primary` on a track of `--color-border`. It is decorative once
27
+ the name exists: the accessible name carries "Loading".
28
+ - **Label.** Visible text when Spinner is the main content of a region:
29
+ "Loading queries…", "Connecting to database…". Follow
30
+ [voice-and-tone](../voice-and-tone.md): state what is happening, no
31
+ chatter. When Spinner is inside a Button, the Button label is the name
32
+ ("Saving…") and the graphic has `aria-hidden="true"`.
33
+
34
+ ## Variants
35
+
36
+ One visual variant. Meaning comes from placement, not color.
37
+
38
+ | Placement | Label |
39
+ | --- | --- |
40
+ | Inside [Button](button.md) / [IconButton](icon-button.md) | Button keeps a loading verb; graphic is decorative. |
41
+ | Inline next to a value or [Badge](badge.md) | Visible short word ("syncing") or `aria-label` on the Spinner. |
42
+ | Region / page | Visible label required. Centered in the region, not over unrelated chrome. |
43
+
44
+ Do not recolor Spinner to `--color-danger` to mean failed — a failed wait
45
+ is an [Alert](alert.md). Do not use `--color-success` to mean done — hide
46
+ the Spinner.
47
+
48
+ ## Sizes
49
+
50
+ | Size | Scale | Use |
51
+ | --- | --- | --- |
52
+ | `sm` | Matches `--type-role-metadata-font-size` | Inside Button `sm`, Badge, inline meta. |
53
+ | `md` (default) | Matches `--type-role-label-font-size` | Inside Button `md`, IconButton `md`, inline waits. |
54
+ | `lg` | Matches `--type-role-body-font-size` | Region-level wait when layout is unknown. |
55
+
56
+ ## States
57
+
58
+ | State | Behavior |
59
+ | --- | --- |
60
+ | default | Animating (unless reduced motion). |
61
+ | hover / focus / active | N/A. Spinner is not a control. |
62
+ | disabled | N/A. |
63
+ | loading | Spinner *is* the loading state of something else. It has no nested loading state. |
64
+ | error | Replace Spinner with [Alert](alert.md). Do not freeze a Spinner as an error cue. |
65
+
66
+ Motion uses `--motion-duration-normal` and `--motion-easing-standard` for
67
+ fade-in. The spin loop itself is continuous.
68
+
69
+ **Reduced motion:** the generated tokens set `--motion-duration-fast` and
70
+ `--motion-duration-normal` to `0s`
71
+ under `prefers-reduced-motion: reduce`. A spinning loop with duration zero
72
+ is invisible or broken. In that case show a **static** indicator (the same
73
+ graphic, not rotating) plus the label. Never communicate loading only with
74
+ motion.
75
+
76
+ ## Accessibility
77
+
78
+ - When the Spinner is the only loading cue in a region: `role="status"`,
79
+ `aria-live="polite"`, `aria-label` (or visible text) that says what is
80
+ loading. `aria-busy="true"` on the region that is waiting.
81
+ - When composed inside a busy Button: the Button has `aria-busy="true"`;
82
+ the graphic is `aria-hidden="true"` so "Loading" is not announced twice.
83
+ - Do not use `role="progressbar"` unless you have a real value. Spinner is
84
+ indeterminate; a progress bar with no `aria-valuenow` is the wrong
85
+ promise.
86
+ - Color is not the only cue; the label (visible or `aria-label`) is.
87
+
88
+ ### Keyboard
89
+
90
+ No keymap. Focus stays on the control that started the wait (the Button),
91
+ or in the region if the whole view is replacing. Do not move focus to the
92
+ Spinner graphic.
93
+
94
+ ## When to use
95
+
96
+ - A short, indeterminate wait: submitting a form, retrying a connection,
97
+ refreshing one value.
98
+ - Inside a Button or IconButton loading state (user story #12).
99
+ - A region whose forthcoming layout is genuinely unknown (first paint of a
100
+ custom view with no stable structure).
101
+
102
+ ## When NOT to use
103
+
104
+ - **The layout is known.** [Skeleton](skeleton.md) — especially table rows,
105
+ Card bodies, and form stacks. Table/DataTable (data slice) composes
106
+ Skeleton for loading rows, not a Spinner over an empty table.
107
+ - **Progress is measurable** (percent, step n of m). Use a determinate
108
+ progress pattern, not Spinner.
109
+ - **The wait is done or failed.** Hide Spinner; show content or Alert.
110
+ - **Decoration.** A never-ending Spinner next to idle content is a lie.
111
+ - **Blocking the whole app by default.** A page-level Spinner is a last
112
+ resort when nothing else can render. Prefer Skeleton of the shell.
113
+
114
+ ## Radix/shadcn mapping
115
+
116
+ No Radix Spinner primitive.
117
+
118
+ | Kiso | Reference |
119
+ | --- | --- |
120
+ | Graphic + `role="status"` + `aria-label="Loading"` | shadcn [Spinner](https://ui.shadcn.com/docs/components/spinner) |
121
+ | Inside Button | shadcn Button "Spinner" example, composed with Kiso Button loading rules |
122
+ | Size | shadcn size utilities restyled to the type-role matching sizes above, not arbitrary `size-*` pixels |
123
+
124
+ Replace shadcn's default icon color with `--color-primary`. Honor reduced
125
+ motion as specified above; do not rely on `animate-spin` alone.
@@ -0,0 +1,92 @@
1
+ # Switch
2
+
3
+ ## Purpose
4
+
5
+ Switch changes one boolean setting between on and off. The change normally
6
+ takes effect immediately, like a physical switch, rather than waiting for form
7
+ submission.
8
+
9
+ ## Anatomy
10
+
11
+ 1. **Root/track** — focusable control and on/off state owner.
12
+ 2. **Thumb** — moves to make state visible; motion is supportive, not the only
13
+ signal.
14
+ 3. **Label** — names the setting, not the action (for example, “Query logging,”
15
+ not “Enable query logging”).
16
+ 4. **Description (optional)** — explains effect or scope.
17
+
18
+ ## Variants
19
+
20
+ - **Off / on** — the two stable setting values.
21
+ - **With description** — for settings whose effect is not obvious from Label.
22
+ - **Controlled** — application owns state, including persistence and rollback.
23
+ - **Uncontrolled** — primitive owns initial state; use only when persistence and
24
+ external synchronization are unnecessary.
25
+
26
+ Switch is one immediate boolean setting. Checkbox is an independent form choice
27
+ or member of a multi-select list; Select chooses one value from many.
28
+
29
+ ## Sizes
30
+
31
+ - **Small** — dense settings tables, with a full-sized Label target.
32
+ - **Medium** — default.
33
+
34
+ Track, thumb, and gap scale as a unit with semantic tokens. Do not encode state
35
+ only through thumb position or color; the accessible state remains required.
36
+
37
+ ## States
38
+
39
+ | State | Behavior |
40
+ | --- | --- |
41
+ | Default | Clearly presents off or on state. |
42
+ | Hover | Track/label pair receives quiet interactive emphasis. |
43
+ | Focus | Root has a visible `--color-focus` ring. |
44
+ | Active | Brief pressed feedback; thumb movement uses `--motion-duration-fast` and `--motion-easing-standard`. |
45
+ | Disabled | Cannot toggle and uses `--color-disabled`. |
46
+ | Loading | Retains the intended or confirmed state, shows pending status, and prevents duplicate changes only when necessary. |
47
+ | Error | Persistence failure is explained with `--color-danger` feedback and a recovery action; do not leave the displayed state ambiguous. |
48
+
49
+ Loading differs from disabled: it communicates an in-progress state change.
50
+ If the request fails, either revert to the confirmed state or retain the choice
51
+ with an explicit retry path.
52
+
53
+ ## Accessibility
54
+
55
+ - Radix supplies `role="switch"` and `aria-checked`; preserve these semantics.
56
+ - Associate Label using matching `for`/`id` or a single, unambiguous
57
+ `aria-labelledby` relationship.
58
+ - Expose disabled and busy status. Link description or failure feedback via
59
+ `aria-describedby`.
60
+ - Keyboard: `Tab` focuses; `Space` toggles. `Enter` may toggle where the
61
+ primitive/browser contract supports it consistently. Do not require drag.
62
+ - State must be announced as on/off and remain discernible without color or
63
+ animation. Honor reduced-motion tokens.
64
+
65
+ ## When to use
66
+
67
+ - For a single preference that applies immediately.
68
+ - In settings surfaces where current on/off state must remain visible.
69
+ - When toggling does not require a separate Save action.
70
+
71
+ ## When NOT to use
72
+
73
+ - Do not use for a value submitted only with the rest of a form; use Checkbox.
74
+ - Do not use for multiple related selections; use a Checkbox group.
75
+ - Do not use for choosing among three or more values; use Select.
76
+ - Do not use when changing state has a destructive or complex consequence that
77
+ needs explicit confirmation; use a clearly named action flow.
78
+
79
+ ## Tokens
80
+
81
+ Use `--color-border`, `--color-surface`, `--color-foreground`,
82
+ `--color-primary`, `--color-focus`, `--color-disabled`, and `--color-danger`,
83
+ plus `--spacing-sm` label gap, `--radius-full`, the five property-qualified
84
+ label typography tokens, `--motion-duration-fast`, and
85
+ `--motion-easing-standard`.
86
+
87
+ ## Radix/shadcn mapping
88
+
89
+ Maps to [Radix Switch](https://www.radix-ui.com/primitives/docs/components/switch)
90
+ and [shadcn/ui Switch](https://ui.shadcn.com/docs/components/switch). Keep
91
+ Radix's switch role, checked state, form behavior, and keyboard interaction as
92
+ the behavioral reference.