@antadesign/anta 0.3.34 → 0.3.35

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 (184) hide show
  1. package/LICENSE.md +14 -1
  2. package/README.md +9 -0
  3. package/dist/anta_helpers.d.ts +46 -30
  4. package/dist/anta_helpers.js +66 -25
  5. package/dist/bundle.css +1 -1
  6. package/dist/bundle.js +2 -1333
  7. package/dist/components/Banner.d.ts +25 -40
  8. package/dist/components/Banner.js +15 -17
  9. package/dist/components/Button.js +1 -0
  10. package/dist/components/ButtonCopy.js +3 -2
  11. package/dist/components/Calendar.js +13 -13
  12. package/dist/components/Card.d.ts +9 -17
  13. package/dist/components/Card.js +50 -46
  14. package/dist/components/Dialog.d.ts +1 -0
  15. package/dist/components/Dialog.js +16 -10
  16. package/dist/components/Expander.d.ts +6 -21
  17. package/dist/components/Expander.js +24 -23
  18. package/dist/components/Icon.d.ts +8 -14
  19. package/dist/components/Input.d.ts +77 -96
  20. package/dist/components/Input.js +23 -27
  21. package/dist/components/InputAutocomplete.d.ts +29 -10
  22. package/dist/components/InputAutocomplete.js +25 -8
  23. package/dist/components/InputDate.js +2 -2
  24. package/dist/components/InputTime.d.ts +10 -4
  25. package/dist/components/InputTime.js +20 -16
  26. package/dist/components/Loader.d.ts +2 -2
  27. package/dist/components/Menu.d.ts +5 -4
  28. package/dist/components/Menu.js +13 -8
  29. package/dist/components/MenuItem.js +1 -14
  30. package/dist/components/MenuItemCopy.js +2 -2
  31. package/dist/components/Panel.d.ts +3 -1
  32. package/dist/components/Panel.js +17 -11
  33. package/dist/components/Progress.d.ts +21 -23
  34. package/dist/components/Progress.js +13 -9
  35. package/dist/components/RadioGroup.js +6 -8
  36. package/dist/components/{select-parts.css → Select.css} +1 -1
  37. package/dist/components/Select.d.ts +53 -35
  38. package/dist/components/Select.js +53 -97
  39. package/dist/components/SelectFaceted.d.ts +7 -3
  40. package/dist/components/SelectFaceted.js +21 -9
  41. package/dist/components/Slider.d.ts +28 -11
  42. package/dist/components/Slider.js +20 -20
  43. package/dist/components/Steps.css +1 -1
  44. package/dist/components/Steps.js +1 -11
  45. package/dist/components/Tabs.d.ts +17 -20
  46. package/dist/components/Tabs.js +5 -8
  47. package/dist/components/Text.d.ts +55 -36
  48. package/dist/components/Text.js +35 -22
  49. package/dist/components/Title.d.ts +16 -26
  50. package/dist/components/Title.js +2 -1
  51. package/dist/components/Toaster.js +9 -2
  52. package/dist/components/Tooltip.d.ts +10 -15
  53. package/dist/components/copy-props.d.ts +14 -3
  54. package/dist/components/copy-props.js +14 -3
  55. package/dist/components/select-tree.d.ts +24 -0
  56. package/dist/components/select-tree.js +35 -0
  57. package/dist/element-base.d.ts +10 -0
  58. package/dist/element-base.js +15 -0
  59. package/dist/elements/a-banner.css +1 -1
  60. package/dist/elements/a-banner.d.ts +4 -1
  61. package/dist/elements/a-banner.js +22 -112
  62. package/dist/elements/a-button.d.ts +2 -1
  63. package/dist/elements/a-button.js +18 -1
  64. package/dist/elements/a-calendar.d.ts +14 -13
  65. package/dist/elements/a-calendar.js +29 -24
  66. package/dist/elements/a-card.css +1 -1
  67. package/dist/elements/a-card.d.ts +0 -9
  68. package/dist/elements/a-card.js +0 -184
  69. package/dist/elements/a-checkbox.css +1 -1
  70. package/dist/elements/a-copy.css +1 -1
  71. package/dist/elements/a-copy.d.ts +1 -5
  72. package/dist/elements/a-copy.js +129 -108
  73. package/dist/elements/a-dialog.css +1 -1
  74. package/dist/elements/a-dialog.d.ts +1 -1
  75. package/dist/elements/a-dialog.js +66 -194
  76. package/dist/elements/a-expander.css +1 -1
  77. package/dist/elements/a-expander.d.ts +4 -30
  78. package/dist/elements/a-expander.js +85 -344
  79. package/dist/elements/a-icon.d.ts +5 -23
  80. package/dist/elements/a-icon.js +1 -2
  81. package/dist/elements/a-input-time.css +1 -1
  82. package/dist/elements/a-input-time.d.ts +2 -1
  83. package/dist/elements/a-input-time.js +101 -168
  84. package/dist/elements/a-input.css +1 -1
  85. package/dist/elements/a-input.d.ts +10 -8
  86. package/dist/elements/a-input.js +155 -230
  87. package/dist/elements/a-loader.d.ts +5 -8
  88. package/dist/elements/a-loader.js +1 -2
  89. package/dist/elements/a-menu-item.css +1 -1
  90. package/dist/elements/a-menu-item.d.ts +5 -3
  91. package/dist/elements/a-menu-item.js +5 -1
  92. package/dist/elements/a-menu.css +1 -1
  93. package/dist/elements/a-menu.d.ts +5 -7
  94. package/dist/elements/a-menu.js +383 -299
  95. package/dist/elements/a-panel.css +1 -1
  96. package/dist/elements/a-panel.js +19 -37
  97. package/dist/elements/a-progress.css +1 -1
  98. package/dist/elements/a-progress.d.ts +0 -22
  99. package/dist/elements/a-progress.js +0 -100
  100. package/dist/elements/a-radio-group.d.ts +1 -1
  101. package/dist/elements/a-radio-group.js +6 -4
  102. package/dist/elements/a-select-faceted.d.ts +1 -1
  103. package/dist/elements/a-select-faceted.js +1 -1
  104. package/dist/elements/a-select.d.ts +1 -1
  105. package/dist/elements/a-select.js +1 -1
  106. package/dist/elements/a-slider.css +1 -1
  107. package/dist/elements/a-slider.d.ts +11 -3
  108. package/dist/elements/a-slider.js +244 -242
  109. package/dist/elements/a-switch.css +1 -1
  110. package/dist/elements/a-tab.css +1 -1
  111. package/dist/elements/a-tabs.css +1 -1
  112. package/dist/elements/a-tabs.d.ts +1 -1
  113. package/dist/elements/a-tabs.js +6 -4
  114. package/dist/elements/a-tag.css +1 -1
  115. package/dist/elements/a-text.css +1 -1
  116. package/dist/elements/a-text.d.ts +6 -43
  117. package/dist/elements/a-text.js +110 -193
  118. package/dist/elements/a-title.css +1 -1
  119. package/dist/elements/a-toast.css +1 -1
  120. package/dist/elements/a-toast.d.ts +10 -45
  121. package/dist/elements/a-toast.js +80 -66
  122. package/dist/elements/a-toaster.css +1 -1
  123. package/dist/elements/a-toaster.js +9 -44
  124. package/dist/elements/a-tooltip.css +1 -1
  125. package/dist/elements/a-tooltip.d.ts +1 -1
  126. package/dist/elements/a-tooltip.js +142 -136
  127. package/dist/elements/copy-behavior.d.ts +1 -1
  128. package/dist/elements/copy-behavior.js +21 -21
  129. package/dist/elements/index.d.ts +3 -3
  130. package/dist/elements/index.js +3 -7
  131. package/dist/elements/input-focus-modality.d.ts +15 -0
  132. package/dist/elements/input-focus-modality.js +44 -0
  133. package/dist/elements/input-trailing-actions.d.ts +5 -0
  134. package/dist/elements/input-trailing-actions.js +38 -0
  135. package/dist/elements/shadow-aria.d.ts +2 -0
  136. package/dist/elements/shadow-aria.js +2 -1
  137. package/dist/elements/value-input.d.ts +3 -0
  138. package/dist/elements/value-input.js +12 -0
  139. package/dist/general_types.d.ts +103 -96
  140. package/dist/index.d.ts +1 -1
  141. package/dist/input-events.d.ts +4 -0
  142. package/dist/input-events.js +15 -0
  143. package/dist/jsx-runtime.d.ts +70 -23
  144. package/dist/jsx-runtime.js +26 -19
  145. package/dist/native-event-props.d.ts +2 -0
  146. package/dist/native-event-props.js +10 -0
  147. package/dist/reset.css +1 -1
  148. package/dist/theme-antithesis.css +10 -1
  149. package/dist/theme-antune.css +1 -1
  150. package/docs/accessibility.md +17 -3
  151. package/docs/changelog.md +348 -2
  152. package/docs/comparison.md +2 -2
  153. package/docs/components/avatar.md +5 -0
  154. package/docs/components/banner.md +117 -187
  155. package/docs/components/button.md +22 -0
  156. package/docs/components/card.md +74 -28
  157. package/docs/components/checkbox.md +9 -10
  158. package/docs/components/dialog.md +83 -32
  159. package/docs/components/expander.md +198 -435
  160. package/docs/components/icon.md +11 -17
  161. package/docs/components/input-autocomplete.md +152 -52
  162. package/docs/components/input-date.md +175 -148
  163. package/docs/components/input-time.md +81 -38
  164. package/docs/components/input.md +343 -349
  165. package/docs/components/loader.md +2 -1
  166. package/docs/components/menu.md +35 -6
  167. package/docs/components/panel.md +18 -0
  168. package/docs/components/progress.md +83 -49
  169. package/docs/components/radio.md +39 -43
  170. package/docs/components/select-faceted.md +54 -7
  171. package/docs/components/select.md +390 -386
  172. package/docs/components/slider.md +136 -19
  173. package/docs/components/steps.md +2 -1
  174. package/docs/components/switch.md +8 -9
  175. package/docs/components/tabs.md +110 -104
  176. package/docs/components/text.md +110 -86
  177. package/docs/components/title.md +47 -33
  178. package/docs/components/toaster.md +242 -84
  179. package/docs/components/tooltip.md +204 -113
  180. package/docs/install-config.md +183 -26
  181. package/docs/packages/plot.md +170 -148
  182. package/docs/theming.md +7 -0
  183. package/package.json +41 -3
  184. package/theme-antithesis-license.md +66 -0
@@ -1,13 +1,12 @@
1
1
  # Tabs
2
2
 
3
- A tablist for switching between views. The strip is data-driven: pass an
4
- `options` array (like [RadioGroup](./radio.md)), each entry a unique `value` plus a
5
- `label` (or `children`) and optional `icon` / `iconTrailing`. That alone is a
6
- selectable strip that emits the chosen value; add matching `<TabPanel>` children
7
- (see [Panels](#panels)) to show and hide the content too.
3
+ Use Tabs to switch between related views, such as account and security settings.
4
+ Pass an `options` array with a unique `value` and a label for each tab. Add
5
+ matching `TabPanel` children to display the selected view, or render that
6
+ content yourself.
8
7
 
9
- Each option can also set `className`, `style`, and `data-*` attributes on its
10
- individual `<a-tab>`.
8
+ Options also accept icons, custom content, `className`, `style`, and `data-*`
9
+ attributes. See [Panels](#panels) for content layout.
11
10
 
12
11
  ## Priority
13
12
 
@@ -43,9 +42,9 @@ custom color); columns are priorities.
43
42
 
44
43
  **Per-tab tone.** A `tone` on an individual option overrides the strip's tone for that one
45
44
  tab: it colors the label and icons in every mode, and the indicator when it's the active tab.
46
- A named tone colors the sliding indicator too; a custom literal color tones the label
47
- everywhere but the indicator only under `noslide` (the single shared sliding element can't
48
- read a descendant's color).
45
+ The track background and border keep the strip's tone when selection changes.
46
+ Named tones also color the sliding indicator. For a custom CSS color on an
47
+ individual option, pass `noslide` to color its indicator too.
49
48
 
50
49
  ```tsx
51
50
  <Tabs
@@ -70,6 +69,9 @@ Three sizes reuse [Button](./button.md)'s type scale, so a strip lines up with
70
69
  same-size buttons and inputs: `small`, `medium` (default), `large`. Icons scale
71
70
  with the tab.
72
71
 
72
+ In the Antithesis theme, tabs match text buttons' horizontal padding:
73
+ 10px per side at `small`, 12px at `medium`, and 14px at `large`.
74
+
73
75
  ## Orientation
74
76
 
75
77
  `horizontal` (default) lays the tabs in a row; `vertical` stacks them into a column.
@@ -119,10 +121,9 @@ instead of `label`, like a [Tag](./tag.md) counter.
119
121
  When the tabs don't fit, their labels truncate with an ellipsis; the strip never
120
122
  widens the page. To scroll or wrap labels instead, see [Styling](#styling).
121
123
 
122
- Set an option's **`tooltip`** and a clipped tab reveals its full label on hover; tabs
123
- that fit show nothing. It renders as a `truncatedOnly` [Tooltip](./tooltip.md#show-only-when-truncated)
124
- anchored to the tab, so it shows only when the label ellipsizes. Hover the truncated
125
- tabs above to see it.
124
+ Set an option's `tooltip` to reveal its full label when truncated. Tabs whose
125
+ labels fit do not show a tooltip. See
126
+ [Show only when truncated](./tooltip.md#show-only-when-truncated).
126
127
 
127
128
  ```tsx
128
129
  <Tabs
@@ -159,79 +160,75 @@ tabs above to see it.
159
160
  </div>
160
161
  ```
161
162
 
162
- Add a `<TabPanel value="…">` child for each option; each panel shows itself when
163
- its value is active. The panel names its tab as its accessible label
164
- (`aria-labelledby`, off-DOM). Panel content is arbitrary React, so it renders normally.
163
+ Add a `TabPanel` child with a matching `value` for each option. The selected
164
+ panel is visible and uses its tab as its accessible label. Panel content can
165
+ include forms, charts, or other components.
166
+
167
+ Panels are optional. To render the selected content yourself, listen to
168
+ `onStateChange` or `onValueChange`.
169
+
170
+ <a id="jsx-vs-dom"></a>
165
171
 
166
- Panels are **optional**. Without panels, `Tabs` renders the strip. Listen to
167
- `onStateChange` / `onValueChange` and render the content yourself.
172
+ ### Panel layout
168
173
 
169
- ### JSX vs DOM
174
+ `Tabs` renders the strip followed by its panels as siblings, without a wrapper.
175
+ `className`, `id`, `style`, and extra props apply to the strip. Keep the strip
176
+ and its panels under the same parent so they can coordinate selection and
177
+ accessible labels.
170
178
 
171
- The strip renders from the `options` array; each `<TabPanel>` renders a
172
- self-managing `<a-tabpanel>`. `Tabs` **never reads or toggles the panels**: there's
173
- no child introspection, so it renders the same in static SSR and works with any
174
- renderer. There is **no wrapper element**: the JSX becomes the strip followed by the
175
- panels as flat **siblings**, and `className` / `id` / `style` / `...rest` land on the
176
- strip. Arranging them is up to you (see below):
179
+ The resulting structure is:
177
180
 
178
181
  ```html
179
- <a-tabs role="tablist">
182
+ <a-tabs role="tablist" aria-label="Settings" default-state="account">
180
183
  <a-tab role="tab" value="account" tabindex="0"><a-tab-label>Account</a-tab-label></a-tab>
181
- …
184
+ <a-tab role="tab" value="security" tabindex="-1"><a-tab-label>Security</a-tab-label></a-tab>
182
185
  </a-tabs>
183
- <a-tabpanel role="tabpanel" value="account">…</a-tabpanel> <!-- active: shown -->
184
- <a-tabpanel role="tabpanel" value="security">…</a-tabpanel> <!-- inactive: display:none -->
186
+ <a-tabpanel role="tabpanel" value="account">Account settings</a-tabpanel>
187
+ <a-tabpanel role="tabpanel" value="security">Security settings</a-tabpanel>
185
188
  ```
186
189
 
187
- A horizontal strip stacks above its panels in normal flow with no extra markup; for a
188
- vertical strip beside its panels, wrap `<Tabs>` in your own flex row. The panels find
189
- the strip as a sibling under the same parent (`a-tabpanel` → `:scope > a-tabs`), so
190
- keep them together; to split them into separate layout regions, drive a
191
- controlled `value` and render the panels yourself.
192
-
193
- Each `<a-tabpanel>` sits **beside** `<a-tabs>` (its sibling, never inside it, so a
194
- panel never lands in the primary track). It reads the active value from `<a-tabs>`
195
- and shows/hides **itself** via its own off-DOM `:state(active)`; nothing writes
196
- `hidden` / `inert` on it. The panel↔tab link is off-DOM too
197
- (`internals.ariaLabelledByElements`), so no `id` wiring is needed. No web component
198
- writes the light DOM, only your JSX does, which keeps a worker-thread reactive engine
199
- in sync.
190
+ A horizontal strip and its panels stack in normal flow. For a vertical strip
191
+ beside its panels, wrap `Tabs` in a flex row. To place content in a separate
192
+ layout region, control `value` and render that content yourself.
200
193
 
201
194
  ### Hiding an inactive panel
202
195
 
203
- A `<TabPanel>` stays mounted while inactive (its DOM, form values, and scroll
204
- survive tab switches); `hideMode` picks how it hides:
196
+ Inactive panels stay mounted, preserving form values and scroll position.
197
+ Choose how they hide with `hideMode`:
205
198
 
206
- - **`display`** (default): `display:none`; leaves the tab order + a11y tree.
207
- - **`visibility`**: `visibility:hidden`, keeping its layout box (to avoid reflow
208
- or to measure a hidden panel).
199
+ - `display` (default) removes the inactive panel from layout, keyboard navigation,
200
+ and the accessibility tree.
201
+ - `visibility` keeps its layout space while hiding its content.
209
202
 
210
203
  ```tsx
211
- <TabPanel value="b" hideMode="visibility"><Chart /></TabPanel>
204
+ <TabPanel value="activity" hideMode="visibility"><ActivityChart /></TabPanel>
212
205
  ```
213
206
 
214
- To **not render** an inactive panel at all (unmount it to reset its state or defer an
215
- expensive subtree, or mount it lazily on first open), drive selection with a
216
- controlled `value` and render the panels yourself. `Tabs` passes panels straight
217
- through, so you decide what's mounted:
207
+ To reset a panel's state when leaving it, control selection and render only the
208
+ active panel:
218
209
 
219
210
  ```tsx
220
- const [tab, setTab] = useState('a')
211
+ const [tab, setTab] = useState('summary')
221
212
 
222
- // Unmount inactive — only the open panel exists:
223
- <Tabs value={tab} onStateChange={(_e, { next }) => next && setTab(next)} options={…}>
224
- {tab === 'a' && <TabPanel value="a"><Expensive /></TabPanel>}
225
- {tab === 'b' && <TabPanel value="b"><Form /></TabPanel>}
213
+ <Tabs
214
+ label="Report"
215
+ value={tab}
216
+ onStateChange={(_event, { next }) => { if (next !== null) setTab(next) }}
217
+ options={[
218
+ { value: 'summary', label: 'Summary' },
219
+ { value: 'activity', label: 'Activity' },
220
+ ]}
221
+ >
222
+ {tab === 'summary' && <TabPanel value="summary"><Summary /></TabPanel>}
223
+ {tab === 'activity' && <TabPanel value="activity"><ActivityChart /></TabPanel>}
226
224
  </Tabs>
227
-
228
- // Lazy — mount on first open, keep after (track a "seen" set in state):
229
- {seen.has('b') && <TabPanel value="b"><Heavy /></TabPanel>}
230
225
  ```
231
226
 
232
- The same controlled pattern places the strip and panels in **different layout
233
- regions**: render `<Tabs options={…} value onStateChange>` in one place and your
234
- content wherever you like, switching on the value yourself.
227
+ For expensive panels, track which values have been opened and mount each panel
228
+ on its first visit. Keep it mounted afterward to preserve its state.
229
+
230
+ The same controlled pattern lets you place the strip and selected content in
231
+ separate layout regions. Render the content according to the current `value`.
235
232
 
236
233
  ## Controlled vs. uncontrolled
237
234
 
@@ -261,7 +258,7 @@ const [tab, setTab] = useState('overview')
261
258
 
262
259
  <Tabs
263
260
  value={tab}
264
- onStateChange={(e, { next }) => setTab(next)}
261
+ onStateChange={(_event, { next }) => { if (next !== null) setTab(next) }}
265
262
  options={[
266
263
  { value: 'overview', label: 'Overview' },
267
264
  { value: 'activity', label: 'Activity' },
@@ -269,7 +266,9 @@ const [tab, setTab] = useState('overview')
269
266
  />
270
267
  ```
271
268
 
272
- ## Keyboard & accessibility
269
+ <a id="keyboard-accessibility"></a>
270
+
271
+ ## Keyboard and accessibility
273
272
 
274
273
  The selected tab is in the page's tab order. Once focus enters the strip, the
275
274
  arrow keys move between enabled tabs (wrapping at the ends), `Home` /
@@ -277,19 +276,16 @@ arrow keys move between enabled tabs (wrapping at the ends), `Home` /
277
276
  arrow / `Home` / `End` navigation activates as it moves (selection follows focus).
278
277
  Disabled tabs are skipped and dropped from the tab order.
279
278
 
280
- Pass `label` for the tablist's accessible name. Each `<TabPanel>` names its tab as
281
- its accessible label (`aria-labelledby`), set off-DOM so nothing writes the panel's
282
- attributes. Static panel content remains readable without making the panel a tab
283
- stop. Set `tabIndex={0}` when you want a text-only panel itself to receive focus;
284
- when the panel starts with a focusable control, Tab reaches that control directly.
279
+ Pass `label` to name the tablist for screen readers. Each `TabPanel` uses its
280
+ corresponding tab as its accessible label. Set `tabIndex={0}` when a text-only
281
+ panel should receive keyboard focus. When the panel starts with a focusable
282
+ control, Tab reaches that control directly.
285
283
 
286
284
  ## Routing
287
285
 
288
- `Tabs` reports the picked value and leaves navigation to the app, so a strip can
289
- switch routes as readily as panels. Treat each `value` as a path: control `value`
290
- from the current location and navigate in `onStateChange` (the pick fires *on click*;
291
- `onValueChange` waits for `value` to change, which for a route happens only after
292
- you've already navigated). Skip the `<TabPanel>`s, since the router renders the view.
286
+ To use Tabs for routes, control `value` from the current path and navigate in
287
+ `onStateChange`. It reports the requested destination before the route changes.
288
+ The router renders the content, so omit `TabPanel` children.
293
289
 
294
290
  ```tsx
295
291
  <Tabs
@@ -317,16 +313,20 @@ Three callbacks, in firing order:
317
313
  | `onChange(e)` | **after** selection applies | — | native `change` event |
318
314
  | `onValueChange(e, { value })` | **after** selection applies | — | `{ value }` |
319
315
 
320
- Use **`onStateChange`** when you control `value`: it fires on the user's pick before
321
- anything applies, so it's your chance to set the new `value`. Applying it (or the
322
- element self-applying, uncontrolled) then fires **`onChange`** / **`onValueChange`**,
323
- so those suit an uncontrolled strip reacting to its own pick. **`onFocus`** /
324
- **`onBlur`** report focus entering and leaving the strip (wired to `focusin` /
325
- `focusout`, since focus lands on a tab).
316
+ Use `onStateChange` to accept or cancel a requested selection. Use `onChange` or
317
+ `onValueChange` to respond after selection changes. `onFocus` and `onBlur`
318
+ report focus entering and leaving the strip.
326
319
 
327
320
  ```tsx
328
- // Uncontrolled: the strip owns selection, so onValueChange fires on each pick.
329
- <Tabs defaultValue="a" onValueChange={(_e, { value }) => route(value)}>…</Tabs>
321
+ <Tabs
322
+ label="Report"
323
+ defaultValue="summary"
324
+ onValueChange={(_event, { value }) => saveLastView(value)}
325
+ options={[
326
+ { value: 'summary', label: 'Summary' },
327
+ { value: 'activity', label: 'Activity' },
328
+ ]}
329
+ />
330
330
  ```
331
331
 
332
332
  ## Tabs props
@@ -347,7 +347,7 @@ so those suit an uncontrolled strip reacting to its own pick. **`onFocus`** /
347
347
  | `options?` | TabOption[] | — | The tabs, as a data array (the strip's single source). Each entry is a `TabOption` (`value`, `label` or `children`, `icon`, `iconTrailing`, `tone`, `disabled`, `round`, `className`, `style`). `className` and `style` land on that option's individual `<a-tab>`, not on the strip. |
348
348
  | `orientation?` | 'horizontal' \| 'vertical' | 'horizontal' | Layout + arrow-key axis. Horizontal ellipsizes labels when tabs overflow (scroll is opt-in via CSS); vertical stacks them. |
349
349
  | `priority?` | 'primary' \| 'secondary' \| 'tertiary' | 'primary' | Visual priority. `primary` is the raised pill on a recessed track (the segmented-control look); `secondary` keeps that sizing but drops the track, marking the selected tab with a subtle active background fill; `tertiary` is a bottom-underline indicator under the selected tab (no track, no rest line). `tone` colors `secondary` + `tertiary`; `primary` stays neutral. |
350
- | `round?` | boolean \| number \| string | — | Fully-round the tabs and the sliding indicator (and the primary track well). Applies strip-wide; a single tab's `round` rounds just that tab. A `number` (px) or CSS length string sets a custom radius on the top-level track well only — the tab pills + indicator stay fully round. |
350
+ | `round?` | boolean \| number \| string | — | Fully-round the tabs, the sliding indicator, and the primary track. Applies strip-wide; a single tab's `round` rounds just that tab. A `number` (px) or CSS length string sets that radius on all three. |
351
351
  | `size?` | 'small' \| 'medium' \| 'large' | 'medium' | Size — small 24px · medium 28px · large 32px tall, matching Button's scale (the tab's label leading runs a touch tighter, offset by 1px more block padding per side). |
352
352
  | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Tone applied to the selected indicator/label, or any literal CSS color for a one-off custom tone (derived in oklch). Named tones track light/dark. |
353
353
  | `value?` | string | — | Controlled active value — the tab `value` to mark selected (and, when a `<TabPanel value="…">` shares it, the panel to reveal). When set, you own selection: the strip renders exactly what this says, and a user pick only *requests* a change via `onStateChange` — apply it by updating this prop. Leave undefined (and use `defaultValue`) for uncontrolled. |
@@ -365,7 +365,7 @@ so those suit an uncontrolled strip reacting to its own pick. **`onFocus`** /
365
365
  | `label?` | ReactNode | — | Visible label. The string shorthand for the tab's content; for richer content pass `children` instead (`label` wins when both are set). |
366
366
  | `round?` | boolean | — | Fully-round just this tab's box. `<Tabs round>` rounds the whole strip (tabs + sliding indicator) instead. |
367
367
  | `style?` | CSSProperties | — | Inline styles on the option's rendered row. |
368
- | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | inherits the strip's `tone | Per-tab tone override, same vocabulary as `<Tabs tone>` — colors this one tab's label + icons (all priorities/modes, named or custom color) and, when it's the active tab, its indicator. For a **custom literal color** the sliding indicator can't adopt it (the shared moving element can't read a descendant's color), so a custom tone colors the label everywhere and the indicator only in `noslide`; the six **named** tones color both in every mode. Overrides the strip's `tone` for this tab. |
368
+ | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | inherits the strip's `tone | Per-tab tone override, same vocabulary as `<Tabs tone>` — colors this one tab's label + icons (all priorities/modes, named or custom color) and, when it's the active tab, its indicator. The track keeps the strip's tone. For a **custom literal color** the sliding indicator can't adopt it (the shared moving element can't read a descendant's color), so a custom tone colors the label everywhere and the indicator only in `noslide`; the six **named** tones color both in every mode. Overrides the strip's `tone` for this tab. |
369
369
  | `tooltip?` | ReactNode | — | Tooltip for this tab — a string or any node — shown **only when one of the tab's ellipsizing label parts is truncated** (tabs ellipsize when the strip overflows), so clipped content reveals its full text on hover while a tab that fits shows nothing. Rendered as a `truncatedOnly` `<Tooltip>` anchored to the tab. For an always-visible tooltip or other custom trigger content, use `children` with your own `<Tooltip>` instead. |
370
370
 
371
371
  ## TabPanel props
@@ -379,6 +379,13 @@ so those suit an uncontrolled strip reacting to its own pick. **`onFocus`** /
379
379
 
380
380
  Use the web component directly when you are not using React or Preact and a native control does not fit.
381
381
 
382
+ Use `state` for controlled selection or `default-state` for the initial
383
+ uncontrolled selection. The read-only `.value` property returns the applied
384
+ selection. Remove `state` to release control and retain that selection.
385
+ User picks update internal state without rewriting authored attributes.
386
+ Accept a controlled pick through `statechange` by setting `state` to
387
+ `event.detail.next`. An empty string is a valid option value.
388
+
382
389
  Keep the tab strip and panels as siblings under one parent. Panels read the active
383
390
  value from `<a-tabs>`. Give the selected tab `tabindex="0"` and the others
384
391
  `tabindex="-1"`; `<a-tabs>` moves focus and selection with the arrow keys, while
@@ -414,21 +421,20 @@ tabs.addEventListener('change', () => {
414
421
  Reach for the props first: **`tone`**, **`size`**, **`priority`**. The focus ring is
415
422
  the global `--focus-ring` (see [Colors](../colors.md#focus-ring)).
416
423
 
417
- For anything else the strip is light-DOM: `<a-tabs>` the tablist, each `<a-tab>` a
418
- tab (its text wrapped in `<a-tab-label>`), `<a-tabpanel>` a panel, and the moving
419
- indicator is `<a-tabs>`'s `::before`. Target them with ordinary CSS (an un-layered rule
420
- beats `@layer anta` without `!important`) and reach for real properties
421
- (`border-radius`, `padding`, `background`, …), **not** the internal `--*` output tokens.
422
- The demo classes (`.square-tabs`, `.glow-tabs`, …) are hooks. Replace them with selectors you own.
424
+ Use ordinary CSS to style the strip (`a-tabs`), tabs (`a-tab`), labels
425
+ (`a-tab-label`), and panels (`a-tabpanel`). The strip's `::before` paints the
426
+ moving indicator. Unlayered consumer rules override Anta's defaults. Use
427
+ properties such as `border-radius`, `padding`, and `background`.
428
+ Replace the example classes with your own selectors.
423
429
 
424
430
  **Roomier track.** The strip hugs its tabs by default (0 padding), so it's exactly a
425
431
  same-size button tall. For a gap around the selected pill (the classic "well" look), add
426
- `padding` and bump `border-radius` by the same amount, so the outer corner stays concentric
427
- with the tabs' 4px. The strip grows 2×the padding taller.
432
+ `padding` and add the same amount to the tabs' radius, `--tab-radius`, so the outer corner
433
+ stays concentric with the tabs in every theme and size. The strip grows 2×the padding taller.
428
434
 
429
435
  ```css
430
- a-tabs.roomy-1 { padding: 1px; border-radius: 5px; } /* 4 + 1 */
431
- a-tabs.roomy-3 { padding: 3px; border-radius: 7px; } /* 4 + 3 */
436
+ a-tabs.roomy-1 { padding: 1px; border-radius: calc(var(--tab-radius) + 1px); }
437
+ a-tabs.roomy-3 { padding: 3px; border-radius: calc(var(--tab-radius) + 3px); }
432
438
  ```
433
439
 
434
440
  **Squarer primary.** Square corners, heavier labels, and a roomier `4px` track with no
@@ -494,19 +500,19 @@ shares the strip width:
494
500
  .wrap-tabs a-tab-label { white-space: normal; text-overflow: clip; overflow: visible; text-align: center; }
495
501
  ```
496
502
 
497
- **Disable the slide.** By default the selected-tab indicator slides between tabs (a
498
- single rectangle, via CSS anchor positioning). Pass `noslide` to paint it on each tab
499
- instead, so the highlight snaps with no movement. It's also the automatic fallback
500
- where anchor positioning isn't supported.
503
+ Pass `noslide` to make the selected-tab highlight change without sliding.
504
+ The highlight also changes without sliding when the browser lacks CSS anchor
505
+ positioning support.
501
506
 
502
507
  ```tsx
503
508
  <Tabs noslide defaultValue="a">…</Tabs>
504
509
  ```
505
510
 
506
- **Fully-rounded pill.** The built-in **`round`** prop rounds the track, every tab, **and**
507
- the sliding pill to `999px` in one flag, no CSS needed (it drives `--tab-radius`, which feeds
508
- the tabs, the moving indicator, and the primary track well):
511
+ **Rounded corners.** The built-in **`round`** prop rounds the track, every tab, and the
512
+ sliding pill together. `true` makes them full pills; a number (px) or CSS length sets one
513
+ custom radius for all three:
509
514
 
510
515
  ```tsx
511
- <Tabs round defaultValue="a" label="Sections">…</Tabs> {/* pill track + tabs + indicator */}
516
+ <Tabs round defaultValue="a" label="Sections">…</Tabs> {/* pill track + tabs + indicator */}
517
+ <Tabs round={8} defaultValue="a" label="Sections">…</Tabs> {/* 8px on all three */}
512
518
  ```
@@ -1,54 +1,21 @@
1
1
  # Text
2
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.
3
+ Use Text for body copy, descriptions, and supporting information. Set `priority`
4
+ to emphasize important content or soften secondary details, and use `tone` to
5
+ add color. Text also styles any links inside it.
14
6
 
15
7
  ## Link behavior
16
8
 
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.
9
+ Links follow the surrounding text's emphasis and color. In supporting text,
10
+ they stay muted; hover makes them easier to identify. The examples show how
11
+ links look at each priority and tone.
36
12
 
37
13
  ## Demo
38
14
 
39
15
  ## Truncation and expansion
40
16
 
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
- JSX `Text` adds a `truncatedOnly` tooltip containing its text content. It
49
- opens only when the text clips. Nest a `<Tooltip>` to replace that tooltip;
50
- an empty nested tooltip suppresses it. `expandable` text has no automatic
51
- tooltip because its own control reveals the content.
17
+ Use `truncate` to keep long text compact. Pass a number to choose how many lines
18
+ to show. Readers can hover over shortened text to read the full content in a tooltip.
52
19
 
53
20
  ```tsx
54
21
  {/* Single line */}
@@ -59,10 +26,52 @@ tooltip because its own control reveals the content.
59
26
  <Text truncate={3}>{longSentence}</Text>
60
27
  ```
61
28
 
29
+ Add `expandable` to let readers reveal long text when they need it. The **Show more**
30
+ icon button appears only when the text does not fit. Add `collapsible` to let them fold it again.
31
+
32
+ ```tsx
33
+ {/* One-way: the chevron is hidden once expanded */}
34
+ <Text truncate expandable>{longSentence} <a href="/text/#link-behavior">Link guidance</a>.</Text>
35
+ <Text truncate={3} expandable>{longSentence} <a href="/text/#link-behavior">Link guidance</a>.</Text>
36
+
37
+ {/* Two-way: the reader can collapse it back */}
38
+ <Text truncate={3} expandable collapsible>{longSentence} <a href="/text/#link-behavior">Link guidance</a>.</Text>
39
+
40
+ {/* Make the folded preview a single expansion target */}
41
+ <Text truncate={3} expandable collapsible expandableInert>{longSentence} <a href="/text/#link-behavior">Link guidance</a>.</Text>
42
+ ```
43
+
44
+ Use `expandableInert` when readers should expand the text before following its
45
+ links. Selecting anywhere in the folded preview expands it; its links become
46
+ available after expansion.
47
+
48
+ ## Component props
49
+
50
+ | Prop | Type | Default | Description |
51
+ |------|------|---------|-------------|
52
+ | `collapsible?` | boolean | — | Allow refolding after expansion. The control stays visible, rotates its chevron, and switches between "Show more" and "Show less". Requires `expandable`. The expanded collapse target spans the full width over 16px of bottom padding; removing that padding with custom CSS allows it to overlap content. |
53
+ | `expandable?` | boolean | — | Show a fade and expansion control when truncated text overflows. Select the control or focus it and press Enter or Space to reveal all content. Expansion is one-way: the control hides once expanded. Add `collapsible` to allow refolding. Requires `truncate`; disables the automatic tooltip. The control comes first in Tab order. By default, folded text stays selectable and its links remain focusable and clickable. Single-line text fades at the inline end with an inline-end target; multiline text fades over its last line with a full-width target. Folded content clips without scrolling or extra padding, and expansion preserves its width. |
54
+ | `expandableInert?` | boolean | — | Make the entire folded, overflowing preview an expand target. JSX applies `inert` to its content, blocking focus, clicks, and text selection and removing it from the accessibility tree until expanded. Fitting text stays interactive, as does server-rendered content before browser measurement. Requires `expandable`. |
55
+ | `inline?` | boolean | — | Render as inline-block instead of the default block element. |
56
+ | `priority?` | 'primary' \| 'secondary' \| 'tertiary' \| 'quaternary' | secondary | Text emphasis, from `primary` (`--text-1`, strongest) to `quaternary` (`--text-4`). `secondary` uses `--text-2`; `tertiary` uses `--text-3`. Neutral primary/secondary links use `--link-color` and `--link-color-hover`. Tertiary/quaternary links inherit the text color, with `--text-2` and `--text-3` respectively on hover. |
57
+ | `size?` | 'small' \| 'medium' \| 'large' | medium | Font size / line height in pixels: `small` = 13/16, `medium` = 15/20, `large` = 17/24. |
58
+ | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | neutral | Color tint. `neutral` uses `--text-{N}`; named tones use `--text-{N}-{tone}`. A CSS color such as `rebeccapurple` keeps its hue, with lightness and chroma set per priority in oklch. With named tones, links inherit the text color and hover uses the next-stronger tone level; at `primary`, hover changes only the underline. |
59
+ | `truncate?` | boolean \| number | — | Clamp with a trailing ellipsis: `true` or `1` selects one line, integers ≥ 2 select that many lines, and `0` or negative values disable truncation. Clipped, non-expandable Text shows its content in an automatic tooltip. Nest `<Tooltip>` to replace it or an empty tooltip to suppress it. Uses CSS `-webkit-line-clamp` in `display: -webkit-box`, with `min-width: 0` for flex and grid layouts. |
60
+
61
+ ## Web Component
62
+
63
+ Use `<a-text>` directly for text priority and tone.
64
+
65
+ ```html
66
+ <a-text priority="secondary">
67
+ Read the <a href="/colors/">color guidance</a>.
68
+ </a-text>
69
+ ```
70
+
62
71
  ### Use a tooltip with the web component
63
72
 
64
- `<a-text>` does not add a tooltip. Nest `<a-tooltip truncated-only>` and pass
65
- the full text as its content:
73
+ Raw Text has no automatic tooltip. Nest `<a-tooltip truncated-only>` with the
74
+ full text:
66
75
 
67
76
  ```html
68
77
  <a-text truncate="1" style="max-width: 260px; cursor: help">
@@ -73,74 +82,89 @@ the full text as its content:
73
82
  </a-text>
74
83
  ```
75
84
 
76
- `a-text.isTruncated` is a read-only layout measurement. Read it on the UI
77
- thread when you compose custom truncation behavior.
85
+ Read `a-text.isTruncated` on the UI thread to check whether the clamp hides content.
86
+ This read-only layout measurement does not change markup or accessibility.
78
87
 
79
- Pair `expandable` with `truncate` to let the reader reveal the full text.
80
- The fade and chevron appear only when the clamped content overflows; text
81
- that fits gets neither. Single-line truncation fades on the right edge,
82
- multi-line over the last line. The chevron is a real `<button>` carrying
83
- `aria-expanded`: click it, or focus it and press Enter, to drop the clamp.
84
- `expandable` takes effect only with `truncate`.
88
+ ### Expand raw HTML
85
89
 
86
- Expanding is one-way by default, so the control is removed once the text
87
- is revealed. Add `collapsible` for a two-way toggle: the chevron stays
88
- visible while expanded (rotated up) and flips its label between "Show
89
- more" and "Show less". `collapsible` takes effect only with `expandable`.
90
+ Place a direct `a-button` before `a-text-content` to put expansion first in Tab
91
+ order. No application toggle handler is needed. Load both entries alongside your
92
+ shared tokens and styles:
90
93
 
91
- ```tsx
92
- {/* One-way: the chevron is removed once expanded */}
93
- <Text truncate expandable>{longSentence}</Text>
94
- <Text truncate={3} expandable>{longSentence}</Text>
94
+ ```ts
95
+ import '@antadesign/anta/elements/a-text'
96
+ import '@antadesign/anta/elements/a-button'
97
+ ```
95
98
 
96
- {/* Two-way: the reader can collapse it back */}
97
- <Text truncate={3} expandable collapsible>{longSentence}</Text>
99
+ ```html
100
+ <a-text truncate="1" expandable collapsible>
101
+ <a-button role="button" tabindex="0" priority="quaternary" paddingless></a-button>
102
+ <a-text-content>A long description that needs more than one line.</a-text-content>
103
+ </a-text>
98
104
  ```
99
105
 
100
- ## Component props
106
+ Expansion and accessible button labels work automatically. Your attributes and
107
+ children stay unchanged. Explicit button ARIA overrides the defaults;
108
+ synchronize any authored `aria-expanded` yourself.
101
109
 
102
- | Prop | Type | Default | Description |
103
- |------|------|---------|-------------|
104
- | `collapsible?` | boolean | — | Let the reader collapse back after expanding: the chevron becomes a "Show more" / "Show less" toggle that stays visible while expanded. Only takes effect together with `expandable`. |
105
- | `expandable?` | boolean | — | Show a fade hint and chevron over the truncated text and let the user expand it by clicking the chevron region or pressing Enter while the chevron has keyboard focus. Only takes effect together with `truncate`. On its own, expanding is **one-way** — the control is removed once expanded; add `collapsible` for a two-way toggle. |
106
- | `inline?` | boolean | — | Render as inline-block instead of the default block element. |
107
- | `priority?` | 'primary' \| 'secondary' \| 'tertiary' \| 'quaternary' | secondary | Visual priority. Maps to text-1..text-4 (`primary` = text-1, the strongest). The default is `secondary` (text-2) — body text reads a step softer than the strongest foreground; pass `primary` for emphasis. |
108
- | `size?` | 'small' \| 'medium' \| 'large' | medium | Type scale. `small` = 13/16, `medium` = 15/20, `large` = 17/24. |
109
- | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | neutral | Color tint. `neutral` (the default) is the untinted `--text-{N}` scale; a named tone applies the matching `--text-{N}-{tone}` palette. Any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) is a one-off custom tone — its hue is kept while lightness/chroma are pinned per priority in oklch. |
110
- | `truncate?` | boolean \| number | — | Truncate with a trailing ellipsis. `true` (or `1`) clamps to a single line; any integer ≥ 2 clamps to that many lines; `0` or a negative value means no truncation. A clipped, non-expandable JSX `Text` shows its text content in a tooltip by default. Nest a `<Tooltip>` to provide your own tooltip instead. Uses the `-webkit-line-clamp` technique, supported in all major browsers (Firefox 68+, Chrome, Safari, Edge). |
110
+ Migrate flat expandable raw markup to these two children. JSX supplies them for
111
+ you; plain Text needs no content wrapper.
111
112
 
112
- ## Web Component
113
+ For multiline raw Text, set `--line-clamp` in your stylesheet. JSX sets the line
114
+ count through the `truncate` prop:
113
115
 
114
- Use the web component directly when you are not using React or Preact and a native control does not fit.
116
+ ```html
117
+ <a-text class="summary" truncate="3" expandable collapsible>
118
+ <a-button role="button" tabindex="0" priority="quaternary" paddingless></a-button>
119
+ <a-text-content>A longer description to show across three lines.</a-text-content>
120
+ </a-text>
121
+ ```
122
+
123
+ ```css
124
+ a-text.summary { --line-clamp: 3; }
125
+ ```
115
126
 
116
- Use `<a-text>` directly for a block that carries the text priority and tone.
127
+ ### Make folded content inert
128
+
129
+ Raw authors manage `inert` on `a-text-content`. Add `expandable-inert` for a
130
+ full-preview expand target, and update `inert` from `textpresentationchange`:
117
131
 
118
132
  ```html
119
- <a-text priority="secondary">
120
- Read the <a href="/colors/">color guidance</a>.
133
+ <a-text id="summary" truncate="1" expandable collapsible expandable-inert>
134
+ <a-button role="button" tabindex="0" priority="quaternary" paddingless></a-button>
135
+ <a-text-content>A long description with <a href="/text/">a link</a>.</a-text-content>
121
136
  </a-text>
122
137
  ```
123
138
 
139
+ ```js
140
+ const text = document.querySelector('#summary')
141
+ const content = text.querySelector(':scope > a-text-content')
142
+ text.addEventListener('textpresentationchange', ({ detail }) => {
143
+ content.inert = detail.overflowing && !detail.expanded
144
+ })
145
+ ```
146
+
147
+ This non-bubbling notification reports `{ expanded, overflowing }` after
148
+ measurement or expansion. Late listeners receive the current snapshot
149
+ asynchronously. `overflowing` describes the folded preview, retained while
150
+ expanded. The element leaves `inert` to you.
151
+
124
152
  ## Styling
125
153
 
126
- Color comes from **`priority`** (the neutral text level) and **`tone`** (the tint);
127
- `size` and the type props set the rest. For a named tone, `Text` consumes the
128
- global **text role tokens** (`--text-1` … `--text-5`, the `-{tone}` tints,
129
- `--link-color`), so retuning those once on `:root` / `.dark` shifts text color
130
- everywhere (see [Colors](../colors.md)). A custom `tone` (any CSS color) is derived in
131
- oklch from **`--text-tone-source`** — the same one-off knob as `Button` / `Tag`.
154
+ Use `priority`, `tone`, and `size` for emphasis, color, and type scale. Shared
155
+ [Colors](../colors.md) keep Text consistent with your theme. For a custom color,
156
+ pass `tone` or set `--text-tone-source`.
132
157
 
133
158
  ```tsx
134
159
  <Text priority="secondary" tone="brand">Tinted secondary text</Text>
135
160
  <Text tone="#0d9488">Custom-tone body copy</Text>
136
161
  ```
137
162
 
138
- `<a-text>` is light-DOM, so one-offs are plain CSS on the element or its prose
139
- links. The `.fancy` class names the demo example:
163
+ Style the light-DOM host, content, control, and links with plain CSS. The `.fancy`
164
+ class belongs to this example:
140
165
 
141
166
  ```css
142
- /* teal body text via light-dark() (deep in light, bright in dark; Anta drives
143
- color-scheme from its theme toggle), with wider tracking */
167
+ /* Anta's theme controls color-scheme for light-dark(). */
144
168
  a-text.fancy {
145
169
  color: light-dark(#0f6e5f, #5fd0bb);
146
170
  font-size: 16px;