@antadesign/anta 0.3.18 → 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 (59) 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/Menu.d.ts +5 -1
  8. package/dist/components/Menu.js +3 -2
  9. package/dist/components/Select.d.ts +1 -0
  10. package/dist/components/Select.js +9 -13
  11. package/dist/components/SelectFaceted.d.ts +1 -0
  12. package/dist/components/SelectFaceted.js +29 -23
  13. package/dist/components/select-parts.css +1 -0
  14. package/dist/elements/a-input.css +1 -1
  15. package/dist/elements/a-menu.js +12 -0
  16. package/dist/elements/a-slider.css +1 -1
  17. package/dist/elements/a-tabs.css +1 -1
  18. package/dist/elements/a-tabs.d.ts +3 -0
  19. package/dist/elements/a-tabs.js +40 -1
  20. package/dist/general_types.d.ts +2 -0
  21. package/dist/jsx-runtime.d.ts +9 -0
  22. package/docs/accessibility.md +34 -0
  23. package/docs/changelog.md +551 -0
  24. package/docs/colors.md +42 -0
  25. package/docs/comparison.md +28 -0
  26. package/docs/components/banner.md +267 -0
  27. package/docs/components/button.md +603 -0
  28. package/docs/components/card.md +322 -0
  29. package/docs/components/checkbox.md +297 -0
  30. package/docs/components/dialog.md +314 -0
  31. package/docs/components/expander.md +529 -0
  32. package/docs/components/icon.md +155 -0
  33. package/docs/components/input-autocomplete.md +101 -0
  34. package/docs/components/input-date.md +406 -0
  35. package/docs/components/input-time.md +174 -0
  36. package/docs/components/input.md +610 -0
  37. package/docs/components/loader.md +139 -0
  38. package/docs/components/menu.md +447 -0
  39. package/docs/components/progress.md +182 -0
  40. package/docs/components/radio.md +369 -0
  41. package/docs/components/select-faceted.md +456 -0
  42. package/docs/components/select.md +931 -0
  43. package/docs/components/slider.md +142 -0
  44. package/docs/components/switch.md +198 -0
  45. package/docs/components/tabs.md +471 -0
  46. package/docs/components/tag.md +185 -0
  47. package/docs/components/text.md +148 -0
  48. package/docs/components/title.md +146 -0
  49. package/docs/components/toaster.md +313 -0
  50. package/docs/components/tooltip.md +177 -0
  51. package/docs/credits.md +22 -0
  52. package/docs/index.md +52 -0
  53. package/docs/install-config.md +240 -0
  54. package/docs/normalization.md +105 -0
  55. package/docs/overview.md +10 -0
  56. package/docs/packages/stickers.md +187 -0
  57. package/docs/packages/table.md +153 -0
  58. package/docs/theming.md +13 -0
  59. package/package.json +10 -6
@@ -0,0 +1,603 @@
1
+ # Button
2
+
3
+ An action control. The `Button` JSX wrapper renders an `<a-button>` web
4
+ component (or `<a role="button">` when `href` is set). Tone, priority,
5
+ size, and state are all plain attributes, so the styling is identical
6
+ whether you use the wrapper or author the element by hand.
7
+
8
+ ## Priorities
9
+
10
+ A button's priority helps control how much attention it draws:
11
+
12
+ ```tsx
13
+ <Button tone="brand" priority="primary" label="Publish" />
14
+ <Button tone="brand" priority="secondary" label="Move" />
15
+ <Button tone="brand" priority="tertiary" label="Cancel" />
16
+ <Button tone="brand" priority="quaternary" label="Remind" />
17
+ ```
18
+
19
+ - `primary` — Saturated fill. Main call to action.
20
+ - `secondary` — Lighter background. Default priority.
21
+ - `tertiary` — Background-less at rest, fills on hover.
22
+ - `quaternary` — Text only, no background.
23
+
24
+ ## Tones
25
+
26
+ Six named tones — `neutral` (default), `brand`, `critical`, `info`,
27
+ `success`, `warning`. Omitting `tone` — or passing an empty string —
28
+ resolves to `neutral`. In addition, a custom `tone` is possible: pass any
29
+ literal CSS color and the button will adapt to it.
30
+
31
+ ```tsx
32
+ <Button label="Save" />
33
+ <Button tone="brand" label="Publish" />
34
+ <Button tone="critical" label="Delete" />
35
+ <Button tone="info" label="Preview" />
36
+ <Button tone="success" label="Approve" />
37
+ <Button tone="warning" label="Archive" />
38
+ ```
39
+
40
+ ```tsx
41
+ <Button priority="primary" tone="#ff1493" label="Pinkify" />
42
+ <Button priority="secondary" tone="oklch(0.655 0.261 356.9)" label="Pinkify" />
43
+ <Button priority="tertiary" tone="hsl(328 100% 54%)" label="Pinkify" />
44
+ <Button priority="primary" tone="mediumaquamarine" label="Mintify" />
45
+ <Button priority="secondary" tone="rgb(102 205 170)" label="Mintify" />
46
+ <Button priority="tertiary" tone="lch(75.7% 39.2 167.8)" label="Mintify" />
47
+ ```
48
+
49
+ For custom tones, **primary** keeps the source's hue and chroma but pins
50
+ its *lightness* near the Brand primary's, so any input — too light or too
51
+ dark — still lands at a Brand-like fill (hover / active step the lightness,
52
+ and the label keeps the standard primary text color). **Secondary**,
53
+ **tertiary**, and **quaternary** take only its *hue* — lightness, chroma,
54
+ and alpha come from the brand-tone curve (via `oklch()` relative color), so
55
+ pale or low-chroma inputs (`tone="#cccccc"`, `tone="white"`) still produce a
56
+ legible button. The full priority × state matrix — rest, hover, active,
57
+ secondary alpha overlay, tertiary fill — is derived automatically, and
58
+ re-tunes between light and dark.
59
+
60
+ If you need pixel-precise color, set `--button-fg` or `--button-bg`
61
+ yourself — inline via `style`, or from your own class or selector. The
62
+ resolver lives in `@layer anta`, so any un-layered rule of yours wins
63
+ regardless of specificity (and inline beats everything). The same override
64
+ also pins hover and active to that color — it overrides the per-state rules
65
+ too, so there's no state change; to keep distinct states, set the per-state
66
+ variables instead — `--button-bg-{priority}-hover`,
67
+ `--button-bg-{priority}-active`, and the `--button-fg-*` equivalents.
68
+
69
+ ## Sizes
70
+
71
+ ```tsx
72
+ <Button tone="brand" size="small" label="Small" />
73
+ <Button tone="brand" size="medium" label="Medium" /> // default
74
+ <Button tone="brand" size="large" label="Large" />
75
+ ```
76
+
77
+ Three sizes (`medium` is default). The **type scale** (font / line-height) and
78
+ the **icon** track the size, matching Anta's text scale and the same-size
79
+ `Input`; the hit area grows or shrinks to match.
80
+
81
+ Height comes from the label's `line-height` plus a uniform 3.5px vertical padding
82
+ and a 1px `padding-bottom` that optically centers Anta's font (its glyphs sit a
83
+ touch low in the line-box) — netting `line-height + 8`, so it lands at
84
+ 24 / 28 / 32px without any per-size padding tuning.
85
+
86
+ | size | height | font / line | icon | text-edge `padding-x` | icon-edge `padding-x` |
87
+ |---|---|---|---|---|---|
88
+ | `small` | 24px | 13 / 16 | 14px | 7px | 5px |
89
+ | `medium` (default) | 28px | 15 / 20 | 16px | 9px | 7px |
90
+ | `large` | 32px | 17 / 24 | 18px | 13px | 11px |
91
+
92
+ The **icon-edge** padding is the text-edge value minus 2px — an icon at
93
+ an edge needs less breathing room than a glyph — so only the side a
94
+ (leading or trailing) icon occupies is trimmed; the text side keeps the
95
+ full padding. The gap between an icon and the label is `0.5ch` at every
96
+ size.
97
+
98
+ Need a different font size? Style the `<a-button-label>` directly — set
99
+ its `font-size` and `line-height` (the label's `line-height` drives the
100
+ button's height, so the box follows your value), and (if the text then sits a
101
+ little high) drop the 1px `padding-bottom` that optically centers Anta's font.
102
+
103
+ By default a button packs its content to the start, so when buttons share
104
+ a width — a vertical menu, equal grid cells — their labels line up on a
105
+ common left edge, which usually reads best in a list.
106
+
107
+ To center the label inside a wider button instead, set
108
+ `justify-content: center` on it (the tones grid above does exactly this).
109
+
110
+ ### Paddingless
111
+
112
+ ```tsx
113
+ If you're familiar with the basics, click
114
+ <Button tone="critical" priority="quaternary" paddingless label="here" />
115
+ to skip ahead to the live demo.
116
+ ```
117
+
118
+ Only valid on `priority="quaternary"`. Zeros the outer padding so the
119
+ button sits flush with surrounding prose — useful when you want a
120
+ button to read as an inline link inside a sentence.
121
+
122
+ ## Round
123
+
124
+ `round` fully rounds the corners — a pill for text buttons, a perfect circle for
125
+ icon-only ones. The radius (`999px`) is clamped to half the button's height, so it
126
+ adapts to every `size` with no configuration. Pass a **number** (px) or a CSS length
127
+ string for a custom radius instead of a full pill.
128
+
129
+ ```tsx
130
+ <Button round tone="brand" label="Pill" />
131
+ <Button round tone="brand" icon="heart" aria-label="Like" /> // circle
132
+ <Button round={10} tone="brand" label="Custom 10px" /> // custom radius
133
+ ```
134
+
135
+ ## Icons
136
+
137
+ ```tsx
138
+ <Button tone="brand" priority="primary" icon="check" label="Confirm" />
139
+ <Button tone="brand" iconTrailing="external-link" label="Read the docs" />
140
+ <Button tone="brand" priority="tertiary" icon="filter" iconTrailing="chevron-down" label="Filter" />
141
+ ```
142
+
143
+ The wrapper renders content in this order inside the button: `icon` →
144
+ `label` → `children` → `iconTrailing`. Icon shape names come from the
145
+ `IconShape` union — see the [Icon](./icon.md) page for the full
146
+ set. `children` (if any) lands between `label` and `iconTrailing`.
147
+
148
+ ### Icon-only
149
+
150
+ ```tsx
151
+ <Button tone="neutral" icon="dots-vertical" size="small" />
152
+ <Button tone="critical" icon="trash" />
153
+ <Button tone="brand" icon="check" size="large" />
154
+ ```
155
+
156
+ Pass `icon` alone — no `label`, no `iconTrailing`, no `children` — and
157
+ the button collapses to a square. A min-size is pinned to a square
158
+ matching the labeled-button height (24px / 28px / 32px for small /
159
+ medium / large) so icon-only and labeled buttons line up, and a tight
160
+ flex parent can't clip the icon.
161
+
162
+ Icon-only buttons get an accessible name automatically — the wrapper
163
+ sets `aria-label={icon}` so `<Button icon="trash" />` ships with the
164
+ name "trash". Pass your own `aria-label` to override.
165
+
166
+ ### Children
167
+
168
+ ```tsx
169
+ <Button tone="brand" label="Save" icon="check">
170
+ <span style={{ opacity: 0.7 }}>⌘S</span>
171
+ </Button>
172
+ ```
173
+
174
+ Children render after the label and before the trailing icon, so you
175
+ can mix custom inline content — keyboard hints, badges, counters —
176
+ with the prop-driven API.
177
+
178
+ Children also work without `label` — pass them as the only content.
179
+
180
+ Text and number children are auto-wrapped in `<a-button-label>` (so the
181
+ truncation rule applies and they don't trip the icon-only detector);
182
+ element children — a `<span>`, a `<Tooltip>`, … — pass through
183
+ unwrapped. Children with no visible content render nothing: empty or
184
+ whitespace-only strings, `NaN`, `null` / `undefined`, and booleans are
185
+ dropped (a valid `0` still renders).
186
+
187
+ ## Underline
188
+
189
+ ```tsx
190
+ <Button tone="brand" priority="tertiary" underline="solid" label="Solid" />
191
+ <Button tone="brand" priority="tertiary" underline="dashed" label="Dashed" />
192
+ <Button tone="brand" priority="tertiary" underline="dotted" label="Dotted" />
193
+ ```
194
+
195
+ When the button has a [link mode](#link-mode), or you simply want an
196
+ underline stylistically, it supports three styles — `solid`, `dashed`,
197
+ `dotted` (only valid on `priority="tertiary"` / `"quaternary"`).
198
+
199
+ ## States
200
+
201
+ ```tsx
202
+ <Button tone="brand" loading label="Submitting" />
203
+ <Button tone="brand" disabled label="Locked" />
204
+ <Button tone="brand" selected label="Toggled on" />
205
+ <Button tone="brand" loading disabled label="Critical" />
206
+ ```
207
+
208
+ - **`loading`** — diagonal stripe overlay slides across the button.
209
+ Stripe color follows `currentColor`, so it tracks the tone. Blocks
210
+ clicks via `pointer-events: none`, and (like `disabled`) removes the
211
+ button from the tab order so Enter/Space can't fire it mid-flight.
212
+ - **`disabled`** — locks the colors to the disabled palette, sets
213
+ `pointer-events: none`, and removes the button from the tab order.
214
+ Beats inline `--button-bg` overrides.
215
+ - **`selected`** — toggled-on / pressed visual; shares the active
216
+ state's look. Useful for filter chips and icon toggles.
217
+
218
+ `ButtonCopy` is a copy button — a `Button` preset for copy-to-clipboard. It
219
+ composes a plain `<Button>` with a slotted `<a-copy>` element that writes to the
220
+ clipboard when the button is activated. A button with a copy glyph changes the
221
+ glyph to a check and retones to `success` for ~2s (✕ / `critical` on failure).
222
+ Set `copy` for a literal string, or drop the `label` for an icon-only copy
223
+ button.
224
+
225
+ ```tsx
226
+ <ButtonCopy copy="npm i @antadesign/anta" label="Copy install command" />
227
+ <ButtonCopy copy="https://anta.design" priority="tertiary" /> // icon-only
228
+
229
+ <ButtonCopy
230
+ copy="npm i @antadesign/anta"
231
+ label="Copy install command"
232
+ iconPlacement="none"
233
+ copiedLabel="Copied to clipboard"
234
+ />
235
+ ```
236
+
237
+ The write lives in the standalone `<a-copy>` element, so the button stays a plain
238
+ button. `ButtonCopy` slots it in for you; to build the same by hand, drop an
239
+ `<a-copy copy=…>` into a `<Button>` or `<a-button>`. `onCopied(ok)` fires after
240
+ each attempt. Override the resting glyph with `icon`; the check / ✕ swap in only
241
+ during the feedback window.
242
+
243
+ With `iconPlacement="none"`, the button stays unchanged. A successful pointer
244
+ copy shows `✓ Copied` beside the pointer. A keyboard copy shows it at the
245
+ button's inline start. Use `copiedLabel` to change the text.
246
+
247
+ ### Icon placement
248
+
249
+ `iconPlacement` sets where the copy glyph sits: `leading` (the default) or
250
+ `trailing`. Pass `none` to omit the glyph. A successful copy then shows the
251
+ confirmation label without changing the button. Set `copiedLabel` to change the
252
+ label text.
253
+
254
+ ```tsx
255
+ <ButtonCopy copy={value} label="Leading" /> // default
256
+ <ButtonCopy copy={value} label="Trailing" iconPlacement="trailing" />
257
+ <ButtonCopy copy={value} label="No icon" iconPlacement="none" />
258
+ ```
259
+
260
+ ### Copy a DOM node
261
+
262
+ Pass `copyNode` to copy a rendered region as rich text (`text/html`) plus plain
263
+ text, instead of a string. Bare `copyNode` copies the nearest ancestor marked
264
+ `data-copy-source`; a string is a CSS selector for an ancestor region. The copy
265
+ button itself is stripped from what's copied.
266
+
267
+ ```tsx
268
+ <div data-copy-source>
269
+ <Text>The quick brown fox jumps over the lazy dog.</Text>
270
+ <ButtonCopy copyNode label="Copy card" size="small" priority="tertiary" />
271
+ </div>
272
+ ```
273
+
274
+ Copying rich text lets a paste target keep formatting (bold, links, list
275
+ structure) while a plain-text target still gets clean text. Paste the copied
276
+ card into a rich editor and a plain one to see both.
277
+
278
+ ### Copy the page URL
279
+
280
+ `copyUrl` copies the current page URL (`location.href`) — no `copy` value needed.
281
+ `copyWithUrl` prefixes a `copy` string with `// URL: <href>`, so a copied snippet
282
+ carries a link back to where it came from.
283
+
284
+ ```tsx
285
+ <ButtonCopy copyUrl label="Copy link" />
286
+ <ButtonCopy copy={snippet} copyWithUrl label="Copy snippet" /> {/* snippet + source URL */}
287
+ ```
288
+
289
+ ### Lazy content
290
+
291
+ When the content is expensive to compute or changes over time, keep `copy`
292
+ reactive and refresh it in **`onCopyRequest`**, which fires on **pointerdown** —
293
+ the click that follows copies the latest value.
294
+
295
+ ```tsx
296
+ const [report, setReport] = useState('')
297
+
298
+ <ButtonCopy
299
+ copy={report}
300
+ label="Copy report"
301
+ onCopyRequest={() => setReport(generateReport())} // recomputed on pointerdown
302
+ />
303
+ ```
304
+
305
+ The value is recomputed on pointerdown and read on the click a moment later, so
306
+ the pointerdown→click gap absorbs the work — including an update that arrives from
307
+ another thread. Only the serializable `copy` string crosses, through the normal
308
+ re-render, so this holds in off-UI-thread runtimes (a worker-rendered app) where a
309
+ callback couldn't. The clipboard API's user-gesture requirement is preserved
310
+ because the write happens on the click.
311
+
312
+ ### Props
313
+
314
+ | Prop | Type | Default | Description |
315
+ |------|------|---------|-------------|
316
+ | `copiedLabel?` | string | Copied | Text in the successful no-icon confirmation. |
317
+ | `copy?` | string | — | Text copied to the clipboard on activation. |
318
+ | `copyNode?` | boolean \| string | — | Copy a DOM node as rich text (`text/html`) + plain text. `true` copies
319
+ the nearest ancestor marked `data-copy-source`; a string is a CSS
320
+ selector for an ancestor region (`closest`). The copy control is stripped
321
+ from the copied output. |
322
+ | `copyUrl?` | true | — | Copy the current page URL (`location.href`). |
323
+ | `copyWithUrl?` | boolean | — | Prefix the copied text with `// URL: <current page URL>`. |
324
+ | `disabled?` | boolean | — | Disable the button. |
325
+ | `download?` | string \| boolean | — | Anchor download attribute. Empty string / `true` triggers a download with the resource's default name; a string overrides the filename. |
326
+ | `form?` | string | — | Form id when the button isn't a descendant of its form. |
327
+ | `href?` | string | — | URL to link to. A definite string renders `<a role="button">`; omit it
328
+ (or pass `undefined`) to render `<a-button>`, so `href={maybeUrl}`
329
+ degrades to a plain button when the URL is absent. |
330
+ | `icon?` | IconShape | — | Leading icon shape. When set alone (no `label`, no `iconTrailing`, no
331
+ `children`), the button renders as a square icon-only control and
332
+ the wrapper auto-supplies `aria-label={icon}` (override by passing
333
+ your own `aria-label`). |
334
+ | `iconPlacement?` | 'leading' \| 'trailing' \| 'none' | 'leading' | Where the copy glyph sits relative to the label — or `'none'` to omit it.
335
+ Without a glyph, a successful copy shows a small confirmation label near
336
+ the pointer and leaves the button unchanged. |
337
+ | `iconTrailing?` | IconShape | — | Trailing icon shape. Renders after `children`, last in the slot order. |
338
+ | `label?` | string | — | Label text. Renders between the leading icon and `children`. |
339
+ | `loading?` | boolean | — | Show a rotating loading indicator. Blocks clicks and keyboard
340
+ activation, and removes the button from the tab order while active. |
341
+ | `onClick?` | (e) => void | — | Click handler. |
342
+ | `onCopied?` | (ok) => void | — | Fires after the copy attempt with whether it succeeded. |
343
+ | `onCopyRequest?` | () => void | — | Compute the copy content lazily. Fires on pointerdown / keydown; update
344
+ `copy` (a state change) here and the activation copies the latest value.
345
+ The gap lets the update land even off the UI thread — only the
346
+ serializable `copy` string crosses. |
347
+ | `paddingless?` | boolean | — | Drops outer padding to zero. |
348
+ | `ping?` | string | — | Space-separated URLs the browser pings on navigation. |
349
+ | `priority?` | 'primary' \| 'secondary' \| 'tertiary' \| 'quaternary' | secondary | Visual emphasis. |
350
+ | `rel?` | string | — | Anchor rel. |
351
+ | `role?` | string | button | ARIA role override (e.g. `'gridcell'` when a button is a cell in a grid).
352
+ Forwarded to the underlying element. |
353
+ | `round?` | boolean \| number \| string | — | Fully-round corners — a pill for text buttons, a circle for icon-only ones
354
+ (`border-radius: 999px`, clamped to the element's height). Pass a `number`
355
+ (px) or a CSS length string (`'1rem'`) for a custom radius instead. |
356
+ | `selected?` | boolean | — | Toggled-on / pressed state, e.g. for filter chips. |
357
+ | `size?` | 'small' \| 'medium' \| 'large' | medium | Size variant. small=24px, medium=28px, large=32px. Omit the
358
+ attribute or pass `'medium'` for the default — both render
359
+ identically and emit no DOM attribute. |
360
+ | `tabIndex?` | number | 0 | Tab order. The button is keyboard-focusable by default (`0`) and
361
+ becomes `-1` automatically while `disabled` or `loading` — `<a-button>`
362
+ and `<a role="button">` aren't focusable without an explicit tabindex,
363
+ and a loading button must stay out of the tab order so Enter/Space can't
364
+ fire it mid-flight. |
365
+ | `target?` | string | — | Anchor target. |
366
+ | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | neutral | Semantic tone, or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`)
367
+ for a one-off custom tone. Primary uses the color as-is; secondary,
368
+ tertiary, and quaternary take its hue and pin lightness/chroma to the
369
+ brand curve so any input stays legible. |
370
+ | `type?` | 'button' \| 'submit' \| 'reset' | — | Form submission type. |
371
+ | `underline?` | 'solid' \| 'dashed' \| 'dotted' | — | Underline style. |
372
+
373
+ ## Link mode
374
+
375
+ ```tsx
376
+ <Button tone="brand" href="/docs" target="_blank" label="Read the docs" />
377
+ ```
378
+
379
+ Setting `href` switches the rendered tag from `<a-button>` to
380
+ `<a role="button" data-anta>`. Styling is identical — both selectors share
381
+ the same CSS rules. `<Button>` adds the `data-anta` marker for you.
382
+
383
+ **`href` is optional**, so a conditional URL needs no branching. Pass
384
+ `href={maybeUrl}` (a `string | undefined`): a definite string links out, an
385
+ absent / `undefined` href renders a plain `<a-button>`. Pair it with `disabled`
386
+ for a link that's live when the URL exists and inert otherwise — no two-render
387
+ split or conditional spread:
388
+
389
+ ```tsx
390
+ // Links out when there's a URL; a plain, disabled button when there isn't.
391
+ <Button href={reportUrl} disabled={!reportUrl} iconTrailing="external-link" label="View report" />
392
+ ```
393
+
394
+ Anta's CSS targets `a-button, a[role="button"][data-anta]` — an anchor (`<a>`)
395
+ with `role="button"`, the `data-anta` opt-in marker, and the right attributes
396
+ gets the styling. That means anta doesn't need an `as` / `asChild` /
397
+ `component` prop or per-framework integrations. Compose your own thin wrapper
398
+ around your library's `Link` component — which renders an `<a>`, so adding
399
+ `role="button"` and `data-anta` matches the selector.
400
+
401
+ The `data-anta` marker is required because `role="button"` is a *generic* ARIA
402
+ role that other widgets emit too — gating on the marker keeps anta from
403
+ restyling anchors it doesn't own (a third-party menu, an embedded editor's
404
+ toolbar, etc.).
405
+
406
+ ```tsx
407
+ // LinkButton.tsx — anta-styled link for client-side routing
408
+ import { Link, type LinkProps } from 'react-router-dom'
409
+ import type { IconShape } from '@antadesign/anta'
410
+
411
+ type LinkButtonProps = LinkProps & {
412
+ tone?: 'neutral' | 'brand' | 'critical' | 'info' | 'success' | 'warning'
413
+ priority?: 'primary' | 'secondary' | 'tertiary' | 'quaternary'
414
+ size?: 'small' | 'medium' | 'large'
415
+ icon?: IconShape
416
+ iconTrailing?: IconShape
417
+ label?: string
418
+ }
419
+
420
+ export const LinkButton = ({
421
+ tone, priority, size, icon, iconTrailing, label, children, ...rest
422
+ }: LinkButtonProps) => (
423
+ <Link role="button" data-anta tone={tone} priority={priority} size={size} {...rest}>
424
+ {icon && <a-icon shape={icon} aria-hidden="true" />}
425
+ {label != null && <a-button-label>{label}</a-button-label>}
426
+ {children}
427
+ {iconTrailing && <a-icon shape={iconTrailing} aria-hidden="true" />}
428
+ </Link>
429
+ )
430
+ ```
431
+
432
+ Usage:
433
+
434
+ ```tsx
435
+ <LinkButton to="/dashboard" tone="brand" label="Dashboard" />
436
+ <LinkButton to="/docs" priority="tertiary" iconTrailing="external-link" label="Docs" />
437
+ ```
438
+
439
+ This approach would work for Next.js `<Link>`, TanStack Router, or any
440
+ other routing library.
441
+
442
+ Beyond a plain click, a `Button` can drive a native form or emit your own
443
+ event:
444
+
445
+ ### Form submission
446
+
447
+ For non-anchor buttons, `type="submit"` and `type="reset"` integrate
448
+ with native forms. `type="submit"` calls `form.requestSubmit()` and also
449
+ dispatches a `submitdetailed` event on the form with
450
+ `{ formData, submitter: { tag, attrs } }` in `detail` — handy for
451
+ analytics or multi-button forms.
452
+
453
+ ```tsx
454
+ <form id="signup">
455
+ <Button tone="brand" type="submit" label="Sign up" />
456
+ <Button tone="brand" priority="tertiary" type="reset" label="Clear" />
457
+ </form>
458
+ {/* Associate with a form by id when the button is outside */}
459
+ <Button tone="neutral" type="submit" form="signup" label="Submit from outside" />
460
+ ```
461
+
462
+ ### Custom click events
463
+
464
+ `data-custom-event="<name>"` makes the button dispatch a bubbling
465
+ `CustomEvent("<name>")` on click. Use it to instrument analytics
466
+ without taking ownership of `onClick`.
467
+
468
+ ```tsx
469
+ <Button tone="brand" label="Save" data-custom-event="save-clicked" />
470
+ ```
471
+
472
+ ### Props
473
+
474
+ 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.
475
+
476
+ Set `role="button"` and `tabindex="0"` when authoring `<a-button>` directly.
477
+
478
+ ```html
479
+ <a-button role="button" tabindex="0" priority="primary">
480
+ <a-icon shape="plus" aria-hidden="true"></a-icon>
481
+ <a-button-label>Create project</a-button-label>
482
+ </a-button>
483
+ ```
484
+
485
+ ### Native HTML button
486
+
487
+ The styling attaches to three element shapes, so you can adopt the button look on
488
+ markup you already own instead of routing through `<Button>`. `data-anta` is the
489
+ opt-in marker — it stops Anta restyling a `role="button"` element it does not own
490
+ (a third-party menu, an embedded editor's toolbar):
491
+
492
+ | Element | For |
493
+ | --- | --- |
494
+ | `<a-button>` | the component's own tag, always styled — what `<Button>` renders |
495
+ | `<a role="button" data-anta>` | a link — what `<Button href>` renders (see [Link mode](#link-mode)) |
496
+ | `<button data-anta>` | a native form control — keeps native form submission, `disabled`, and Enter / Space activation |
497
+
498
+ `tone` / `priority` / `size` are plain attributes on the element (the `<Button>`
499
+ wrapper sets them for you). A native `<button>` also carries its own default
500
+ `type="submit"`, so inside a `<form>` it submits without extra wiring:
501
+
502
+ ```html
503
+ <!-- A real <button>: submits forms and toggles `disabled` natively. -->
504
+ <button data-anta tone="brand" priority="primary">Save changes</button>
505
+ <button data-anta priority="secondary">Cancel</button>
506
+ <button data-anta tone="critical" priority="tertiary">Delete</button>
507
+ ```
508
+
509
+ Reach for the props first: **`tone`** sets the colour (any CSS colour for a custom
510
+ tone — it derives the whole tone × priority × state curve in oklch), **`priority`**
511
+ the emphasis, **`size`** the dimensions. The focus ring is the global
512
+ [`--focus-ring`](../colors.md#focus-ring).
513
+
514
+ ```tsx
515
+ <Button tone="#e0457b" priority="primary" label="Custom" />
516
+ ```
517
+
518
+ For anything beyond the props, `<a-button>` is light-DOM — restyle it with plain
519
+ CSS (an un-layered rule beats `@layer anta` without `!important`). Set
520
+ `background-color` / `color` **per state**, plus full type control (size,
521
+ line-height, letter-spacing, `font-feature-settings`), padding, radius, even a
522
+ frosted `backdrop-filter` — no need to touch a single token. **`light-dark()`** works
523
+ too (Anta sets `color-scheme` from its theme toggle), so colours adapt to dark mode
524
+ with no `.dark` selector. The classes below are just for the demos:
525
+
526
+ ```css
527
+ /* First — inverted pill: a theme-flipping gradient (near-black in light,
528
+ near-white in dark) via light-dark() in the gradient stops. Anta sets
529
+ color-scheme from its .dark toggle, so it adapts with no .dark selector. */
530
+ a-button.checkout {
531
+ background: linear-gradient(140deg, light-dark(#26262b, #fff), light-dark(#000, #dcdce0));
532
+ color: light-dark(#fff, #0b0b0c);
533
+ font-size: 20px;
534
+ line-height: 1.2em;
535
+ padding: 12px 26px;
536
+ border-radius: 999px;
537
+ }
538
+ a-button.checkout:hover { background: linear-gradient(140deg, light-dark(#3a3a42, #fff), light-dark(#161618, #cacace)); }
539
+ a-button.checkout:active { background: linear-gradient(140deg, light-dark(#000, #ececf0), light-dark(#1a1a1c, #fff)); }
540
+ a-button.checkout a-button-label { /* customised label */
541
+ text-transform: uppercase;
542
+ letter-spacing: 0.14em;
543
+ font-weight: 600;
544
+ }
545
+
546
+ /* Second — liquid glass: frost + translucent tint + a diagonal specular sheen,
547
+ a top/bottom bevel, and a soft lift. Sits on a colourful backdrop so it reads. */
548
+ a-button.glass {
549
+ color: #fff;
550
+ background-color: rgba(255, 255, 255, 0.12);
551
+ background-image: linear-gradient(135deg, rgba(255,255,255,0.55), rgba(255,255,255,0.06) 45%, transparent 65%);
552
+ border: 1px solid rgba(255, 255, 255, 0.4);
553
+ border-radius: 999px;
554
+ backdrop-filter: blur(8px) saturate(1.8) brightness(1.06);
555
+ box-shadow:
556
+ inset 0 1px 1px rgba(255,255,255,0.65), /* top bevel */
557
+ inset 0 -2px 3px rgba(255,255,255,0.18),
558
+ 0 8px 24px rgba(0,0,0,0.22); /* lift */
559
+ font-size: 20px; line-height: 1.2em; padding: 12px 26px;
560
+ }
561
+ a-button.glass:hover { background-color: rgba(255, 255, 255, 0.22); }
562
+ a-button.glass:active { background-color: rgba(255, 255, 255, 0.08); }
563
+ a-button.glass a-button-label { /* beautified label — gradient text */
564
+ font-weight: 600;
565
+ background: linear-gradient(180deg, #fff, #cfe0ff);
566
+ background-clip: text;
567
+ color: transparent;
568
+ filter: drop-shadow(0 1px 1px rgba(0,0,0,0.3));
569
+ }
570
+ ```
571
+
572
+ ```tsx
573
+ <Button className="custom" tone="brand" loading label="Saving changes" />
574
+
575
+ <style>{`
576
+ /* Keep loading's blocked interaction; replace only its stripe overlay. */
577
+ a-button.custom[loading]::before {
578
+ inset: -200%;
579
+ border-radius: 100%;
580
+ background: conic-gradient(
581
+ from 0deg,
582
+ color-mix(in oklch, currentColor 0%, transparent),
583
+ color-mix(in oklch, currentColor 10%, transparent)
584
+ );
585
+ opacity: 1;
586
+ filter: none;
587
+ animation: button-loader-field-spin 1s linear infinite;
588
+ animation-delay: -9999s;
589
+ }
590
+
591
+ @keyframes button-loader-field-spin {
592
+ to { transform: rotate(1turn); }
593
+ }
594
+ `}</style>
595
+ ```
596
+
597
+ **Loader field.** `loading` still blocks pointer and keyboard activation. This
598
+ only replaces its default stripe overlay with the Loader’s conic field, scaled
599
+ beyond the button so it can rotate behind the label without exposing an edge.
600
+
601
+ Don't reach for the resolved `--button-fg` / `--button-bg`: they're recomputed per
602
+ state, so setting one only catches a single state — restyle per state as above, or
603
+ use `tone` for a full custom-colour curve.