@antadesign/anta 0.3.17 → 0.3.19

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 (77) hide show
  1. package/README.md +38 -242
  2. package/dist/components/Input.d.ts +4 -1
  3. package/dist/components/Input.js +2 -0
  4. package/dist/components/InputDate.css +1 -0
  5. package/dist/components/InputDate.d.ts +1 -0
  6. package/dist/components/InputDate.js +5 -5
  7. package/dist/components/Loader.d.ts +39 -0
  8. package/dist/components/Loader.js +33 -0
  9. package/dist/components/Menu.d.ts +5 -1
  10. package/dist/components/Menu.js +3 -2
  11. package/dist/components/Progress.d.ts +17 -8
  12. package/dist/components/Progress.js +12 -9
  13. package/dist/components/Select.d.ts +1 -0
  14. package/dist/components/Select.js +9 -13
  15. package/dist/components/SelectFaceted.d.ts +1 -0
  16. package/dist/components/SelectFaceted.js +29 -23
  17. package/dist/components/select-parts.css +1 -0
  18. package/dist/elements/a-button.css +1 -1
  19. package/dist/elements/a-icon.d.ts +1 -0
  20. package/dist/elements/a-icon.js +1 -0
  21. package/dist/elements/a-input.css +1 -1
  22. package/dist/elements/a-loader.css +1 -0
  23. package/dist/elements/a-loader.d.ts +12 -0
  24. package/dist/elements/a-loader.js +15 -0
  25. package/dist/elements/a-menu.js +12 -0
  26. package/dist/elements/a-progress.css +1 -1
  27. package/dist/elements/a-progress.js +32 -2
  28. package/dist/elements/a-slider.css +1 -1
  29. package/dist/elements/a-tabs.css +1 -1
  30. package/dist/elements/a-tabs.d.ts +3 -0
  31. package/dist/elements/a-tabs.js +40 -1
  32. package/dist/elements/index.d.ts +1 -0
  33. package/dist/elements/index.js +3 -0
  34. package/dist/general_types.d.ts +27 -2
  35. package/dist/index.d.ts +6 -1
  36. package/dist/index.js +2 -0
  37. package/dist/jsx-runtime.d.ts +11 -1
  38. package/dist/reset.css +1 -1
  39. package/dist/theme-anta.css +1 -1
  40. package/docs/accessibility.md +34 -0
  41. package/docs/changelog.md +551 -0
  42. package/docs/colors.md +42 -0
  43. package/docs/comparison.md +28 -0
  44. package/docs/components/banner.md +267 -0
  45. package/docs/components/button.md +603 -0
  46. package/docs/components/card.md +322 -0
  47. package/docs/components/checkbox.md +297 -0
  48. package/docs/components/dialog.md +314 -0
  49. package/docs/components/expander.md +529 -0
  50. package/docs/components/icon.md +155 -0
  51. package/docs/components/input-autocomplete.md +101 -0
  52. package/docs/components/input-date.md +406 -0
  53. package/docs/components/input-time.md +174 -0
  54. package/docs/components/input.md +610 -0
  55. package/docs/components/loader.md +139 -0
  56. package/docs/components/menu.md +447 -0
  57. package/docs/components/progress.md +182 -0
  58. package/docs/components/radio.md +369 -0
  59. package/docs/components/select-faceted.md +456 -0
  60. package/docs/components/select.md +931 -0
  61. package/docs/components/slider.md +142 -0
  62. package/docs/components/switch.md +198 -0
  63. package/docs/components/tabs.md +471 -0
  64. package/docs/components/tag.md +185 -0
  65. package/docs/components/text.md +148 -0
  66. package/docs/components/title.md +146 -0
  67. package/docs/components/toaster.md +313 -0
  68. package/docs/components/tooltip.md +177 -0
  69. package/docs/credits.md +22 -0
  70. package/docs/index.md +52 -0
  71. package/docs/install-config.md +240 -0
  72. package/docs/normalization.md +105 -0
  73. package/docs/overview.md +10 -0
  74. package/docs/packages/stickers.md +187 -0
  75. package/docs/packages/table.md +153 -0
  76. package/docs/theming.md +13 -0
  77. package/package.json +10 -6
@@ -0,0 +1,322 @@
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's no dedicated actions slot. For a control at the top-right of the header — a
120
+ "more" button, a tag — pass a laid-out node as the `header` and put the title and
121
+ the control in it yourself (a flex row). You own the layout, so the title and
122
+ control sit exactly where you place them.
123
+
124
+ ```tsx
125
+ <Card
126
+ header={
127
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
128
+ <Title level={4}>Production database</Title>
129
+ <Button size="small" priority="tertiary" icon="more" aria-label="More" />
130
+ </div>
131
+ }
132
+ >
133
+ Region us-east-1 · 4 vCPU · 16 GB
134
+ </Card>
135
+ ```
136
+
137
+ ## Footer
138
+
139
+ `footer` is a **left-aligned** row (it wraps under pressure) — the opposite of
140
+ `Dialog`'s right-aligned footer, since a card reads as left-aligned content rather
141
+ than a decision surface. Pass several buttons as a fragment.
142
+
143
+ ```tsx
144
+ <Card
145
+ header="Invite teammates"
146
+ footer={<><Button priority="primary" size="small" label="Send invites" /><Button size="small" label="Copy link" /></>}
147
+ >
148
+ <Text>They'll get access to this project and its test runs.</Text>
149
+ </Card>
150
+ ```
151
+
152
+ ## Media
153
+
154
+ Pass `media` for a full-bleed image or illustration — it escapes the card padding
155
+ and clips to the corners. `mediaPosition` places it on the `top` (the default),
156
+ `bottom`, `left`, or `right` edge.
157
+
158
+ **Top** (default)
159
+
160
+ **Left**
161
+
162
+ ```tsx
163
+ <Card media={<img src="/hero.jpg" alt="" />} header="Top media">…</Card> {/* top is the default */}
164
+ <Card media={<img src="/hero.jpg" alt="" />} mediaPosition="left" header="Left media">…</Card>
165
+ {/* mediaPosition also takes "bottom" and "right" */}
166
+ ```
167
+
168
+ ## Link card
169
+
170
+ Give a card an `href` and the whole thing becomes a link — a single focusable
171
+ anchor, keyboard-activatable, with the `hover` lift and inset focus ring. The
172
+ accessible name comes from the header (falling back to the body, then the URL), so
173
+ a screen reader announces one concise link instead of the whole card's text. A
174
+ link card is display content: keep interactive controls (buttons, nested links)
175
+ out of it — its text is non-selectable and the whole surface navigates.
176
+
177
+ ```tsx
178
+ <Card
179
+ href="/docs/ci"
180
+ header={<><Icon shape="book-open" /> CI integration guide</>}
181
+ >
182
+ Wire Antithesis into your pipeline in a few minutes.
183
+ </Card>
184
+ ```
185
+
186
+ Set `target` / `rel` for a new-tab link (`target="_blank" rel="noopener"`), or
187
+ `aria-label` to override the derived link name.
188
+
189
+ ## Selected
190
+
191
+ `selected` draws an inset ring in the tone color — for choice cards, plan pickers,
192
+ and selected list items.
193
+
194
+ ```tsx
195
+ <Card selected tone="brand" priority="primary" header="Team plan">…</Card>
196
+ <Card header="Free plan">…</Card>
197
+ ```
198
+
199
+ ## Loading
200
+
201
+ `loading` dims the card with a skeleton pulse and sets `aria-busy`. On a link card
202
+ it also blocks navigation until the load resolves.
203
+
204
+ ```tsx
205
+ <Card loading header="Loading…">
206
+ <Text>Content is being fetched.</Text>
207
+ </Card>
208
+ ```
209
+
210
+ ### Props
211
+
212
+ | Prop | Type | Default | Description |
213
+ |------|------|---------|-------------|
214
+ | `children?` | ReactNode | — | The card body. A string is wrapped in a `<Text>` at secondary priority,
215
+ following the card's `tone` and `size`; pass your own nodes to take over. |
216
+ | `footer?` | ReactNode | — | Footer content, usually action buttons. Rendered as a **left-aligned** row
217
+ (wraps under pressure) — the opposite of `Dialog`'s right-aligned footer. |
218
+ | `header?` | ReactNode | — | Header content, in the top zone. A string is wrapped in a `<Title>` — a real
219
+ heading in the document outline, tinted to match `tone`, at a level that
220
+ tracks `size` (small → 5, medium → 4, large → 3). Pass your own node
221
+ (`<Title level={2}>`, an `<h2>`) for a different level or styling. Omit for
222
+ no header. |
223
+ | `href?` | string | — | Turn the whole card into a link. The card renders a focusable anchor and its
224
+ accessible name comes from `header` → body → this URL (override with
225
+ `aria-label`). A link card is display content — don't nest interactive
226
+ controls inside it. |
227
+ | `icon?` | (string & {}) \| string \| number \| bigint \| boolean \| ReactElement \| Iterable \| ReactPortal \| Promise \| null | — | Leading visual for the header, laid out to the left of the `header` /
228
+ `subtitle` as one aligned row (so it doesn't sit inline in the title, where it
229
+ mis-aligns). A **string** is an icon shape, rendered as an `<Icon>` inside a
230
+ circular chip (sized per the card via `--card-icon-size`); pass an `<img>`,
231
+ initials (`<Text>`), or any node to use it as-is. Omit for none. |
232
+ | `loading?` | boolean | — | Loading state — dims the card with a skeleton pulse, sets `aria-busy`, and (in
233
+ link mode) blocks navigation. |
234
+ | `media?` | ReactNode | — | Media (image, illustration) rendered full-bleed to the card edge, clipped to
235
+ its corners. Position it with `mediaPosition`. |
236
+ | `mediaPosition?` | 'top' \| 'bottom' \| 'left' \| 'right' | 'top' | Which edge the `media` bleeds to. |
237
+ | `ping?` | string | — | Space-separated URLs the browser pings on navigation (only with `href`). |
238
+ | `priority?` | 'primary' \| 'secondary' \| 'tertiary' | 'primary' | Surface emphasis. `primary` (the default) is a clean sheet; `secondary` is a
239
+ subtle fill; `tertiary` is a frosted, semi-transparent panel with a backdrop
240
+ blur. |
241
+ | `rel?` | string | — | Anchor rel (only with `href`). |
242
+ | `round?` | boolean \| number \| string | — | Fully-round corners (`border-radius: 999px`, clamped to the box). Pass a
243
+ `number` (px) or a CSS length string (`'1rem'`) for a custom radius. Omit for
244
+ the default `--card-radius`. |
245
+ | `selected?` | boolean | — | Selected / chosen state — draws an inset ring in the tone color, for
246
+ choice-card / plan-picker patterns. |
247
+ | `size?` | 'small' \| 'medium' \| 'large' | 'medium' | Size variant — scales the padding. |
248
+ | `subtitle?` | ReactNode | — | A secondary line under the header title, rendered as smaller text
249
+ (`<Text size="small">`) in the heading section. Tinted to match `tone`, like
250
+ the title. Omit for no subtitle. |
251
+ | `target?` | string | — | Anchor target (only with `href`). |
252
+ | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Semantic tone, or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for
253
+ a one-off custom tone. Named tones re-point the surface + text; a custom
254
+ color keeps its hue while lightness/chroma are pinned. `'neutral'` (the
255
+ default) is the same as omitting it. |
256
+
257
+ Use this when you are not using the React or Preact wrapper and a native HTML control does not fit: construct the equivalent Anta web component from the elements below.
258
+
259
+ Slots hold media, header, and footer content. Unslotted content is the card body.
260
+
261
+ ```html
262
+ <a-card>
263
+ <a-title slot="header" level="5" role="heading" aria-level="5">Release notes</a-title>
264
+ <a-text>Everything without a slot attribute becomes the body.</a-text>
265
+ <a-button slot="footer" role="button" tabindex="0" size="small">
266
+ <a-button-label>Read more</a-button-label>
267
+ </a-button>
268
+ </a-card>
269
+ ```
270
+
271
+ Reach for the props first: **`tone`** sets the color (any CSS color for a custom
272
+ tone — it derives the surface in oklch), **`priority`** the fill, **`size`** the
273
+ padding. The one custom-color knob worth keeping is **`--card-tone-source`** (the
274
+ color a custom `tone` derives from — set it to drive the palette from your own
275
+ variable). The corner radius is **`--card-radius`**.
276
+
277
+ ```tsx
278
+ <Card tone="#e0457b" header="Custom" />
279
+ ```
280
+
281
+ **Header heading level.** A string `header` becomes a `<Title>` whose level tracks
282
+ `size` — small → 5, medium → 4, large → 3 — a real heading in the document outline.
283
+ For a different level or your own styling, pass a `<Title level={n}>` (or an
284
+ `<h2>`–`<h6>`) as the header; the card zeroes its top margin so it spaces the same
285
+ either way:
286
+
287
+ ```tsx
288
+ <Card header={<Title level={2}>Heading semantics</Title>}>
289
+ The header is a level-2 heading node, in the page outline.
290
+ </Card>
291
+ ```
292
+
293
+ For everything else, `<a-card>` exposes shadow **parts** —
294
+ `::part(container)` (the box / anchor), `::part(media)`, `::part(content)`,
295
+ `::part(header)` (the icon + text zone), `::part(icon)`, `::part(title)` (the
296
+ title / subtitle text column), `::part(body)`, `::part(footer)` — which you style
297
+ directly. Each section carries its own `--card-padding`, so a divider on
298
+ `::part(header)` reads as a true section rule.
299
+
300
+ Each example keeps its CSS in a `<style>` **inside** its own preview, targeting a
301
+ demo class on that preview's card — so the rule you read is literally in the
302
+ preview's DOM, next to the element it styles. In your app, swap the demo class for
303
+ your own selector.
304
+
305
+ **Scroll a capped body.** The card never scrolls, and the body doesn't by default.
306
+ When you cap the card's height and want the body to scroll instead of the card
307
+ growing, that's one line on `::part(body)`:
308
+
309
+ ```css
310
+ /* cap the card's height, then let the body scroll */
311
+ a-card.scrollable { height: 160px; }
312
+ a-card.scrollable::part(body) { overflow: auto; }
313
+ ```
314
+
315
+ **Separate the header** with a divider by styling `::part(header)`:
316
+
317
+ ```css
318
+ a-card.divided::part(header) {
319
+ padding-bottom: 8px;
320
+ border-bottom: 1px solid var(--border-5);
321
+ }
322
+ ```
@@ -0,0 +1,297 @@
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` colours 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
+ ## Colouring 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
+ colour, 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 & 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 ignored (the element then owns the state). |
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 2nd argument — the ergonomic "just give me the new value" callback
200
+ (mirrors `Input`'s `onValueChange`). |
201
+ | `round?` | boolean \| number \| string | — | Round the checkbox mark to a circle (`border-radius: 999px` on the box). Pass
202
+ a `number` (px) or a CSS length string for a rounded-square mark instead. |
203
+ | `size?` | 'small' \| 'medium' \| 'large' | 'medium' | Size variant. small=14px, medium=16px, large=18px box. |
204
+ | `tone?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Colour of the **mark** in every state — the checked-box fill *and* the
205
+ unselected box border. A named tone or any literal CSS color (`'#ff1493'`,
206
+ `'rebeccapurple'`) for a one-off custom tone. Named tones track light/dark mode
207
+ automatically; a custom colour keeps its hue + chroma and pins lightness to the
208
+ fill curve. Use `toneSelected` instead to tone only the checked mark and leave
209
+ the empty box neutral. The label + hint stay neutral — recolour them in plain
210
+ CSS via the theme-aware `--text-N-{tone}` tokens. |
211
+ | `toneSelected?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Like `tone`, but coloured onto the **checked mark only** — the empty box stays
212
+ neutral grey until it's checked. Prefer this over `tone` when a resting tinted
213
+ border would read as a validation state. Same value set as `tone`; if both are
214
+ set, `tone` governs the off-state border and `toneSelected` the checked fill. |
215
+ | `value?` | string | "on" | Value submitted with the form when checked — like a native checkbox. |
216
+
217
+ Use this when you are not using the React or Preact wrapper and a native HTML control does not fit: construct the equivalent Anta web component from the elements below.
218
+
219
+ The focusable host carries the state. Label and hint are light-DOM children.
220
+
221
+ ```html
222
+ <a-checkbox role="checkbox" tabindex="0" name="updates" default-state="checked">
223
+ <a-checkbox-label>Email updates</a-checkbox-label>
224
+ <a-checkbox-hint>Product and security notices.</a-checkbox-hint>
225
+ </a-checkbox>
226
+ ```
227
+
228
+ ### Native HTML checkbox
229
+
230
+ For a standard HTML form, add `data-anta` to a native checkbox. It keeps the
231
+ browser's label behavior, checked and indeterminate states, and form submission
232
+ while using Anta's control surface.
233
+
234
+ `size`, `tone`, `tone-selected`, and `round` use the matching Checkbox visual
235
+ treatments.
236
+
237
+ ```html
238
+ <div style="display: grid; gap: 8px">
239
+ <label><input data-anta type="checkbox" name="updates" size="small" tone="brand"> Product updates</label>
240
+ <label><input data-anta type="checkbox" name="security" tone-selected="success" checked> Security notices</label>
241
+ <label><input data-anta type="checkbox" name="billing" size="large" round tone="warning" checked> Billing alerts</label>
242
+ </div>
243
+ ```
244
+
245
+ Reach for the props first: **`tone`** colours the mark in every state,
246
+ **`toneSelected`** only the checked mark (any CSS colour for a custom tone — it
247
+ derives the full rest/hover/active curve in oklch), **`size`** the dimensions + type.
248
+ To tint the label + hint, add a `color` rule (on the host for the label, on
249
+ `a-checkbox-hint` for the hint) with the `--text-N-{tone}` tokens — there's no
250
+ text-tone prop. The focus ring is the global
251
+ [`--focus-ring`](../colors.md#focus-ring).
252
+
253
+ ```tsx
254
+ <Checkbox tone="#e0457b" defaultChecked>Custom colour</Checkbox>
255
+ ```
256
+
257
+ For anything else, `<a-checkbox>` is light-DOM — the **box is `::before`**, the
258
+ **check/minus glyph is `::after`** (a masked SVG — swap `--checkbox-mask-check` for a
259
+ custom mark), the label/hint are the `a-checkbox-label` / `a-checkbox-hint` children.
260
+ Target them with plain CSS, **per state** (an un-layered rule beats `@layer anta`
261
+ without `!important`). The classes below are just for the demos:
262
+
263
+ ```css
264
+ /* Outlined — an unfilled box: the checked box keeps its background and paints the
265
+ border + checkmark in the tone colour (light-dark() tunes both themes at once). */
266
+ a-checkbox.outlined:state(checked)::before {
267
+ background: var(--checkbox-bg);
268
+ border-color: light-dark(#5f4bc3, #7460d7);
269
+ }
270
+ a-checkbox.outlined:state(checked)::after {
271
+ background-color: light-dark(#5f4bc3, #7460d7);
272
+ }
273
+
274
+ /* Customized — bold label, bigger circular box, bigger checkmark.
275
+ Colours use light-dark() so they're tuned for both themes at once. */
276
+ a-checkbox.customized { font-weight: 700; }
277
+ a-checkbox.customized::before,
278
+ a-checkbox.customized::after { inline-size: 24px; block-size: 24px; margin-block-start: -2px; }
279
+ a-checkbox.customized::before { border-radius: 50%; border-width: 2px; }
280
+ a-checkbox.customized::after { mask-size: 16px; } /* bigger mark */
281
+ a-checkbox.customized:state(checked)::before {
282
+ background: light-dark(#1f6e5f, #2f9c84);
283
+ border-color: light-dark(#1f6e5f, #2f9c84);
284
+ }
285
+
286
+ /* Beautified — a full white heart mark swapped in via --checkbox-mask-check
287
+ (centred by the default mask-position), sized down a touch, on a pink fill */
288
+ a-checkbox.beautified {
289
+ --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");
290
+ }
291
+ a-checkbox.beautified::before { border-radius: 8px; }
292
+ a-checkbox.beautified::after { mask-size: 10px; } /* smaller heart */
293
+ a-checkbox.beautified:state(checked)::before {
294
+ background: light-dark(#ec4899, #f472b6); /* pink, both themes */
295
+ border-color: transparent;
296
+ }
297
+ ```