@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.
- package/README.md +38 -242
- package/dist/anta_helpers.d.ts +2 -2
- package/dist/components/Banner.d.ts +3 -3
- package/dist/components/Button.d.ts +1 -1
- package/dist/components/Calendar.d.ts +11 -12
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +1 -1
- package/dist/components/Checkbox.d.ts +8 -9
- package/dist/components/Dialog.d.ts +1 -1
- package/dist/components/Expander.d.ts +5 -6
- package/dist/components/Input.d.ts +6 -3
- package/dist/components/Input.js +3 -1
- package/dist/components/InputAutocomplete.d.ts +8 -8
- package/dist/components/InputDate.css +1 -0
- package/dist/components/InputDate.d.ts +1 -0
- package/dist/components/InputDate.js +5 -5
- package/dist/components/InputTime.d.ts +3 -3
- package/dist/components/InputTime.js +1 -1
- package/dist/components/Menu.d.ts +7 -3
- package/dist/components/Menu.js +3 -2
- package/dist/components/MenuItem.d.ts +1 -1
- package/dist/components/MenuItem.js +1 -1
- package/dist/components/Progress.d.ts +1 -1
- package/dist/components/RadioGroup.d.ts +12 -13
- package/dist/components/Select.d.ts +32 -38
- package/dist/components/Select.js +9 -13
- package/dist/components/SelectFaceted.d.ts +28 -33
- package/dist/components/SelectFaceted.js +29 -23
- package/dist/components/Slider.d.ts +5 -5
- package/dist/components/Switch.d.ts +4 -4
- package/dist/components/Tabs.d.ts +7 -7
- package/dist/components/Title.d.ts +1 -1
- package/dist/components/Toaster.d.ts +1 -1
- package/dist/components/Tooltip.d.ts +1 -1
- package/dist/components/select-parts.css +1 -0
- package/dist/elements/a-card.css +1 -1
- package/dist/elements/a-dialog.css +1 -1
- package/dist/elements/a-expander.css +1 -1
- package/dist/elements/a-expander.d.ts +1 -0
- package/dist/elements/a-expander.js +12 -2
- package/dist/elements/a-input.css +1 -1
- package/dist/elements/a-menu.js +12 -0
- package/dist/elements/a-progress.css +1 -1
- package/dist/elements/a-slider.css +1 -1
- package/dist/elements/a-tabs.css +1 -1
- package/dist/elements/a-tabs.d.ts +3 -0
- package/dist/elements/a-tabs.js +40 -1
- package/dist/elements/a-tooltip.css +1 -1
- package/dist/elements/a-tooltip.d.ts +1 -1
- package/dist/elements/a-tooltip.js +1 -1
- package/dist/elements/index.d.ts +1 -1
- package/dist/general_types.d.ts +24 -22
- package/dist/index.d.ts +3 -3
- package/dist/jsx-runtime.d.ts +9 -0
- package/docs/accessibility.md +34 -0
- package/docs/changelog.md +561 -0
- package/docs/colors.md +42 -0
- package/docs/comparison.md +28 -0
- package/docs/components/banner.md +267 -0
- package/docs/components/button.md +603 -0
- package/docs/components/card.md +321 -0
- package/docs/components/checkbox.md +296 -0
- package/docs/components/dialog.md +314 -0
- package/docs/components/expander.md +526 -0
- package/docs/components/icon.md +155 -0
- package/docs/components/input-autocomplete.md +100 -0
- package/docs/components/input-date.md +401 -0
- package/docs/components/input-time.md +174 -0
- package/docs/components/input.md +610 -0
- package/docs/components/loader.md +139 -0
- package/docs/components/menu.md +511 -0
- package/docs/components/progress.md +182 -0
- package/docs/components/radio.md +368 -0
- package/docs/components/select-faceted.md +451 -0
- package/docs/components/select.md +926 -0
- package/docs/components/slider.md +147 -0
- package/docs/components/switch.md +198 -0
- package/docs/components/tabs.md +471 -0
- package/docs/components/tag.md +185 -0
- package/docs/components/text.md +148 -0
- package/docs/components/title.md +146 -0
- package/docs/components/toaster.md +313 -0
- package/docs/components/tooltip.md +177 -0
- package/docs/credits.md +18 -0
- package/docs/index.md +52 -0
- package/docs/install-config.md +240 -0
- package/docs/normalization.md +102 -0
- package/docs/overview.md +10 -0
- package/docs/packages/stickers.md +187 -0
- package/docs/packages/table.md +153 -0
- package/docs/theming.md +13 -0
- 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
|
+
```
|