@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.
- package/README.md +38 -242
- package/dist/components/Input.d.ts +4 -1
- package/dist/components/Input.js +2 -0
- 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/Menu.d.ts +5 -1
- package/dist/components/Menu.js +3 -2
- package/dist/components/Select.d.ts +1 -0
- package/dist/components/Select.js +9 -13
- package/dist/components/SelectFaceted.d.ts +1 -0
- package/dist/components/SelectFaceted.js +29 -23
- package/dist/components/select-parts.css +1 -0
- package/dist/elements/a-input.css +1 -1
- package/dist/elements/a-menu.js +12 -0
- 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/general_types.d.ts +2 -0
- package/dist/jsx-runtime.d.ts +9 -0
- package/docs/accessibility.md +34 -0
- package/docs/changelog.md +551 -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 +322 -0
- package/docs/components/checkbox.md +297 -0
- package/docs/components/dialog.md +314 -0
- package/docs/components/expander.md +529 -0
- package/docs/components/icon.md +155 -0
- package/docs/components/input-autocomplete.md +101 -0
- package/docs/components/input-date.md +406 -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 +447 -0
- package/docs/components/progress.md +182 -0
- package/docs/components/radio.md +369 -0
- package/docs/components/select-faceted.md +456 -0
- package/docs/components/select.md +931 -0
- package/docs/components/slider.md +142 -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 +22 -0
- package/docs/index.md +52 -0
- package/docs/install-config.md +240 -0
- package/docs/normalization.md +105 -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,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.
|