@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,148 @@
1
+ # Text
2
+
3
+ A block-level text container that scopes the color hierarchy of its
4
+ contents. `priority` sets the emphasis from `primary` (`--text-1`) to
5
+ `quaternary` (`--text-4`). It defaults to `secondary` (`--text-2`), so body
6
+ text reads a step softer than the strongest foreground; pass `primary`
7
+ for emphasis. `tone` adds a tint (`brand`, `success`, `critical`,
8
+ `warning`, `info`, or any literal CSS color for a one-off custom tone); the
9
+ default `neutral` leaves it untinted. `inline` renders it `inline-block` for use
10
+ mid-sentence.
11
+
12
+ Links nested inside `<Text>` take Anta's prose-link styling from the
13
+ design system, with no per-page overrides to maintain.
14
+
15
+ ## Link behavior
16
+
17
+ Links inside `<Text>` follow a priority-aware hierarchy that keeps
18
+ emphasis consistent with the surrounding text:
19
+
20
+ | Priority | Link color (default) | Link color (hover) |
21
+ |---------------|-----------------------------------|-------------------------------|
22
+ | `primary` | `--link-color` (brand blue) | `--link-color-hover` |
23
+ | `secondary` | `--link-color` | `--link-color-hover` |
24
+ | `tertiary` | `currentColor` (= `--text-3`) | `--text-2` |
25
+ | `quaternary` | `currentColor` (= `--text-4`) | `--text-3` |
26
+
27
+ The underline is a 0.5px hairline at 75% alpha by default and bumps to
28
+ 1px / 100% alpha on hover. On `:active` it returns to the 0.5px / 75%
29
+ resting state.
30
+
31
+ For tinted text (`tone="brand|success|critical|warning|info"`) links are
32
+ always muted regardless of priority: they take `currentColor` and step up
33
+ to the next-stronger level of the same tint on hover. At
34
+ `priority="primary"` there is no level above, so hovering only brings the
35
+ underline to full alpha.
36
+
37
+ ## Demo
38
+
39
+ ## Truncation & expansion
40
+
41
+ `truncate` takes `true` (or `1`) for a single-line ellipsis, any integer
42
+ ≥ 2 for multi-line clamping, and `0` or a negative value for no
43
+ truncation. Both modes use `-webkit-line-clamp` inside
44
+ `display: -webkit-box`, supported in every major browser despite the
45
+ prefix (Firefox 68+, Chrome, Safari, Edge). The host gets `min-width: 0`
46
+ so truncation works inside flex and grid containers.
47
+
48
+ ```tsx
49
+ {/* Single line */}
50
+ <Text truncate>{longSentence}</Text>
51
+
52
+ {/* Multi-line: pass the line count */}
53
+ <Text truncate={2}>{longSentence}</Text>
54
+ <Text truncate={3}>{longSentence}</Text>
55
+ ```
56
+
57
+ Pair `expandable` with `truncate` to let the reader reveal the full text.
58
+ The fade and chevron appear only when the clamped content overflows; text
59
+ that fits gets neither. Single-line truncation fades on the right edge,
60
+ multi-line over the last line. The chevron is a real `<button>` carrying
61
+ `aria-expanded`: click it, or focus it and press Enter, to drop the clamp.
62
+ `expandable` takes effect only with `truncate`.
63
+
64
+ Expanding is one-way by default, so the control is removed once the text
65
+ is revealed. Add `collapsible` for a two-way toggle: the chevron stays
66
+ visible while expanded (rotated up) and flips its label between "Show
67
+ more" and "Show less". `collapsible` takes effect only with `expandable`.
68
+
69
+ ```tsx
70
+ {/* One-way: the chevron is removed once expanded */}
71
+ <Text truncate expandable>{longSentence}</Text>
72
+ <Text truncate={3} expandable>{longSentence}</Text>
73
+
74
+ {/* Two-way: the reader can collapse it back */}
75
+ <Text truncate={3} expandable collapsible>{longSentence}</Text>
76
+ ```
77
+
78
+ ### Props
79
+
80
+ | Prop | Type | Default | Description |
81
+ |------|------|---------|-------------|
82
+ | `collapsible?` | boolean | — | Let the reader collapse back after expanding: the chevron becomes a
83
+ "Show more" / "Show less" toggle that stays visible while expanded.
84
+ Only takes effect together with `expandable`. |
85
+ | `expandable?` | boolean | — | Show a fade hint and chevron over the truncated text and let the user
86
+ expand it by clicking the chevron region or pressing Enter while the
87
+ chevron has keyboard focus. Only takes effect together with `truncate`.
88
+ On its own, expanding is **one-way** — the control is removed once
89
+ expanded; add `collapsible` for a two-way toggle. |
90
+ | `inline?` | boolean | — | Render as inline-block instead of the default block element. |
91
+ | `priority?` | 'primary' \| 'secondary' \| 'tertiary' \| 'quaternary' | secondary | Visual priority. Maps to text-1..text-4 (`primary` = text-1, the
92
+ strongest). The default is `secondary` (text-2) — body text reads a
93
+ step softer than the strongest foreground; pass `primary` for emphasis. |
94
+ | `size?` | 'small' \| 'medium' \| 'large' | medium | Type scale. `small` = 13/16, `medium` = 15/20, `large` = 17/24. |
95
+ | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | neutral | Color tint. `neutral` (the default) is the untinted `--text-{N}` scale; a
96
+ named tone applies the matching `--text-{N}-{tone}` palette. Any literal CSS
97
+ color (`'#ff1493'`, `'rebeccapurple'`) is a one-off custom tone — its hue is
98
+ kept while lightness/chroma are pinned per priority in oklch. |
99
+ | `truncate?` | boolean \| number | — | Truncate with a trailing ellipsis. `true` (or `1`) clamps to a
100
+ single line; any integer ≥ 2 clamps to that many lines; `0` or a
101
+ negative value means no truncation. Uses the `-webkit-line-clamp`
102
+ technique, supported in all major browsers (Firefox 68+, Chrome,
103
+ Safari, Edge). |
104
+
105
+ 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.
106
+
107
+ Use `<a-text>` directly for a block that carries the text priority and tone.
108
+
109
+ ```html
110
+ <a-text priority="secondary">
111
+ Read the <a href="/colors/">color guidance</a>.
112
+ </a-text>
113
+ ```
114
+
115
+ Colour comes from **`priority`** (the neutral text level) and **`tone`** (the tint);
116
+ `size` and the type props set the rest. For a named tone, `Text` consumes the
117
+ global **text role tokens** (`--text-1` … `--text-5`, the `-{tone}` tints,
118
+ `--link-color`), so retuning those once on `:root` / `.dark` shifts text colour
119
+ everywhere (see [Colors](../colors.md)). A custom `tone` (any CSS color) is derived in
120
+ oklch from **`--text-tone-source`** — the same one-off knob as `Button` / `Tag`.
121
+
122
+ ```tsx
123
+ <Text priority="secondary" tone="brand">Tinted secondary text</Text>
124
+ <Text tone="#0d9488">Custom-tone body copy</Text>
125
+ ```
126
+
127
+ `<a-text>` is light-DOM, so one-offs are plain CSS on the element or its prose
128
+ links. The `.fancy` class names the demo example:
129
+
130
+ ```css
131
+ /* teal body text via light-dark() (deep in light, bright in dark; Anta drives
132
+ color-scheme from its theme toggle), with wider tracking */
133
+ a-text.fancy {
134
+ color: light-dark(#0f6e5f, #5fd0bb);
135
+ font-size: 16px;
136
+ line-height: 1.7;
137
+ letter-spacing: 0.06em;
138
+ }
139
+ a-text.fancy a { text-decoration-thickness: 2px; text-underline-offset: 3px; }
140
+ ```
141
+
142
+ ## Example
143
+
144
+ ```tsx
145
+ import { Text } from '@antadesign/anta'
146
+
147
+ <Text>The quick brown fox jumps over the lazy dog with a <a href="#">link inside</a> the sentence, followed by enough additional prose to push the paragraph well past a single line. Curabitur sodales ligula in libero, and Donec a tincidunt elit. Mauris vehicula, est nec porta cursus, tellus eros vestibulum lectus, vitae luctus turpis enim sed nibh.</Text>
148
+ ```
@@ -0,0 +1,146 @@
1
+ # Title
2
+
3
+ A margin-free heading with six `level`s. `level` sets the type scale and the
4
+ `aria-level` exposed to assistive technology. `priority` runs from `primary`
5
+ through `quaternary` (`--text-1` through `--text-4`); `tone` accepts `neutral`
6
+ (the default), a named tone, or a literal CSS color.
7
+
8
+ Use a raw `<h1>`–`<h6>` for document-outline or SEO semantics. Anta's reset
9
+ gives those headings the same type scale and document margins. Use `<Title>` in
10
+ component layouts when you need tone, priority, or parent-controlled spacing.
11
+
12
+ > **`<Title>` has no margins.** Space it with the parent’s `gap` or padding. For
13
+ > a one-off, add `marginBlock` to the title itself.
14
+
15
+ ## Levels
16
+
17
+ The parent supplies the `16px` gap between these titles.
18
+
19
+ Level 1: Workspace settings
20
+ Level 2: Recent activity
21
+ Level 3: Security alerts
22
+ Level 4: Sign-in policy
23
+ Level 5: Session timeout
24
+ Level 6: Updated five minutes ago
25
+
26
+ ## Inline content
27
+
28
+ Pass icons and inline elements as children. Icons are optically aligned with
29
+ the title text.
30
+
31
+ ```tsx
32
+ <Title level={3}>
33
+ <Icon shape="book-open" /> Saved items{' '}
34
+ <span style={{ color: 'var(--text-1-brand)' }}>(3)</span>
35
+ </Title>
36
+ ```
37
+
38
+ Use a span for a small inline accent. Don't nest `<Text>` or `<a-text>` inside a
39
+ title: they are block containers, while heading content should remain inline.
40
+
41
+ ## Level reference
42
+
43
+ | level | font-size | line-height |
44
+ |------:|----------:|------------:|
45
+ | 1 | 28px | 32px |
46
+ | 2 | 24px | 28px |
47
+ | 3 | 20px | 24px |
48
+ | 4 | 17px | 20px |
49
+ | 5 | 15px | 20px |
50
+ | 6 | 13px | 16px |
51
+
52
+ ## Tone × priority
53
+
54
+ The four priorities are shown across every named tone at `level={3}`. Omit
55
+ `tone` for the neutral scale. The grid scrolls horizontally on narrow screens.
56
+
57
+
58
+ Neutral primary
59
+ Secondary
60
+ Tertiary
61
+ Quaternary
62
+
63
+ Brand primary
64
+ Secondary
65
+ Tertiary
66
+ Quaternary
67
+
68
+ Success primary
69
+ Secondary
70
+ Tertiary
71
+ Quaternary
72
+
73
+ Critical primary
74
+ Secondary
75
+ Tertiary
76
+ Quaternary
77
+
78
+ Warning primary
79
+ Secondary
80
+ Tertiary
81
+ Quaternary
82
+
83
+ Info primary
84
+ Secondary
85
+ Tertiary
86
+ Quaternary
87
+
88
+
89
+ ## Accessibility
90
+
91
+ `<Title>` renders an `<a-title>` tag with `role="heading"` and
92
+ `aria-level={level}`. It is not a real `<h*>`. Use a raw heading when the HTML
93
+ document outline or SEO needs heading elements; the reset applies the same type
94
+ scale.
95
+
96
+ ### Props
97
+
98
+ | Prop | Type | Default | Description |
99
+ |------|------|---------|-------------|
100
+ | `level?` | 1 \| 2 \| 3 \| 4 \| 5 \| 6 | 2 | Heading level, 1-6. Drives font-size, line-height, and vertical
101
+ rhythm. Also surfaced to assistive tech via `aria-level`
102
+ (h1 is typically reserved for the page title). |
103
+ | `priority?` | 'primary' \| 'secondary' \| 'tertiary' \| 'quaternary' | primary | Visual priority. Maps to text-1..text-4 (`primary` = text-1). |
104
+ | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | neutral | Color tint. `neutral` (the default) is the untinted `--text-{N}` scale; a
105
+ named tone applies the matching `--text-{N}-{tone}` palette. Any literal CSS
106
+ color (`'#ff1493'`, `'rebeccapurple'`) is a one-off custom tone — its hue is
107
+ kept while lightness/chroma are pinned per priority in oklch. |
108
+
109
+ 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.
110
+
111
+ Set the heading role and level when writing `<a-title>` directly.
112
+
113
+ ```html
114
+ <a-title level="2" tone="brand" role="heading" aria-level="2">
115
+ Release notes
116
+ </a-title>
117
+ ```
118
+
119
+ `priority` selects a neutral text level. Named `tone`s use the matching global
120
+ text tokens. A literal `tone` is stored in `--title-tone-source` and resolved
121
+ into the four priority levels.
122
+
123
+ ```tsx
124
+ <Title priority="secondary">Supporting heading</Title>
125
+ <Title tone="#0d9488">Custom-tone heading</Title>
126
+ ```
127
+
128
+ `<a-title>` is light DOM, so local type treatment is ordinary CSS. The class is
129
+ only for this example.
130
+
131
+ ```css
132
+ a-title.release-heading {
133
+ font-size: 18px;
134
+ line-height: 24px;
135
+ letter-spacing: 0.02em;
136
+ text-transform: uppercase;
137
+ }
138
+ ```
139
+
140
+ ## Example
141
+
142
+ ```tsx
143
+ import { Title } from '@antadesign/anta'
144
+
145
+ <Title>Section heading</Title>
146
+ ```
@@ -0,0 +1,313 @@
1
+ # Toaster
2
+
3
+ A viewport-anchored region that shows toasts — short, self-dismissing notices
4
+ stacked in a corner. It isn't a fixed "toast card": you toast **anything** — a
5
+ `Banner`, a `Card`, a `Sticker`, a string, a bespoke node — and the region handles
6
+ placement, stacking, and the auto-dismiss timer.
7
+
8
+ Two pieces work together. **`<Toaster>`** is the region; you mount one and keep it
9
+ mounted (a portal is fine). **`Toaster.manager`** is the store you drive from
10
+ anywhere: `Toaster.manager.add(render)` adds a toast, where `render` is a function
11
+ returning the content. The store holds no DOM — the mounted `<Toaster>` subscribes
12
+ to it and renders each toast through React/Preact, so there's nowhere for a toast
13
+ to go if no `<Toaster>` is mounted.
14
+
15
+ Configure one toast and fire it — the playground shows the render function plus the
16
+ options object you pass to `add`. Edit the `Banner` in the code (or the props panel)
17
+ and the `{ placement, duration }` it's toasted with.
18
+
19
+ ## Mount the region, drive the store
20
+
21
+ Mount one `<Toaster>` at a stable spot in your tree and leave it there:
22
+
23
+ ```tsx
24
+ import { Toaster } from '@antadesign/anta'
25
+
26
+ function App() {
27
+ return (
28
+ <>
29
+ <YourApp />
30
+ <Toaster /> {/* keep it mounted — toasts render here */}
31
+ </>
32
+ )
33
+ }
34
+ ```
35
+
36
+ Then, from anywhere, add a toast by passing a render function. The simplest case is
37
+ a string — it's wrapped in a dismissible `Banner` for you:
38
+
39
+ ```tsx
40
+ import { Toaster } from '@antadesign/anta'
41
+
42
+ Toaster.manager.add(() => 'Saved.', { placement: 'bottom-right' })
43
+ ```
44
+
45
+ `add` returns an `id`, and the render function receives it — so a toast can dismiss
46
+ itself. See [Timing and dismissal](#timing-and-dismissal) for how each kind of
47
+ content closes its toast.
48
+
49
+ ## Toast anything
50
+
51
+ The render function can return a **string, JSX, or a live DOM node** — the same
52
+ `add` handles all three. A bare string / number is wrapped in a dismissible `Banner`;
53
+ other JSX renders through the reconciler as-is; a DOM node is slotted by the element
54
+ (return a *stable* node, created once, not a fresh one per call).
55
+
56
+ ```tsx
57
+ // JSX — rendered as-is
58
+ Toaster.manager.add(() => <Card tone="info" size="small" header="Deployment ready">
59
+ Your build passed all checks.
60
+ </Card>, { placement: 'top-right' })
61
+
62
+ // a string — auto-wrapped in a dismissible Banner
63
+ Toaster.manager.add(() => 'Copied to clipboard')
64
+
65
+ // a DOM node (create it once, return the same reference)
66
+ const el = document.createElement('div')
67
+ el.textContent = '🎉'
68
+ Toaster.manager.add(() => el)
69
+ ```
70
+
71
+ Returning a raw DOM node **into React errors** if you hand it to JSX directly —
72
+ `add` avoids that by routing a `Node` to the element instead of the reconciler.
73
+ (Toast a JSX component like `<Banner/>` by returning it; reach for a DOM node only
74
+ when you already have one, e.g. from a non-React widget.)
75
+
76
+ ## Placement
77
+
78
+ `placement` picks the corner or edge; the default is `bottom-right`. Bottom zones
79
+ grow upward (newest nearest the edge), top zones grow downward. Six values:
80
+
81
+ `top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center`, `bottom-right`.
82
+
83
+ One `<Toaster>` serves every placement — each toast routes to its own zone, so a
84
+ single mounted region drops notices in any corner.
85
+
86
+ ## Timing and dismissal
87
+
88
+ Each toast auto-dismisses after `duration` (ms), 5000 by default; the countdown
89
+ pauses while the pointer is over the toast **or** focus is inside it. `duration:
90
+ Infinity` keeps it until dismissed (empty, zero, and non-positive values fall back
91
+ to the default). On dismiss the toast animates out and then leaves.
92
+
93
+ ```tsx
94
+ Toaster.manager.add(() => 'Still here until you close it', { duration: Infinity }) // sticky
95
+ ```
96
+
97
+ The toast itself carries **no ✕** — dismissal comes from the content. Three ways,
98
+ depending on what you toast:
99
+
100
+ - **A string** auto-wraps in a `Banner`, so its ✕ is there for free.
101
+ - **Any control carrying `data-toast-dismiss`** dismisses the toast on click. This is
102
+ the simplest path — no handler, no `id`, no `onDismiss`; the render function doesn't
103
+ even take an argument. Just add the attribute to a button, link, or menu item inside
104
+ the content (the same convention as `<a-banner>`'s `data-banner-dismiss` and
105
+ `<a-dialog>`'s `data-dialog-close`):
106
+
107
+ ```tsx
108
+ Toaster.manager.add(() => (
109
+ <Card header="Update ready">
110
+ A new version is available. <Button data-toast-dismiss label="Later" />
111
+ </Card>
112
+ ))
113
+ ```
114
+
115
+ - **A `Banner` you toast yourself** dismisses through its own `onDismiss` — wire it
116
+ to `dismiss(id)`, and its ✕ (plus any `data-banner-dismiss` action inside it)
117
+ closes the toast:
118
+
119
+ ```tsx
120
+ Toaster.manager.add((id) => (
121
+ <Banner tone="success" round message="Saved" onDismiss={() => Toaster.manager.dismiss(id)} />
122
+ ))
123
+ ```
124
+
125
+ Or drive dismissal from anywhere with the returned id:
126
+
127
+ ```tsx
128
+ const id = Toaster.manager.add(() => 'Saving…', { duration: Infinity })
129
+ Toaster.manager.dismiss(id) // animate out, then remove
130
+ Toaster.manager.update(id, () => 'Saved') // swap content in place
131
+ Toaster.manager.clear() // dismiss all
132
+ ```
133
+
134
+ Pass your own `id` to **upsert** — a second `add` with the same id replaces the
135
+ live toast's content and restarts its timer, instead of stacking a duplicate. Good
136
+ for a step that reports progress, or to dedupe a repeated event:
137
+
138
+ ```tsx
139
+ Toaster.manager.add(() => <Banner message="Saving…" />, { id: 'save' })
140
+ // …later…
141
+ Toaster.manager.add(() => <Banner tone="success" message="Saved" />, { id: 'save' }) // in place
142
+ ```
143
+
144
+ ## Show the time left
145
+
146
+ The toast exposes how much of its timer is left as **`--toast-remaining`** — a
147
+ number that animates from `1` (just shown) to `0` (about to dismiss), inherited by
148
+ the content. Draw a countdown with one CSS rule that reads it. No duration to pass,
149
+ no keyframes, no timer of your own — and it pauses with the toast (hover / focus)
150
+ because the variable itself pauses. Try **Toast with countdown** in the demo above
151
+ and hover it.
152
+
153
+ ```tsx
154
+ Toaster.manager.add(
155
+ (id) => (
156
+ <div className="countdown-toast">
157
+ <Banner tone="info" round message="Auto-dismissing…" onDismiss={() => Toaster.manager.dismiss(id)} />
158
+ <span className="countdown-bar" />
159
+ </div>
160
+ ),
161
+ { duration: 6000 },
162
+ )
163
+ ```
164
+
165
+ ```css
166
+ .countdown-toast { position: relative; }
167
+ .countdown-bar {
168
+ position: absolute; inset-inline: 10px; bottom: 5px; height: 3px;
169
+ border-radius: 999px; transform-origin: left;
170
+ background: color-mix(in oklch, currentColor 35%, transparent);
171
+ transform: scaleX(var(--toast-remaining)); /* 1 → 0 over the toast's life */
172
+ }
173
+ ```
174
+
175
+ **Why a variable, not a prop?** You could instead pass `secondsLeft` into the render
176
+ function and tick it. But updating a prop re-runs the render function and re-diffs the
177
+ whole toast — the Banner, its ✕, everything — many times a second, which can disturb
178
+ focus, text selection, or an in-progress animation inside the content. `--toast-remaining`
179
+ updates a single inherited value the browser interpolates on the compositor: the
180
+ content renders **once**, nothing reconciles, and it's smooth at display refresh rate
181
+ instead of your tick interval. The toast already owns the timer and its pause, so
182
+ exposing the value keeps one source of truth.
183
+
184
+ `--toast-remaining` is display only — the real dismissal is the toast's own timer — so
185
+ where a browser can't animate a custom property it simply stays full. A **sticky**
186
+ (`duration: Infinity`) toast has no countdown; the value stays `1`.
187
+
188
+ The bar is motion — if you draw one, gate it under `prefers-reduced-motion` yourself
189
+ (e.g. `@media (prefers-reduced-motion: reduce) { .countdown-bar { display: none } }`).
190
+ `a-toast`'s own enter/exit fades are already suppressed there; `--toast-remaining`
191
+ keeps ticking so non-visual uses still work, and the toast still auto-dismisses.
192
+
193
+ ## Multiple regions
194
+
195
+ `Toaster.manager` is the default store. For an isolated region, create your own and
196
+ bind it to a `<Toaster>` with `toaster`:
197
+
198
+ ```tsx
199
+ import { createToaster, Toaster } from '@antadesign/anta'
200
+
201
+ export const alerts = createToaster()
202
+
203
+ // mount its region:
204
+ <Toaster toaster={alerts} />
205
+
206
+ // drive it:
207
+ alerts.add(() => <Banner tone="critical" message="Connection lost" />, { placement: 'top-center' })
208
+ ```
209
+
210
+ One store drives one `<Toaster>`.
211
+
212
+ ## Announcements
213
+
214
+ Announcement is opt-in **per toast**: pass `politeness` to give that toast an
215
+ `aria-live` region — `'polite'` waits for a pause, `'assertive'` interrupts. Omit
216
+ it for no announcement, which is right when the content carries its own live
217
+ semantics (a `Banner` defaults to `role="status"`).
218
+
219
+ ```tsx
220
+ Toaster.manager.add(() => <Banner tone="critical" message="Connection lost" />, {
221
+ politeness: 'assertive',
222
+ })
223
+ ```
224
+
225
+ ### Props
226
+
227
+ | Prop | Type | Default | Description |
228
+ |------|------|---------|-------------|
229
+ | `label?` | string | 'Notifications' | Accessible label for the region landmark. |
230
+ | `toaster?` | Toaster | — | The store this region renders. Omit to bind the default store driven by
231
+ `Toaster.manager`; pass a `createToaster()` for an isolated region. |
232
+
233
+ 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.
234
+
235
+ Place each `<a-toast>` in a positional slot. This button clones the toast template
236
+ into the toaster. `data-toast-dismiss` requests removal; listen for `dismiss` and
237
+ remove the toast from the DOM.
238
+
239
+ ```html
240
+ <div data-anta-composition="toaster">
241
+ <a-button data-toast-add role="button" tabindex="0"><a-button-label>Show update</a-button-label></a-button>
242
+ <a-toaster role="region" aria-label="Notifications">
243
+ </a-toaster>
244
+ <template data-toast-template>
245
+ <a-toast slot="bottom-center" aria-live="polite">
246
+ <a-banner tone="info" round>
247
+ <a-banner-message slot="message">A new version is available.</a-banner-message>
248
+ <a-button slot="actions" role="button" tabindex="0" data-toast-dismiss><a-button-label>Dismiss</a-button-label></a-button>
249
+ </a-banner>
250
+ </a-toast>
251
+ </template>
252
+ </div>
253
+ <script type="module">
254
+ import '@antadesign/anta/elements'
255
+
256
+ const root = document.querySelector('[data-anta-composition="toaster"]')
257
+ const trigger = root.querySelector('[data-toast-add]')
258
+ const template = root.querySelector('template[data-toast-template]')
259
+ const toaster = root.querySelector('a-toaster')
260
+
261
+ trigger.addEventListener('click', () => toaster.append(template.content.cloneNode(true)))
262
+ // A dismissed toast finishes its exit animation, but stays in the DOM until its owner removes it.
263
+ root.addEventListener('dismiss', (event) => {
264
+ if (event.target instanceof HTMLElement && event.target.matches('a-toast')) event.target.remove()
265
+ })
266
+ </script>
267
+ ```
268
+
269
+ The region and item are plain custom elements — style them with ordinary CSS
270
+ (`@layer anta`, so an un-layered rule of yours wins without `!important`).
271
+
272
+ Region spacing lives on `a-toaster` as tokens: **`--toaster-inset`** (gap from the
273
+ viewport edge), **`--toaster-gap`** (space between stacked toasts), and
274
+ **`--toaster-width`** (column width). The item's enter/exit timing lives on
275
+ `a-toast`: **`--toast-dur`**. The item also publishes **`--toast-remaining`** (a
276
+ number, `1` → `0`, tracking the auto-dismiss timer) for content to read — see
277
+ [Show the time left](#show-the-time-left). The item is style-neutral — no chrome of
278
+ its own — so a toast looks like exactly the content you pass.
279
+
280
+ `<a-toast>` is the content box itself, so add elevation (or any style) with the
281
+ plain `a-toast` selector:
282
+
283
+ ```css
284
+ a-toaster { --toaster-width: 420px; --toaster-gap: 10px; }
285
+ a-toast { filter: drop-shadow(0 6px 20px color-mix(in oklch, black 22%, transparent)); }
286
+ ```
287
+
288
+ The content keeps its own styling — a toned `Banner` brings its surface, a `Card`
289
+ its border.
290
+
291
+ ## Example
292
+
293
+ ```tsx
294
+ import { Toaster, Button, Banner } from '@antadesign/anta'
295
+
296
+ <Toaster />
297
+ <Button
298
+ label="Toast it"
299
+ onClick={() =>
300
+ Toaster.manager.add(
301
+ (id) => (
302
+ <Banner
303
+ tone="success"
304
+ round
305
+ message="Your changes were saved."
306
+ onDismiss={() => Toaster.manager.dismiss(id)}
307
+ />
308
+ ),
309
+ { placement: 'bottom-right', duration: 4000 },
310
+ )
311
+ }
312
+ />
313
+ ```