@antadesign/anta 0.3.30 → 0.3.31

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.
@@ -1,32 +1,31 @@
1
1
  # Button
2
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.
3
+ A button starts an action. Use its priority, tone, and size to match the action's
4
+ importance and context.
7
5
 
8
6
  ## Priority
9
7
 
10
- A button's priority helps control how much attention it draws:
8
+ Use one primary button for the main action in a group. Lower priorities reduce
9
+ visual emphasis.
11
10
 
12
11
  ```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" />
12
+ <Button tone="brand" priority="primary" label="Publish" />
13
+ <Button tone="brand" priority="secondary" label="Move" />
14
+ <Button tone="brand" priority="tertiary" label="Cancel" />
16
15
  <Button tone="brand" priority="quaternary" label="Remind" />
17
16
  ```
18
17
 
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.
18
+ - `primary`: main call to action
19
+ - `secondary`: supporting action and the default
20
+ - `tertiary`: low-emphasis action with a hover background
21
+ - `quaternary`: text-only action
23
22
 
24
23
  ## Tone
25
24
 
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.
25
+ Six named tones: `neutral` (default), `brand`, `critical`, `info`, `success`,
26
+ and `warning`. Omitting `tone`, or passing an empty string, resolves to
27
+ `neutral`. A custom tone is also possible: pass any literal CSS color and the
28
+ button will adapt to it.
30
29
 
31
30
  ```tsx
32
31
  <Button label="Save" />
@@ -37,264 +36,157 @@ literal CSS color and the button will adapt to it.
37
36
  <Button tone="warning" label="Archive" />
38
37
  ```
39
38
 
39
+ ### Custom tone
40
+
41
+ Pass any CSS color to `tone`. Custom tones work with every priority and adapt to
42
+ the current color mode.
43
+
40
44
  ```tsx
41
- <Button priority="primary" tone="#ff1493" label="Pinkify" />
45
+ <Button priority="primary" tone="#ff1493" label="Pinkify" />
42
46
  <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
+ <Button priority="tertiary" tone="hsl(328 100% 54%)" label="Pinkify" />
48
+ <Button priority="primary" tone="mediumaquamarine" label="Mintify" />
49
+ <Button priority="secondary" tone="rgb(102 205 170)" label="Mintify" />
50
+ <Button priority="tertiary" tone="lch(75.7% 39.2 167.8)" label="Mintify" />
47
51
  ```
48
52
 
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
53
  ## Size
70
54
 
55
+ Buttons come in `small`, `medium`, and `large`. `medium` is the default.
56
+
71
57
  ```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" />
58
+ <Button tone="brand" size="small" label="Small" />
59
+ <Button tone="brand" size="medium" label="Medium" />
60
+ <Button tone="brand" size="large" label="Large" />
75
61
  ```
76
62
 
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
63
  ### Paddingless
111
64
 
65
+ Use `paddingless` with a quaternary button when an action should sit flush with
66
+ surrounding text.
67
+
112
68
  ```tsx
113
69
  If you're familiar with the basics, click
114
70
  <Button tone="critical" priority="quaternary" paddingless label="here" />
115
- to skip ahead to the live demo.
71
+ to skip the live demo.
116
72
  ```
117
73
 
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
74
  ## Round
123
75
 
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.
76
+ Set `round` for a pill or circular icon button. Pass a number or CSS length for
77
+ a custom corner radius.
128
78
 
129
79
  ```tsx
130
80
  <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
81
+ <Button round tone="brand" icon="heart" aria-label="Like" />
82
+ <Button round={10} tone="brand" label="Custom 10px" />
133
83
  ```
134
84
 
135
85
  ## Icons
136
86
 
87
+ Use `icon` for a leading icon and `iconTrailing` for a trailing icon. See
88
+ [Icon](./icon.md) for available shapes.
89
+
137
90
  ```tsx
138
91
  <Button tone="brand" priority="primary" icon="check" label="Confirm" />
139
92
  <Button tone="brand" iconTrailing="external-link" label="Read the docs" />
140
93
  <Button tone="brand" priority="tertiary" icon="filter" iconTrailing="chevron-down" label="Filter" />
141
94
  ```
142
95
 
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
96
  ### Icon-only
149
97
 
98
+ Omit the label for an icon-only button. Pass a clear `aria-label` when the icon
99
+ name does not describe the action well.
100
+
150
101
  ```tsx
151
- <Button tone="neutral" icon="dots-vertical" size="small" />
152
- <Button tone="critical" icon="trash" />
153
- <Button tone="brand" icon="check" size="large" />
102
+ <Button icon="dots-vertical" size="small" aria-label="More options" />
103
+ <Button tone="critical" icon="trash" aria-label="Delete" />
104
+ <Button round tone="brand" icon="check" size="large" aria-label="Confirm" />
154
105
  ```
155
106
 
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
107
  ### Children
167
108
 
109
+ Use children for inline content such as keyboard hints, badges, or counters.
110
+ Text and number children receive the same label styling as the `label` prop.
111
+
168
112
  ```tsx
169
113
  <Button tone="brand" label="Save" icon="check" aria-keyshortcuts="Meta+s">
170
- <kbd aria-hidden="true" style={{ font: 'inherit', fontVariationSettings: 'inherit', opacity: 0.7 }}>⌘S</kbd>
114
+ <kbd aria-hidden="true">⌘S</kbd>
171
115
  </Button>
172
116
  ```
173
117
 
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
118
  ## Underline
188
119
 
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
- <Button tone="brand" priority="quaternary" underline="solid" underlineOnHover label="Hover only" />
194
- ```
195
-
196
- When the button has a [link mode](#link-mode), or you simply want an
197
- underline stylistically, it supports three styles — `solid`, `dashed`,
198
- `dotted` (only valid on `priority="tertiary"` / `"quaternary"`). Set
199
- `underlineOnHover` to keep that underline hidden until pointer hover.
120
+ Tertiary and quaternary buttons support solid, dashed, and dotted underlines.
121
+ Use `underlineOnHover` to show the underline only on hover.
200
122
 
201
123
  ## States
202
124
 
125
+ `loading` and `disabled` prevent activation. `selected` communicates a toggled
126
+ or pressed state.
127
+
203
128
  ```tsx
204
129
  <Button tone="brand" loading label="Submitting" />
205
130
  <Button tone="brand" disabled label="Locked" />
206
131
  <Button tone="brand" selected label="Toggled on" />
207
- <Button tone="brand" loading disabled label="Critical" />
208
132
  ```
209
133
 
210
- - **`loading`** — diagonal stripe overlay slides across the button.
211
- Stripe color follows `currentColor`, so it tracks the tone. Blocks
212
- clicks via `pointer-events: none`, and (like `disabled`) removes the
213
- button from the tab order so Enter/Space can't fire it mid-flight.
214
- - **`disabled`** — locks the colors to the disabled palette, sets
215
- `pointer-events: none`, and removes the button from the tab order.
216
- Beats inline `--button-bg` overrides.
217
- - **`selected`** — toggled-on / pressed visual; shares the active
218
- state's look. Useful for filter chips and icon toggles.
219
-
220
134
  ## ButtonCopy
221
135
 
222
- `ButtonCopy` is a copy button — a `Button` preset for copy-to-clipboard. It
223
- composes a plain `<Button>` with a slotted `<a-copy>` element that writes to the
224
- clipboard when the button is activated. A button with a copy glyph changes the
225
- glyph to a check and retones to `success` for ~2s (✕ / `critical` on failure).
226
- Set `copy` for a literal string, or drop the `label` for an icon-only copy
227
- button.
136
+ `ButtonCopy` is a `Button` preset for copy-to-clipboard. Its copy glyph changes
137
+ to a check on success or an x on failure, then returns to its resting state.
138
+ `onCopied(ok)` reports the result. Omit the label for an icon-only copy button.
139
+
140
+ The preset composes a regular `Button` with `<a-copy>`. Use `<a-copy>` directly
141
+ when you need the same behavior in another control.
228
142
 
229
143
  ```tsx
230
144
  <ButtonCopy copy="npm i @antadesign/anta" label="Copy install command" />
231
- <ButtonCopy copy="https://anta.design" priority="tertiary" /> // icon-only
232
-
233
- <ButtonCopy
234
- copy="npm i @antadesign/anta"
235
- label="Copy install command"
236
- iconPlacement="none"
237
- copiedLabel="Copied to clipboard"
238
- />
145
+ <ButtonCopy copy="https://anta.design" priority="tertiary" />
239
146
  ```
240
147
 
241
- The write lives in the standalone `<a-copy>` element, so the button stays a plain
242
- button. `ButtonCopy` slots it in for you; to build the same by hand, drop an
243
- `<a-copy copy=…>` into a `<Button>` or `<a-button>`. `onCopied(ok)` fires after
244
- each attempt. Override the resting glyph with `icon`; the check / ✕ swap in only
245
- during the feedback window.
246
-
247
- With `iconPlacement="none"`, the button stays unchanged. A successful pointer
248
- copy shows `✓ Copied` beside the pointer. A keyboard copy shows it at the
249
- button's inline start. Use `copiedLabel` to change the text.
250
-
251
148
  ### Icon placement
252
149
 
253
- `iconPlacement` sets where the copy glyph sits: `leading` (the default) or
254
- `trailing`. Pass `none` to omit the glyph. A successful copy then shows the
255
- confirmation label without changing the button. Set `copiedLabel` to change the
256
- label text.
150
+ Set `iconPlacement` to `leading`, `trailing`, or `none`. With `none`, the button
151
+ does not change. Successful pointer activation shows a confirmation beside the
152
+ pointer; keyboard activation shows it at the button's inline start. Use
153
+ `copiedLabel` to change the message.
257
154
 
258
155
  ```tsx
259
- <ButtonCopy copy={value} label="Leading" /> // default
156
+ <ButtonCopy copy={value} label="Leading" />
260
157
  <ButtonCopy copy={value} label="Trailing" iconPlacement="trailing" />
261
158
  <ButtonCopy copy={value} label="No icon" iconPlacement="none" />
262
159
  ```
263
160
 
264
161
  ### Copy a DOM node
265
162
 
266
- Pass `copyNode` to copy a rendered region as rich text (`text/html`) plus plain
267
- text, instead of a string. Bare `copyNode` copies the nearest ancestor marked
268
- `data-copy-source`; a string is a CSS selector for an ancestor region. The copy
269
- button itself is stripped from what's copied.
163
+ Use `copyNode` to copy a rendered region as `text/html` and plain text. Bare
164
+ `copyNode` finds the nearest `data-copy-source`; a string selects an ancestor.
165
+ The copy control itself is omitted from the copied content.
270
166
 
271
167
  ```tsx
272
168
  <div data-copy-source>
273
169
  <Text>The quick brown fox jumps over the lazy dog.</Text>
274
- <ButtonCopy copyNode label="Copy card" size="small" priority="tertiary" />
170
+ <ButtonCopy copyNode label="Copy card" />
275
171
  </div>
276
172
  ```
277
173
 
278
- Copying rich text lets a paste target keep formatting (bold, links, list
279
- structure) while a plain-text target still gets clean text. Paste the copied
280
- card into a rich editor and a plain one to see both.
281
-
282
174
  ### Copy the page URL
283
175
 
284
- `copyUrl` copies the current page URL (`location.href`) — no `copy` value needed.
285
- `copyWithUrl` prefixes a `copy` string with `// URL: <href>`, so a copied snippet
286
- carries a link back to where it came from.
176
+ Use `copyUrl` to copy `location.href`. `copyWithUrl` prefixes copied text with
177
+ `// URL: <current page URL>`.
287
178
 
288
179
  ```tsx
289
180
  <ButtonCopy copyUrl label="Copy link" />
290
- <ButtonCopy copy={snippet} copyWithUrl label="Copy snippet" /> {/* snippet + source URL */}
181
+ <ButtonCopy copy={snippet} copyWithUrl label="Copy snippet" />
291
182
  ```
292
183
 
293
184
  ### Copy dynamic text
294
185
 
295
- `copy` is a controlled string. Start it at `''`, then set the new text in
296
- `onCopyRequest`. The next render updates `copy`, and activation writes that value.
297
- `onCopyRequest` does not return text. It does not add a polling loop.
186
+ `copy` is controlled. For text generated on demand, initialize it to `''` and
187
+ update it in `onCopyRequest`. The callback fires on pointerdown or Enter/Space
188
+ keydown, before activation writes the current `copy` value. Its return value is
189
+ ignored.
298
190
 
299
191
  ```tsx
300
192
  const [report, setReport] = useState('')
@@ -306,24 +198,18 @@ const [report, setReport] = useState('')
306
198
  />
307
199
  ```
308
200
 
309
- `onCopyRequest` fires on pointerdown or Enter/Space keydown, before the click or
310
- menu selection that writes to the clipboard.
311
-
312
201
  #### Why `copy` is controlled
313
202
 
314
- In a usual React or Preact app, an `onClick` handler can calculate a string and
315
- call the Clipboard API itself. `ButtonCopy` also supports applications rendered
316
- from a worker. The DOM copy control and Clipboard API are on the browser UI
317
- thread, but the application's JSX code can be elsewhere. A function cannot cross
318
- that boundary as a callable reference.
203
+ Anta supports applications whose JSX renderer runs in a worker. The DOM copy
204
+ control and Clipboard API remain on the browser's UI thread, and a callback
205
+ cannot cross that boundary as a callable reference.
319
206
 
320
- The request tells the application to calculate the text. Its state update makes
321
- the renderer send the string as the `copy` attribute. The control then writes that
322
- attribute during activation, when the browser permits clipboard access. A
323
- `lazyCopy: () => string` prop could only hide this same state update inside the
324
- wrapper. A generic return value would also need a defined clipboard format and
325
- serialization; Anta supports text (`copy`), a DOM region (`copyNode`), and the
326
- current URL (`copyUrl`).
207
+ `onCopyRequest` asks the application to calculate the text. The resulting state
208
+ update sends a serializable `copy` string to the DOM control, which writes it
209
+ during the user activation required by the Clipboard API. A `lazyCopy` callback
210
+ would only hide this state update in a main-thread wrapper and would not work
211
+ across renderers. Separate `copy`, `copyNode`, and `copyUrl` inputs also keep the
212
+ clipboard format explicit.
327
213
 
328
214
  ### Props
329
215
 
@@ -363,105 +249,64 @@ current URL (`copyUrl`).
363
249
 
364
250
  ## Link mode
365
251
 
366
- ```tsx
367
- <Button tone="brand" href="/docs" target="_blank" label="Read the docs" />
368
- ```
369
-
370
- Setting `href` switches the rendered tag from `<a-button>` to
371
- `<a role="button" data-anta>`. Styling is identical — both selectors share
372
- the same CSS rules. `<Button>` adds the `data-anta` marker for you.
373
-
374
- **`href` is optional**, so a conditional URL needs no branching. Pass
375
- `href={maybeUrl}` (a `string | undefined`): a definite string links out, an
376
- absent / `undefined` href renders a plain `<a-button>`. Pair it with `disabled`
377
- for a link that's live when the URL exists and inert otherwise — no two-render
378
- split or conditional spread:
252
+ Set `href` to render a link with button styling. An absent or `undefined` URL
253
+ keeps the regular button, which makes conditional links straightforward.
379
254
 
380
255
  ```tsx
381
- // Links out when there's a URL; a plain, disabled button when there isn't.
382
- <Button href={reportUrl} disabled={!reportUrl} iconTrailing="external-link" label="View report" />
256
+ <Button href="/docs" target="_blank" label="Read the docs" />
257
+ <Button
258
+ href={reportUrl}
259
+ disabled={!reportUrl}
260
+ iconTrailing="external-link"
261
+ label="View report"
262
+ />
383
263
  ```
384
264
 
385
265
  ### Routing libraries
386
266
 
387
- Anta's CSS targets `a-button, a[role="button"][data-anta]` — an anchor (`<a>`)
388
- with `role="button"`, the `data-anta` opt-in marker, and the right attributes
389
- gets the styling. That means anta doesn't need an `as` / `asChild` /
390
- `component` prop or per-framework integrations. Compose your own thin wrapper
391
- around your library's `Link` component — which renders an `<a>`, so adding
392
- `role="button"` and `data-anta` matches the selector.
393
-
394
- The `data-anta` marker is required because `role="button"` is a *generic* ARIA
395
- role that other widgets emit too — gating on the marker keeps anta from
396
- restyling anchors it doesn't own (a third-party menu, an embedded editor's
397
- toolbar, etc.).
267
+ For client-side routing, wrap your router's link and add `role="button"` and
268
+ `data-anta` to its anchor output.
398
269
 
399
270
  ```tsx
400
- // LinkButton.tsx — anta-styled link for client-side routing
401
- import { Link, type LinkProps } from 'react-router-dom'
402
- import type { IconShape } from '@antadesign/anta'
403
-
404
- type LinkButtonProps = LinkProps & {
405
- tone?: 'neutral' | 'brand' | 'critical' | 'info' | 'success' | 'warning'
406
- priority?: 'primary' | 'secondary' | 'tertiary' | 'quaternary'
407
- size?: 'small' | 'medium' | 'large'
408
- icon?: IconShape
409
- iconTrailing?: IconShape
410
- label?: string
411
- }
271
+ import { Link } from 'react-router-dom'
412
272
 
413
- export const LinkButton = ({
414
- tone, priority, size, icon, iconTrailing, label, children, ...rest
415
- }: LinkButtonProps) => (
416
- <Link role="button" data-anta tone={tone} priority={priority} size={size} {...rest}>
417
- {icon && <a-icon shape={icon} aria-hidden="true" />}
418
- {label != null && <a-button-label>{label}</a-button-label>}
419
- {children}
420
- {iconTrailing && <a-icon shape={iconTrailing} aria-hidden="true" />}
273
+ export const LinkButton = ({ label, ...props }) => (
274
+ <Link role="button" data-anta {...props}>
275
+ <a-button-label>{label}</a-button-label>
421
276
  </Link>
422
277
  )
423
278
  ```
424
279
 
425
- Usage:
426
-
427
- ```tsx
428
- <LinkButton to="/dashboard" tone="brand" label="Dashboard" />
429
- <LinkButton to="/docs" priority="tertiary" iconTrailing="external-link" label="Docs" />
430
- ```
431
-
432
- This approach would work for Next.js `<Link>`, TanStack Router, or any
433
- other routing library.
434
-
435
280
  ## Special events
436
281
 
437
- Beyond a plain click, a `Button` can drive a native form or emit your own
438
- event:
282
+ Beyond a plain click, a button can submit or reset a native form and dispatch a
283
+ named custom event.
439
284
 
440
285
  ### Form submission
441
286
 
442
- For non-anchor buttons, `type="submit"` and `type="reset"` integrate
443
- with native forms. `type="submit"` calls `form.requestSubmit()` and also
444
- dispatches a `submitdetailed` event on the form with
445
- `{ formData, submitter: { tag, attrs } }` in `detail` — handy for
446
- analytics or multi-button forms.
287
+ For non-link buttons, `type="submit"` and `type="reset"` integrate with native
288
+ forms. The `form` prop associates a button with a form elsewhere on the page.
289
+ Submitting calls `form.requestSubmit()`, so the form's validation and `submit`
290
+ event still run. It also dispatches `submitdetailed` on the form with
291
+ `{ formData, submitter: { tag, attrs } }` in `detail`, which can identify the
292
+ trigger in analytics or multi-button forms.
447
293
 
448
294
  ```tsx
449
295
  <form id="signup">
450
296
  <Button tone="brand" type="submit" label="Sign up" />
451
- <Button tone="brand" priority="tertiary" type="reset" label="Clear" />
297
+ <Button priority="tertiary" type="reset" label="Clear" />
452
298
  </form>
453
- {/* Associate with a form by id when the button is outside */}
454
- <Button tone="neutral" type="submit" form="signup" label="Submit from outside" />
299
+ <Button type="submit" form="signup" label="Submit from outside" />
455
300
  ```
456
301
 
457
302
  ### Custom click events
458
303
 
459
- `data-custom-event="<name>"` makes the button dispatch a bubbling
460
- `CustomEvent("<name>")` on click. Use it to instrument analytics
461
- without taking ownership of `onClick`.
304
+ Set `data-custom-event="<name>"` to dispatch a bubbling `CustomEvent("<name>")`
305
+ on click. This is useful for declarative actions or analytics that should not
306
+ take ownership of `onClick`.
462
307
 
463
308
  ```tsx
464
- <Button tone="brand" label="Save" data-custom-event="save-clicked" />
309
+ <Button label="Save" data-custom-event="save-clicked" />
465
310
  ```
466
311
 
467
312
  ## Component props
@@ -494,9 +339,7 @@ without taking ownership of `onClick`.
494
339
 
495
340
  ## Web Component
496
341
 
497
- Use the web component directly when you are not using React or Preact and a native control does not fit.
498
-
499
- Set `role="button"` and `tabindex="0"` when authoring `<a-button>` directly.
342
+ When using `<a-button>` directly, add its button role and keyboard tab stop.
500
343
 
501
344
  ```html
502
345
  <a-button role="button" tabindex="0" priority="primary">
@@ -505,124 +348,27 @@ Set `role="button"` and `tabindex="0"` when authoring `<a-button>` directly.
505
348
  </a-button>
506
349
  ```
507
350
 
508
- ### Native HTML button
509
-
510
- The styling attaches to three element shapes, so you can adopt the button look on
511
- markup you already own instead of routing through `<Button>`. `data-anta` is the
512
- opt-in marker — it stops Anta restyling a `role="button"` element it does not own
513
- (a third-party menu, an embedded editor's toolbar):
514
-
515
- | Element | For |
516
- | --- | --- |
517
- | `<a-button>` | the component's own tag, always styled — what `<Button>` renders |
518
- | `<a role="button" data-anta>` | a link — what `<Button href>` renders (see [Link mode](#link-mode)) |
519
- | `<button data-anta>` | a native form control — keeps native form submission, `disabled`, and Enter / Space activation |
520
-
521
- `tone` / `priority` / `size` are plain attributes on the element (the `<Button>`
522
- wrapper sets them for you). A native `<button>` also carries its own default
523
- `type="submit"`, so inside a `<form>` it submits without extra wiring:
351
+ Use `<button data-anta>` when you need native button and form behavior.
524
352
 
525
353
  ```html
526
- <!-- A real <button>: submits forms and toggles `disabled` natively. -->
527
354
  <button data-anta tone="brand" priority="primary">Save changes</button>
528
- <button data-anta priority="secondary">Cancel</button>
529
- <button data-anta tone="critical" priority="tertiary">Delete</button>
355
+ <button data-anta disabled>Unavailable</button>
530
356
  ```
531
357
 
532
358
  ## Styling
533
359
 
534
- Reach for the props first: **`tone`** sets the color (any CSS color for a custom
535
- tone — it derives the whole tone × priority × state curve in oklch), **`priority`**
536
- the emphasis, **`size`** the dimensions. The focus ring is the global
537
- [`--focus-ring`](../colors.md#focus-ring).
538
-
539
- ```tsx
540
- <Button tone="#e0457b" priority="primary" label="Custom" />
541
- ```
542
-
543
- For anything beyond the props, `<a-button>` is light-DOM — restyle it with plain
544
- CSS (an un-layered rule beats `@layer anta` without `!important`). Set
545
- `background-color` / `color` **per state**, plus full type control (size,
546
- line-height, letter-spacing, `font-feature-settings`), padding, radius, even a
547
- frosted `backdrop-filter` — no need to touch a single token. **`light-dark()`** works
548
- too (Anta sets `color-scheme` from its theme toggle), so colors adapt to dark mode
549
- with no `.dark` selector. The classes below are just for the demos:
360
+ Use `tone`, `priority`, `size`, and `round` before adding custom CSS. For a
361
+ distinct treatment, target a class on the button and cover its interaction
362
+ states.
550
363
 
551
364
  ```css
552
- /* First — inverted pill: a theme-flipping gradient (near-black in light,
553
- near-white in dark) via light-dark() in the gradient stops. Anta sets
554
- color-scheme from its .dark toggle, so it adapts with no .dark selector. */
555
365
  a-button.checkout {
556
- background: linear-gradient(140deg, light-dark(#26262b, #fff), light-dark(#000, #dcdce0));
557
- color: light-dark(#fff, #0b0b0c);
558
- font-size: 20px;
559
- line-height: 1.2em;
560
- padding: 12px 26px;
561
- border-radius: 999px;
562
- }
563
- a-button.checkout:hover { background: linear-gradient(140deg, light-dark(#3a3a42, #fff), light-dark(#161618, #cacace)); }
564
- a-button.checkout:active { background: linear-gradient(140deg, light-dark(#000, #ececf0), light-dark(#1a1a1c, #fff)); }
565
- a-button.checkout a-button-label { /* customized label */
566
- text-transform: uppercase;
567
- letter-spacing: 0.14em;
568
- font-weight: 600;
569
- }
570
-
571
- /* Second — liquid glass: frost + translucent tint + a diagonal specular sheen,
572
- a top/bottom bevel, and a soft lift. Sits on a colorful backdrop so it reads. */
573
- a-button.glass {
574
366
  color: #fff;
575
- background-color: rgba(255, 255, 255, 0.12);
576
- background-image: linear-gradient(135deg, rgba(255,255,255,0.55), rgba(255,255,255,0.06) 45%, transparent 65%);
577
- border: 1px solid rgba(255, 255, 255, 0.4);
367
+ background: #673de6;
578
368
  border-radius: 999px;
579
- backdrop-filter: blur(8px) saturate(1.8) brightness(1.06);
580
- box-shadow:
581
- inset 0 1px 1px rgba(255,255,255,0.65), /* top bevel */
582
- inset 0 -2px 3px rgba(255,255,255,0.18),
583
- 0 8px 24px rgba(0,0,0,0.22); /* lift */
584
- font-size: 20px; line-height: 1.2em; padding: 12px 26px;
585
- }
586
- a-button.glass:hover { background-color: rgba(255, 255, 255, 0.22); }
587
- a-button.glass:active { background-color: rgba(255, 255, 255, 0.08); }
588
- a-button.glass a-button-label { /* beautified label — gradient text */
589
- font-weight: 600;
590
- background: linear-gradient(180deg, #fff, #cfe0ff);
591
- background-clip: text;
592
- color: transparent;
593
- filter: drop-shadow(0 1px 1px rgba(0,0,0,0.3));
369
+ padding-inline: 20px;
594
370
  }
595
- ```
596
371
 
597
- ```tsx
598
- <Button className="custom" tone="brand" loading label="Saving changes" />
599
-
600
- <style>{`
601
- /* Keep loading's blocked interaction; replace only its stripe overlay. */
602
- a-button.custom[loading]::before {
603
- inset: -200%;
604
- border-radius: 100%;
605
- background: conic-gradient(
606
- from 0deg,
607
- color-mix(in oklch, currentColor 0%, transparent),
608
- color-mix(in oklch, currentColor 10%, transparent)
609
- );
610
- opacity: 1;
611
- filter: none;
612
- animation: button-loader-field-spin 1s linear infinite;
613
- animation-delay: -9999s;
614
- }
615
-
616
- @keyframes button-loader-field-spin {
617
- to { transform: rotate(1turn); }
618
- }
619
- `}</style>
372
+ a-button.checkout:hover { background: #5931c4; }
373
+ a-button.checkout:active { background: #48269f; }
620
374
  ```
621
-
622
- **Loader field.** `loading` still blocks pointer and keyboard activation. This
623
- only replaces its default stripe overlay with the Loader’s conic field, scaled
624
- beyond the button so it can rotate behind the label without exposing an edge.
625
-
626
- Don't reach for the resolved `--button-fg` / `--button-bg`: they're recomputed per
627
- state, so setting one only catches a single state — restyle per state as above, or
628
- use `tone` for a full custom-color curve.