@antadesign/anta 0.3.18 → 0.3.20

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 (92) hide show
  1. package/README.md +38 -242
  2. package/dist/anta_helpers.d.ts +2 -2
  3. package/dist/components/Banner.d.ts +3 -3
  4. package/dist/components/Button.d.ts +1 -1
  5. package/dist/components/Calendar.d.ts +11 -12
  6. package/dist/components/Card.d.ts +1 -1
  7. package/dist/components/Card.js +1 -1
  8. package/dist/components/Checkbox.d.ts +8 -9
  9. package/dist/components/Dialog.d.ts +1 -1
  10. package/dist/components/Expander.d.ts +5 -6
  11. package/dist/components/Input.d.ts +6 -3
  12. package/dist/components/Input.js +3 -1
  13. package/dist/components/InputAutocomplete.d.ts +8 -8
  14. package/dist/components/InputDate.css +1 -0
  15. package/dist/components/InputDate.d.ts +1 -0
  16. package/dist/components/InputDate.js +5 -5
  17. package/dist/components/InputTime.d.ts +3 -3
  18. package/dist/components/InputTime.js +1 -1
  19. package/dist/components/Menu.d.ts +7 -3
  20. package/dist/components/Menu.js +3 -2
  21. package/dist/components/MenuItem.d.ts +1 -1
  22. package/dist/components/MenuItem.js +1 -1
  23. package/dist/components/Progress.d.ts +1 -1
  24. package/dist/components/RadioGroup.d.ts +12 -13
  25. package/dist/components/Select.d.ts +32 -38
  26. package/dist/components/Select.js +9 -13
  27. package/dist/components/SelectFaceted.d.ts +28 -33
  28. package/dist/components/SelectFaceted.js +29 -23
  29. package/dist/components/Slider.d.ts +5 -5
  30. package/dist/components/Switch.d.ts +4 -4
  31. package/dist/components/Tabs.d.ts +7 -7
  32. package/dist/components/Title.d.ts +1 -1
  33. package/dist/components/Toaster.d.ts +1 -1
  34. package/dist/components/Tooltip.d.ts +1 -1
  35. package/dist/components/select-parts.css +1 -0
  36. package/dist/elements/a-card.css +1 -1
  37. package/dist/elements/a-dialog.css +1 -1
  38. package/dist/elements/a-expander.css +1 -1
  39. package/dist/elements/a-expander.d.ts +1 -0
  40. package/dist/elements/a-expander.js +12 -2
  41. package/dist/elements/a-input.css +1 -1
  42. package/dist/elements/a-menu.js +12 -0
  43. package/dist/elements/a-progress.css +1 -1
  44. package/dist/elements/a-slider.css +1 -1
  45. package/dist/elements/a-tabs.css +1 -1
  46. package/dist/elements/a-tabs.d.ts +3 -0
  47. package/dist/elements/a-tabs.js +40 -1
  48. package/dist/elements/a-tooltip.css +1 -1
  49. package/dist/elements/a-tooltip.d.ts +1 -1
  50. package/dist/elements/a-tooltip.js +1 -1
  51. package/dist/elements/index.d.ts +1 -1
  52. package/dist/general_types.d.ts +24 -22
  53. package/dist/index.d.ts +3 -3
  54. package/dist/jsx-runtime.d.ts +9 -0
  55. package/docs/accessibility.md +34 -0
  56. package/docs/changelog.md +561 -0
  57. package/docs/colors.md +42 -0
  58. package/docs/comparison.md +28 -0
  59. package/docs/components/banner.md +267 -0
  60. package/docs/components/button.md +603 -0
  61. package/docs/components/card.md +321 -0
  62. package/docs/components/checkbox.md +296 -0
  63. package/docs/components/dialog.md +314 -0
  64. package/docs/components/expander.md +526 -0
  65. package/docs/components/icon.md +155 -0
  66. package/docs/components/input-autocomplete.md +100 -0
  67. package/docs/components/input-date.md +401 -0
  68. package/docs/components/input-time.md +174 -0
  69. package/docs/components/input.md +610 -0
  70. package/docs/components/loader.md +139 -0
  71. package/docs/components/menu.md +511 -0
  72. package/docs/components/progress.md +182 -0
  73. package/docs/components/radio.md +368 -0
  74. package/docs/components/select-faceted.md +451 -0
  75. package/docs/components/select.md +926 -0
  76. package/docs/components/slider.md +147 -0
  77. package/docs/components/switch.md +198 -0
  78. package/docs/components/tabs.md +471 -0
  79. package/docs/components/tag.md +185 -0
  80. package/docs/components/text.md +148 -0
  81. package/docs/components/title.md +146 -0
  82. package/docs/components/toaster.md +313 -0
  83. package/docs/components/tooltip.md +177 -0
  84. package/docs/credits.md +18 -0
  85. package/docs/index.md +52 -0
  86. package/docs/install-config.md +240 -0
  87. package/docs/normalization.md +102 -0
  88. package/docs/overview.md +10 -0
  89. package/docs/packages/stickers.md +187 -0
  90. package/docs/packages/table.md +153 -0
  91. package/docs/theming.md +13 -0
  92. package/package.json +10 -6
@@ -0,0 +1,321 @@
1
+ # Card
2
+
3
+ A surface container: a bordered, toned box that groups related content. The
4
+ `Card` JSX wrapper renders an `<a-card>`, which lays out an optional full-bleed
5
+ `media` region plus a `header` / body / `footer` stack. Pass `href` to turn the
6
+ whole card into a link.
7
+
8
+ A card stylizes only its own surface, never its content's typography. Pass
9
+ `header` and body (`children`) as strings and the wrapper wraps them in a
10
+ `<Title>` and a `<Text>` that follow the card's `tone` and `size`; pass your own
11
+ nodes to take over. It shares the `tone` / `priority` / `size` vocabulary with the
12
+ rest of Anta.
13
+
14
+ ## Anatomy
15
+
16
+ A card is three stacked sections — `header`, body (`children`), `footer` — plus an
17
+ optional full-bleed `media` region. Each is optional and an empty one reserves no
18
+ space. Spacing comes from one `--card-padding`: the outer inset and the body→footer
19
+ gap are a full `--card-padding`, and the header sits a half-step closer to its body
20
+ (`--card-padding / 2`). A string `header` becomes a `<Title>` and a string body a
21
+ `<Text>`; pass your own node (`<Title>`, `<Text>`, a layout) to take over. An
22
+ optional `subtitle` renders under the title as smaller text (`<Text size="small">`). There's
23
+ no dedicated actions slot: lay out header controls (a button, a tag) inside the
24
+ `header` itself.
25
+
26
+ ```tsx
27
+ <Card
28
+ media={<img src="/hero.jpg" alt="" />}
29
+ icon="book-open" {/* a string shape → an <Icon> in a circular chip */}
30
+ header="Card title"
31
+ subtitle="A short supporting subtitle"
32
+ footer={<><Button priority="secondary" label="Launch" /><Button priority="tertiary" label="Edit" /></>}
33
+ >
34
+ The body sits under the header. Every section is optional.
35
+ </Card>
36
+
37
+ {/* mediaPosition moves the media to another edge */}
38
+ <Card
39
+ media={<img src="/hero.jpg" alt="" />}
40
+ mediaPosition="left"
41
+ header="Card title"
42
+ footer={<><Button priority="secondary" label="Launch" /><Button priority="tertiary" label="Edit" /></>}
43
+ >
44
+ The same four zones, with the media bleeding to the left edge instead of the top.
45
+ </Card>
46
+ ```
47
+
48
+ ## Priority
49
+
50
+ `priority` sets the surface. `primary` (the default) is a clean sheet — `bg-1`
51
+ in light mode, `bg-4` in dark (and a toned card's cleanest tint: `bg-2-{tone}`
52
+ light, `bg-4-{tone}` dark); `secondary` is a subtle fill (`bg-2`, or the deeper
53
+ tint for a toned card); `tertiary` is a frosted panel: the `primary` surface
54
+ made semi-transparent with a backdrop blur, so it reads over busy, colored, or
55
+ image backgrounds (the blur shows where there's detail behind). All three carry
56
+ a hairline ring — `border-4` for a neutral card, or the tone's `border-4-{tone}`
57
+ when toned (stepping to `border-3` on hover).
58
+
59
+ ```tsx
60
+ <Card header="Primary (default)">…</Card> {/* primary is the default */}
61
+ <Card priority="secondary" header="Secondary">…</Card>
62
+ <Card priority="tertiary" header="Tertiary">…</Card>
63
+ ```
64
+
65
+ ## Tones
66
+
67
+ A semantic `tone` tints the surface, border, and text. The set matches the rest of
68
+ Anta: `neutral` (the default) plus `brand`, `info`, `success`, `warning`, and
69
+ `critical`. Color comes from the theme tokens, so every tone tracks light and dark
70
+ mode. A toned card is how you build a **banner** or **callout** — a filled card
71
+ with an icon and a message (add a `footer` for an action):
72
+
73
+ ```tsx
74
+ {/* A string `icon` gets the circular chip; pass a bare <Icon> node for a plain
75
+ tinted glyph, as these banners do. */}
76
+ <Card tone="info" priority="primary" icon={<Icon shape="info" />} header="Heads up">…</Card>
77
+ <Card tone="success" priority="primary" icon={<Icon shape="circle-check" />} header="All checks passed">…</Card>
78
+ <Card tone="warning" priority="primary" icon={<Icon shape="warning-triangle" />} header="Approaching quota">…</Card>
79
+ <Card tone="critical" priority="primary" icon={<Icon shape="warning-diamond" />} header="Deployment failed">…</Card>
80
+ ```
81
+
82
+ `tone` also accepts **any literal CSS color** for a one-off tint — the hue is kept
83
+ while lightness and chroma are pinned to the named-tone curve (and re-tuned for
84
+ dark mode), the same mechanism as `Button` / `Expander` / `Tag`. The surface,
85
+ border, and the `<Title>` / `<Text>` the wrapper adds all derive from the color —
86
+ `Title` and `Text` take a custom tone too, so a string `header` / body tints to
87
+ match:
88
+
89
+ ```tsx
90
+ <Card tone="#ff1493" priority="primary" header="Magenta">…</Card>
91
+ <Card tone="rebeccapurple" priority="primary" header="Purple">…</Card>
92
+ <Card tone="#0d9488" priority="primary" header="Teal">…</Card>
93
+ ```
94
+
95
+ ## Size
96
+
97
+ `size` scales the padding, the auto-wrapped body text, and the icon chip —
98
+ `small`, `medium` (the default), `large`.
99
+
100
+ ```tsx
101
+ <Card size="small" header="Small">…</Card>
102
+ <Card header="Medium (default)">…</Card> {/* medium is the default */}
103
+ <Card size="large" header="Large">…</Card>
104
+ ```
105
+
106
+ ## Round
107
+
108
+ `round` softens the corners. A `number` (px) or CSS length sets a custom radius —
109
+ the usual choice for a card (`round="0"` squares them); bare `round` goes fully
110
+ round (`999px`, clamped to the box). Omit it for the default (`--card-radius`, 8px).
111
+
112
+ ```tsx
113
+ <Card round={16} header="Custom radius">…</Card>
114
+ <Card round="0" header="Square">…</Card>
115
+ ```
116
+
117
+ ## Header controls
118
+
119
+ There is no dedicated actions slot. To place a control in the top-right of the
120
+ header, pass a layout node as `header`. For example, use a flex row containing
121
+ the title and a "more" button. That layout determines where both items appear.
122
+
123
+ ```tsx
124
+ <Card
125
+ header={
126
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
127
+ <Title level={4}>Production database</Title>
128
+ <Button size="small" priority="tertiary" icon="more" aria-label="More" />
129
+ </div>
130
+ }
131
+ >
132
+ Region us-east-1 · 4 vCPU · 16 GB
133
+ </Card>
134
+ ```
135
+
136
+ ## Footer
137
+
138
+ `footer` is a **left-aligned** row (it wraps under pressure) — the opposite of
139
+ `Dialog`'s right-aligned footer, since a card reads as left-aligned content rather
140
+ than a decision surface. Pass several buttons as a fragment.
141
+
142
+ ```tsx
143
+ <Card
144
+ header="Invite teammates"
145
+ footer={<><Button priority="primary" size="small" label="Send invites" /><Button size="small" label="Copy link" /></>}
146
+ >
147
+ <Text>They'll get access to this project and its test runs.</Text>
148
+ </Card>
149
+ ```
150
+
151
+ ## Media
152
+
153
+ Pass `media` for a full-bleed image or illustration — it escapes the card padding
154
+ and clips to the corners. `mediaPosition` places it on the `top` (the default),
155
+ `bottom`, `left`, or `right` edge.
156
+
157
+ **Top** (default)
158
+
159
+ **Left**
160
+
161
+ ```tsx
162
+ <Card media={<img src="/hero.jpg" alt="" />} header="Top media">…</Card> {/* top is the default */}
163
+ <Card media={<img src="/hero.jpg" alt="" />} mediaPosition="left" header="Left media">…</Card>
164
+ {/* mediaPosition also takes "bottom" and "right" */}
165
+ ```
166
+
167
+ ## Link card
168
+
169
+ Give a card an `href` and the whole thing becomes a link — a single focusable
170
+ anchor, keyboard-activatable, with the `hover` lift and inset focus ring. The
171
+ accessible name comes from the header (falling back to the body, then the URL), so
172
+ a screen reader announces one concise link instead of the whole card's text. A
173
+ link card is display content: keep interactive controls (buttons, nested links)
174
+ out of it — its text is non-selectable and the whole surface navigates.
175
+
176
+ ```tsx
177
+ <Card
178
+ href="/docs/ci"
179
+ header={<><Icon shape="book-open" /> CI integration guide</>}
180
+ >
181
+ Connect your CI pipeline in a few minutes.
182
+ </Card>
183
+ ```
184
+
185
+ Set `target` / `rel` for a new-tab link (`target="_blank" rel="noopener"`), or
186
+ `aria-label` to override the derived link name.
187
+
188
+ ## Selected
189
+
190
+ `selected` draws an inset ring in the tone color — for choice cards, plan pickers,
191
+ and selected list items.
192
+
193
+ ```tsx
194
+ <Card selected tone="brand" priority="primary" header="Team plan">…</Card>
195
+ <Card header="Free plan">…</Card>
196
+ ```
197
+
198
+ ## Loading
199
+
200
+ `loading` dims the card with a skeleton pulse and sets `aria-busy`. On a link card
201
+ it also blocks navigation until the load resolves.
202
+
203
+ ```tsx
204
+ <Card loading header="Loading…">
205
+ <Text>Content is being fetched.</Text>
206
+ </Card>
207
+ ```
208
+
209
+ ### Props
210
+
211
+ | Prop | Type | Default | Description |
212
+ |------|------|---------|-------------|
213
+ | `children?` | ReactNode | — | The card body. A string is wrapped in a `<Text>` at secondary priority,
214
+ following the card's `tone` and `size`; pass your own nodes to take over. |
215
+ | `footer?` | ReactNode | — | Footer content, usually action buttons. Rendered as a **left-aligned** row
216
+ (wraps under pressure) — the opposite of `Dialog`'s right-aligned footer. |
217
+ | `header?` | ReactNode | — | Header content, in the top zone. A string is wrapped in a `<Title>` — a real
218
+ heading in the document outline, tinted to match `tone`, at a level that
219
+ tracks `size` (small → 5, medium → 4, large → 3). Pass your own node
220
+ (`<Title level={2}>`, an `<h2>`) for a different level or styling. Omit for
221
+ no header. |
222
+ | `href?` | string | — | Turn the whole card into a link. The card renders a focusable anchor and its
223
+ accessible name comes from `header` → body → this URL (override with
224
+ `aria-label`). A link card is display content — don't nest interactive
225
+ controls inside it. |
226
+ | `icon?` | (string & {}) \| string \| number \| bigint \| boolean \| ReactElement \| Iterable \| ReactPortal \| Promise \| null | — | Leading visual for the header, laid out to the left of the `header` /
227
+ `subtitle` as one aligned row (so it doesn't sit inline in the title, where it
228
+ mis-aligns). A **string** is an icon shape, rendered as an `<Icon>` inside a
229
+ circular chip (sized per the card via `--card-icon-size`); pass an `<img>`,
230
+ initials (`<Text>`), or any node to use it as-is. Omit for none. |
231
+ | `loading?` | boolean | — | Loading state — dims the card with a skeleton pulse, sets `aria-busy`, and (in
232
+ link mode) blocks navigation. |
233
+ | `media?` | ReactNode | — | Media (image, illustration) rendered full-bleed to the card edge, clipped to
234
+ its corners. Position it with `mediaPosition`. |
235
+ | `mediaPosition?` | 'top' \| 'bottom' \| 'left' \| 'right' | 'top' | Which edge the `media` bleeds to. |
236
+ | `ping?` | string | — | Space-separated URLs the browser pings on navigation (only with `href`). |
237
+ | `priority?` | 'primary' \| 'secondary' \| 'tertiary' | 'primary' | Surface emphasis. `primary` (the default) is a clean sheet; `secondary` is a
238
+ subtle fill; `tertiary` is a frosted, semi-transparent panel with a backdrop
239
+ blur. |
240
+ | `rel?` | string | — | Anchor rel (only with `href`). |
241
+ | `round?` | boolean \| number \| string | — | Fully-round corners (`border-radius: 999px`, clamped to the box). Pass a
242
+ `number` (px) or a CSS length string (`'1rem'`) for a custom radius. Omit for
243
+ the default `--card-radius`. |
244
+ | `selected?` | boolean | — | Selected / chosen state — draws an inset ring in the tone color, for
245
+ choice-card / plan-picker patterns. |
246
+ | `size?` | 'small' \| 'medium' \| 'large' | 'medium' | Size variant — scales the padding. |
247
+ | `subtitle?` | ReactNode | — | A secondary line under the header title, rendered as smaller text
248
+ (`<Text size="small">`) in the heading section. Tinted to match `tone`, like
249
+ the title. Omit for no subtitle. |
250
+ | `target?` | string | — | Anchor target (only with `href`). |
251
+ | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Semantic tone, or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for
252
+ a one-off custom tone. Named tones re-point the surface + text; a custom
253
+ color keeps its hue while lightness/chroma are pinned. `'neutral'` (the
254
+ default) is the same as omitting it. |
255
+
256
+ Use the web component directly when you are not using React or Preact and a native control does not fit.
257
+
258
+ Slots hold media, header, and footer content. Unslotted content is the card body.
259
+
260
+ ```html
261
+ <a-card>
262
+ <a-title slot="header" level="5" role="heading" aria-level="5">Release notes</a-title>
263
+ <a-text>Everything without a slot attribute becomes the body.</a-text>
264
+ <a-button slot="footer" role="button" tabindex="0" size="small">
265
+ <a-button-label>Read more</a-button-label>
266
+ </a-button>
267
+ </a-card>
268
+ ```
269
+
270
+ Reach for the props first: **`tone`** sets the color (any CSS color for a custom
271
+ tone — it derives the surface in oklch), **`priority`** the fill, **`size`** the
272
+ padding. The one custom-color knob worth keeping is **`--card-tone-source`** (the
273
+ color a custom `tone` derives from — set it to drive the palette from your own
274
+ variable). The corner radius is **`--card-radius`**.
275
+
276
+ ```tsx
277
+ <Card tone="#e0457b" header="Custom" />
278
+ ```
279
+
280
+ **Header heading level.** A string `header` becomes a `<Title>` whose level tracks
281
+ `size` — small → 5, medium → 4, large → 3 — a real heading in the document outline.
282
+ For a different level or your own styling, pass a `<Title level={n}>` (or an
283
+ `<h2>`–`<h6>`) as the header; the card zeroes its top margin so it spaces the same
284
+ either way:
285
+
286
+ ```tsx
287
+ <Card header={<Title level={2}>Heading semantics</Title>}>
288
+ The header is a level-2 heading node, in the page outline.
289
+ </Card>
290
+ ```
291
+
292
+ For everything else, `<a-card>` exposes shadow **parts** —
293
+ `::part(container)` (the box / anchor), `::part(media)`, `::part(content)`,
294
+ `::part(header)` (the icon + text zone), `::part(icon)`, `::part(title)` (the
295
+ title / subtitle text column), `::part(body)`, `::part(footer)` — which you style
296
+ directly. Each section carries its own `--card-padding`, so a divider on
297
+ `::part(header)` reads as a true section rule.
298
+
299
+ Each example keeps its CSS in a `<style>` **inside** its own preview, targeting a
300
+ demo class on that preview's card — so the rule you read is literally in the
301
+ preview's DOM, next to the element it styles. In your app, swap the demo class for
302
+ your own selector.
303
+
304
+ **Scroll a capped body.** The card never scrolls, and the body doesn't by default.
305
+ When you cap the card's height and want the body to scroll instead of the card
306
+ growing, that's one line on `::part(body)`:
307
+
308
+ ```css
309
+ /* cap the card's height, then let the body scroll */
310
+ a-card.scrollable { height: 160px; }
311
+ a-card.scrollable::part(body) { overflow: auto; }
312
+ ```
313
+
314
+ **Separate the header** with a divider by styling `::part(header)`:
315
+
316
+ ```css
317
+ a-card.divided::part(header) {
318
+ padding-bottom: 8px;
319
+ border-bottom: 1px solid var(--border-5);
320
+ }
321
+ ```
@@ -0,0 +1,296 @@
1
+ # Checkbox
2
+
3
+ Checkboxes let users select one or more items from a set, or toggle an option on or off. Use them for multiple independent choices; for a single immediate setting, use a switch instead.
4
+
5
+ The `Checkbox` wrapper renders an `<a-checkbox>` element that manages its own state. It works both controlled and uncontrolled with `checked` / `defaultChecked` (boolean or `'indeterminate'`) and `onStateChange` for updates.
6
+
7
+ ## Tone
8
+
9
+ `tone` colors the **mark** — the checked-box fill and, for a tinted tone, the
10
+ unselected box border (a light shade of the fill that darkens on hover and active,
11
+ like the neutral border; `neutral` keeps a grey border). The label and hint stay
12
+ neutral. Six named tones (`neutral` default), or pass any CSS color for a custom tone.
13
+
14
+ ```tsx
15
+ <Checkbox defaultChecked tone="neutral">Neutral</Checkbox>
16
+ <Checkbox defaultChecked tone="brand">Brand</Checkbox>
17
+ <Checkbox defaultChecked tone="critical">Critical</Checkbox>
18
+ <Checkbox defaultChecked tone="mediumaquamarine">Custom color</Checkbox>
19
+ ```
20
+
21
+ ## Selected-only tone
22
+
23
+ `toneSelected` tones the **checked mark only** — the empty box stays neutral grey
24
+ until it's checked. Reach for it instead of `tone` when a resting tinted border would
25
+ read as a validation error. Same value set as `tone`.
26
+
27
+ ```tsx
28
+ <Checkbox toneSelected="brand">Off stays neutral</Checkbox>
29
+ <Checkbox defaultChecked toneSelected="critical">Checked mark tinted</Checkbox>
30
+ ```
31
+
32
+ ## Coloring the text
33
+
34
+ There's no text-tone prop. To tint the **label**, set `color` on the checkbox with a
35
+ theme-aware `--text-N-{tone}` token (the label inherits it); for the **hint**, add a
36
+ `color` rule on the `a-checkbox-hint` child. Pair either with `tone` / `toneSelected`
37
+ to tint the whole control.
38
+
39
+ ```css
40
+ /* Mark via the prop, text via a plain color rule — the label inherits the host
41
+ color, the hint is targeted directly. The class is just for the demo — swap
42
+ your own selector. */
43
+ a-checkbox.toned-text { color: var(--text-1-critical); }
44
+ a-checkbox.toned-text a-checkbox-hint { color: var(--text-3-critical); }
45
+ ```
46
+
47
+ ## Size
48
+
49
+ Three sizes scale the box **and** the label + hint type together: `small`
50
+ (14px box / 13px label), `medium` (16px / 15px, the default), and `large`
51
+ (18px / 17px).
52
+
53
+ ```tsx
54
+ <Checkbox defaultChecked size="small">Small</Checkbox>
55
+ <Checkbox defaultChecked>Medium</Checkbox>
56
+ <Checkbox defaultChecked size="large">Large</Checkbox>
57
+ ```
58
+
59
+ ## Label and hint
60
+
61
+ Pass the label as `children` or the `label` prop. Add a `hint` for a secondary line beneath. For a label-less checkbox (e.g. a table "select all"), pass `aria-label`.
62
+
63
+ ```tsx
64
+ <Checkbox label="Email notifications" hint="Weekly digest, never marketing." />
65
+ <Checkbox aria-label="Select all rows" />
66
+ ```
67
+
68
+ ## States
69
+
70
+ Checkboxes are unchecked, checked, or indeterminate (for partial selection in groups).
71
+
72
+ ```tsx
73
+ <Checkbox defaultChecked label="Checked" />
74
+ <Checkbox label="Unchecked" />
75
+ <Checkbox defaultChecked="indeterminate" label="Indeterminate" />
76
+ ```
77
+
78
+ ## Disabled
79
+
80
+ The `disabled` attribute dims the checkbox and removes it from the tab order.
81
+
82
+ ```tsx
83
+ <Checkbox disabled label="Disabled" />
84
+ <Checkbox disabled defaultChecked label="Disabled checked" />
85
+ ```
86
+
87
+ ## Controlled
88
+
89
+ Uncontrolled by default: pass `defaultChecked` and read the value from `onStateChange` or at form-submit. Make it controlled with `checked` and `onStateChange` to drive it from your store.
90
+
91
+ ```tsx
92
+ const [agreed, setAgreed] = useState(false)
93
+
94
+ <Checkbox
95
+ checked={agreed}
96
+ onStateChange={(_e, { next }) => setAgreed(next as boolean)}
97
+ >
98
+ I agree
99
+ </Checkbox>
100
+ ```
101
+
102
+ `onStateChange` fires before the element applies, with signature `(event, { next, prev })`. Call `event.preventDefault()` to veto the change. A controlled checkbox with no `onStateChange` is read-only.
103
+
104
+ ## Events
105
+
106
+ Three callbacks, in firing order:
107
+
108
+ | Callback | When | Cancelable | Payload |
109
+ |---|---|---|---|
110
+ | `onStateChange(e, { next, prev })` | **before** the toggle applies | ✅ `e.preventDefault()` vetoes | next / prev state |
111
+ | `onChange(e)` | **after** it applies | — | native `change` event |
112
+ | `onValueChange(e, attrs)` | **after** it applies | — | `{ checked, indeterminate, name, value }` |
113
+
114
+ Reach for **`onStateChange`** only to intercept or veto (or to drive a controlled
115
+ checkbox). For the everyday "react to the new value", use **`onValueChange`** — it
116
+ hands you the value without touching `event.target`. `onChange` is the plain
117
+ native hook. (In controlled mode `onChange`/`onValueChange` fire once you've updated
118
+ `checked`.) `onFocus` / `onBlur` work too — the host is the focusable element.
119
+
120
+ Checkbox has `onStateChange` (and `Input` doesn't) because its state is
121
+ **discrete** — `checked · unchecked · indeterminate` — so a transition can be
122
+ vetoed *before* it applies. A text field's value is free-form, with no discrete
123
+ state to model or veto, so `Input` (and the composed `Select`) expose only the
124
+ `onValueChange` half. The `onValueChange` shape is identical across all of them.
125
+
126
+ ```tsx
127
+ <Checkbox onValueChange={(_e, { checked }) => save(checked)}>Subscribe</Checkbox>
128
+ ```
129
+
130
+ ## Indeterminate
131
+
132
+ Set `checked="indeterminate"` to show the "mixed" state (minus glyph). Common for a parent checkbox whose state derives from its children: all on = checked, all off = unchecked, mixed = indeterminate.
133
+
134
+ ```tsx
135
+ function ParentChild() {
136
+ const [on, setOn] = useState([true, false])
137
+ return (
138
+ <>
139
+ <Checkbox
140
+ checked={on[0] && on[1] ? true : on[0] || on[1] ? 'indeterminate' : false}
141
+ onStateChange={(_e, { next }) => setOn([next === true, next === true])}
142
+ >
143
+ Parent
144
+ </Checkbox>
145
+ <div style={{ marginInlineStart: 24 }}>
146
+ <Checkbox
147
+ checked={on[0]}
148
+ onStateChange={(_e, { next }) => setOn([next === true, on[1]])}
149
+ >
150
+ Child 1
151
+ </Checkbox>
152
+ <Checkbox
153
+ checked={on[1]}
154
+ onStateChange={(_e, { next }) => setOn([on[0], next === true])}
155
+ >
156
+ Child 2
157
+ </Checkbox>
158
+ </div>
159
+ </>
160
+ )
161
+ }
162
+ ```
163
+
164
+ ## Accessibility
165
+
166
+ The wrapper sets `role="checkbox"` and `aria-checked` (`"true"` / `"false"` / `"mixed"`), and derives the accessible name from `label` or `children`. For a label-less checkbox, pass `aria-label`.
167
+
168
+ Space toggles the checkbox. Follows the [WAI-ARIA checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/).
169
+
170
+ ### Props
171
+
172
+ | Prop | Type | Default | Description |
173
+ |------|------|---------|-------------|
174
+ | `checked?` | CheckboxValue | — | Controlled checked state. When provided the checkbox is controlled — it
175
+ renders exactly this and never self-applies; `onStateChange` is a *request*
176
+ the consumer accepts by updating this prop. Use `defaultChecked` for an
177
+ uncontrolled checkbox. `'indeterminate'` shows the minus glyph and takes
178
+ visual precedence; clicking it requests `true`. |
179
+ | `defaultChecked?` | CheckboxValue | false | Initial checked state for an uncontrolled checkbox. Read once; later
180
+ changes are ignored and the element updates its state after interaction. |
181
+ | `disabled?` | boolean | — | Disable the checkbox (no interaction, dropped from the tab order). |
182
+ | `hint?` | ReactNode | — | Secondary text rendered under the label — explanatory copy, like
183
+ Input's hint. Not part of the accessible name. |
184
+ | `label?` | string | — | Visible label — the *value* of the checkbox (clicked along with the box).
185
+ Convenience for the common single-string case; for richer content (markup,
186
+ a link, an info icon) use `children`. When both are supplied, `label`
187
+ renders first. Required unless `children` or `aria-label` is provided
188
+ (a `role="checkbox"` takes its name from the author, not the markup). |
189
+ | `name?` | string | — | Form field name. Inside a `<form>` the checkbox submits under this name,
190
+ contributing `value` when checked — like a native checkbox. |
191
+ | `onChange?` | (event) => void | — | Fired *after* the checked state changes — a native `change` event (the
192
+ post-apply counterpart to `onStateChange`). Not cancelable. For a controlled
193
+ checkbox this fires once you've updated `checked`. |
194
+ | `onStateChange?` | (event, detail) => void | — | Fired on click / Space *before* the element applies any change. Event-first
195
+ so `event.preventDefault()` is the synchronous veto (uncontrolled mode);
196
+ `detail` carries `{ next, prev }`. In controlled mode the element never
197
+ self-applies — answer by updating `checked`, reject by doing nothing. |
198
+ | `onValueChange?` | (event, attrs) => void | — | Like `onChange`, but with a `{ checked, indeterminate, name, value }` snapshot
199
+ as the second argument, matching `Input`'s `onValueChange`. |
200
+ | `round?` | boolean \| number \| string | — | Round the checkbox mark to a circle (`border-radius: 999px` on the box). Pass
201
+ a `number` (px) or a CSS length string for a rounded-square mark instead. |
202
+ | `size?` | 'small' \| 'medium' \| 'large' | 'medium' | Size variant. small=14px, medium=16px, large=18px box. |
203
+ | `tone?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Color of the **mark** in every state — the checked-box fill *and* the
204
+ unselected box border. A named tone or any literal CSS color (`'#ff1493'`,
205
+ `'rebeccapurple'`) for a one-off custom tone. Named tones track light/dark mode
206
+ automatically; a custom color keeps its hue + chroma and pins lightness to the
207
+ fill curve. Use `toneSelected` instead to tone only the checked mark and leave
208
+ the empty box neutral. The label + hint stay neutral — recolor them in plain
209
+ CSS via the theme-aware `--text-N-{tone}` tokens. |
210
+ | `toneSelected?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Like `tone`, but colored onto the **checked mark only** — the empty box stays
211
+ neutral grey until it's checked. Prefer this over `tone` when a resting tinted
212
+ border would read as a validation state. Same value set as `tone`; if both are
213
+ set, `tone` governs the off-state border and `toneSelected` the checked fill. |
214
+ | `value?` | string | "on" | Value submitted with the form when checked — like a native checkbox. |
215
+
216
+ Use the web component directly when you are not using React or Preact and a native control does not fit.
217
+
218
+ The focusable host carries the state. Label and hint are light-DOM children.
219
+
220
+ ```html
221
+ <a-checkbox role="checkbox" tabindex="0" name="updates" default-state="checked">
222
+ <a-checkbox-label>Email updates</a-checkbox-label>
223
+ <a-checkbox-hint>Product and security notices.</a-checkbox-hint>
224
+ </a-checkbox>
225
+ ```
226
+
227
+ ### Native HTML checkbox
228
+
229
+ For a standard HTML form, add `data-anta` to a native checkbox. It keeps the
230
+ browser's label behavior, checked and indeterminate states, and form submission
231
+ while using Anta's control surface.
232
+
233
+ `size`, `tone`, `tone-selected`, and `round` use the matching Checkbox visual
234
+ treatments.
235
+
236
+ ```html
237
+ <div style="display: grid; gap: 8px">
238
+ <label><input data-anta type="checkbox" name="updates" size="small" tone="brand"> Product updates</label>
239
+ <label><input data-anta type="checkbox" name="security" tone-selected="success" checked> Security notices</label>
240
+ <label><input data-anta type="checkbox" name="billing" size="large" round tone="warning" checked> Billing alerts</label>
241
+ </div>
242
+ ```
243
+
244
+ Reach for the props first: **`tone`** colors the mark in every state,
245
+ **`toneSelected`** only the checked mark (any CSS color for a custom tone — it
246
+ derives the full rest/hover/active curve in oklch), **`size`** the dimensions + type.
247
+ To tint the label + hint, add a `color` rule (on the host for the label, on
248
+ `a-checkbox-hint` for the hint) with the `--text-N-{tone}` tokens — there's no
249
+ text-tone prop. The focus ring is the global
250
+ [`--focus-ring`](../colors.md#focus-ring).
251
+
252
+ ```tsx
253
+ <Checkbox tone="#e0457b" defaultChecked>Custom color</Checkbox>
254
+ ```
255
+
256
+ For anything else, `<a-checkbox>` is light-DOM — the **box is `::before`**, the
257
+ **check/minus glyph is `::after`** (a masked SVG — swap `--checkbox-mask-check` for a
258
+ custom mark), the label/hint are the `a-checkbox-label` / `a-checkbox-hint` children.
259
+ Target them with plain CSS, **per state** (an un-layered rule beats `@layer anta`
260
+ without `!important`). The classes below are just for the demos:
261
+
262
+ ```css
263
+ /* Outlined — an unfilled box: the checked box keeps its background and paints the
264
+ border + checkmark in the tone color (light-dark() tunes both themes at once). */
265
+ a-checkbox.outlined:state(checked)::before {
266
+ background: var(--checkbox-bg);
267
+ border-color: light-dark(#5f4bc3, #7460d7);
268
+ }
269
+ a-checkbox.outlined:state(checked)::after {
270
+ background-color: light-dark(#5f4bc3, #7460d7);
271
+ }
272
+
273
+ /* Customized — bold label, bigger circular box, bigger checkmark.
274
+ Colors use light-dark() so they're tuned for both themes at once. */
275
+ a-checkbox.customized { font-weight: 700; }
276
+ a-checkbox.customized::before,
277
+ a-checkbox.customized::after { inline-size: 24px; block-size: 24px; margin-block-start: -2px; }
278
+ a-checkbox.customized::before { border-radius: 50%; border-width: 2px; }
279
+ a-checkbox.customized::after { mask-size: 16px; } /* bigger mark */
280
+ a-checkbox.customized:state(checked)::before {
281
+ background: light-dark(#1f6e5f, #2f9c84);
282
+ border-color: light-dark(#1f6e5f, #2f9c84);
283
+ }
284
+
285
+ /* Beautified — a full white heart mark swapped in via --checkbox-mask-check
286
+ (centered by the default mask-position), sized down a touch, on a pink fill */
287
+ a-checkbox.beautified {
288
+ --checkbox-mask-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54z'/%3E%3C/svg%3E");
289
+ }
290
+ a-checkbox.beautified::before { border-radius: 8px; }
291
+ a-checkbox.beautified::after { mask-size: 10px; } /* smaller heart */
292
+ a-checkbox.beautified:state(checked)::before {
293
+ background: light-dark(#ec4899, #f472b6); /* pink, both themes */
294
+ border-color: transparent;
295
+ }
296
+ ```