@antadesign/anta 0.3.28 → 0.3.29

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 (135) hide show
  1. package/NOTICES.md +32 -1
  2. package/dist/anta_helpers.d.ts +21 -0
  3. package/dist/anta_helpers.js +24 -0
  4. package/dist/box-observation.d.ts +8 -0
  5. package/dist/box-observation.js +41 -0
  6. package/dist/box-types.d.ts +2 -0
  7. package/dist/bundle.css +1 -1
  8. package/dist/bundle.js +64 -27
  9. package/dist/components/Box.d.ts +28 -12
  10. package/dist/components/Box.js +18 -11
  11. package/dist/components/Breadcrumbs.css +1 -1
  12. package/dist/components/Calendar.js +3 -5
  13. package/dist/components/Checkbox.d.ts +2 -2
  14. package/dist/components/Input.d.ts +8 -2
  15. package/dist/components/Input.js +4 -0
  16. package/dist/components/InputAutocomplete.js +3 -12
  17. package/dist/components/InputDate.css +1 -1
  18. package/dist/components/InputDate.js +2 -0
  19. package/dist/components/Menu.d.ts +4 -4
  20. package/dist/components/Menu.js +1 -1
  21. package/dist/components/Panel.d.ts +17 -0
  22. package/dist/components/Panel.js +22 -0
  23. package/dist/components/RadioGroup.d.ts +2 -1
  24. package/dist/components/Select.d.ts +14 -14
  25. package/dist/components/Select.js +47 -35
  26. package/dist/components/SelectFaceted.d.ts +17 -6
  27. package/dist/components/SelectFaceted.js +61 -41
  28. package/dist/components/Steps.css +1 -1
  29. package/dist/components/Switch.d.ts +3 -3
  30. package/dist/components/TabPanel.d.ts +3 -8
  31. package/dist/components/TabPanel.js +2 -1
  32. package/dist/components/Tabs.d.ts +2 -2
  33. package/dist/components/Tabs.js +2 -1
  34. package/dist/components/Tooltip.d.ts +1 -1
  35. package/dist/components/Tooltip.js +1 -1
  36. package/dist/components/select-parts.css +1 -1
  37. package/dist/elements/a-avatar.css +1 -1
  38. package/dist/elements/a-banner.css +1 -1
  39. package/dist/elements/a-box.css +1 -1
  40. package/dist/elements/a-box.d.ts +6 -6
  41. package/dist/elements/a-box.js +74 -23
  42. package/dist/elements/a-button.css +1 -1
  43. package/dist/elements/a-button.d.ts +9 -1
  44. package/dist/elements/a-button.js +64 -1
  45. package/dist/elements/a-calendar.css +1 -1
  46. package/dist/elements/a-capture.css +1 -1
  47. package/dist/elements/a-card.css +1 -1
  48. package/dist/elements/a-checkbox.css +1 -1
  49. package/dist/elements/a-checkbox.d.ts +3 -0
  50. package/dist/elements/a-checkbox.js +17 -1
  51. package/dist/elements/a-copy.css +1 -1
  52. package/dist/elements/a-dialog.css +1 -1
  53. package/dist/elements/a-expander.css +1 -1
  54. package/dist/elements/a-icon.css +1 -1
  55. package/dist/elements/a-icon.shapes.css +1 -1
  56. package/dist/elements/a-input-time.css +1 -1
  57. package/dist/elements/a-input-time.d.ts +8 -0
  58. package/dist/elements/a-input-time.js +115 -8
  59. package/dist/elements/a-input.css +1 -1
  60. package/dist/elements/a-input.d.ts +27 -1
  61. package/dist/elements/a-input.js +176 -35
  62. package/dist/elements/a-loader.css +1 -1
  63. package/dist/elements/a-menu-group.css +1 -1
  64. package/dist/elements/a-menu-item.css +1 -1
  65. package/dist/elements/a-menu-item.js +1 -0
  66. package/dist/elements/a-menu-separator.css +1 -1
  67. package/dist/elements/a-menu.css +1 -1
  68. package/dist/elements/a-menu.d.ts +8 -13
  69. package/dist/elements/a-menu.js +146 -45
  70. package/dist/elements/a-panel.css +1 -0
  71. package/dist/elements/a-panel.d.ts +16 -0
  72. package/dist/elements/a-panel.js +153 -0
  73. package/dist/elements/a-progress.css +1 -1
  74. package/dist/elements/a-radio-group.css +1 -1
  75. package/dist/elements/a-radio.css +1 -1
  76. package/dist/elements/a-radio.d.ts +4 -0
  77. package/dist/elements/a-radio.js +18 -0
  78. package/dist/elements/a-slider.css +1 -1
  79. package/dist/elements/a-slider.d.ts +1 -0
  80. package/dist/elements/a-slider.js +3 -3
  81. package/dist/elements/a-switch.css +1 -1
  82. package/dist/elements/a-switch.d.ts +3 -0
  83. package/dist/elements/a-switch.js +17 -1
  84. package/dist/elements/a-tab.css +1 -1
  85. package/dist/elements/a-tabpanel.css +1 -1
  86. package/dist/elements/a-tabs.css +1 -1
  87. package/dist/elements/a-tag.css +1 -1
  88. package/dist/elements/a-text.css +1 -1
  89. package/dist/elements/a-title.css +1 -1
  90. package/dist/elements/a-toast.css +1 -1
  91. package/dist/elements/a-toaster.css +1 -1
  92. package/dist/elements/a-tooltip.css +1 -1
  93. package/dist/elements/a-tooltip.js +30 -7
  94. package/dist/elements/focus.d.ts +9 -0
  95. package/dist/elements/focus.js +41 -0
  96. package/dist/elements/index.d.ts +1 -0
  97. package/dist/elements/index.js +3 -0
  98. package/dist/elements/panel-focus.d.ts +10 -0
  99. package/dist/elements/panel-focus.js +86 -0
  100. package/dist/elements/shadow-aria.d.ts +10 -0
  101. package/dist/elements/shadow-aria.js +155 -0
  102. package/dist/general_types.d.ts +48 -19
  103. package/dist/generate-icons.mjs +3 -0
  104. package/dist/index.d.ts +4 -1
  105. package/dist/index.js +2 -0
  106. package/dist/jsx-runtime.d.ts +10 -1
  107. package/dist/reset.css +1 -1
  108. package/dist/theme-antithesis.css +1 -1
  109. package/dist/theme-antune.css +1 -1
  110. package/dist/tokens.css +1 -1
  111. package/docs/accessibility.md +4 -2
  112. package/docs/changelog.md +82 -4
  113. package/docs/colors.md +6 -6
  114. package/docs/components/box.md +98 -34
  115. package/docs/components/breadcrumbs.md +33 -0
  116. package/docs/components/button.md +2 -2
  117. package/docs/components/capture.md +8 -8
  118. package/docs/components/checkbox.md +9 -4
  119. package/docs/components/input-autocomplete.md +6 -0
  120. package/docs/components/input-date.md +8 -2
  121. package/docs/components/input.md +99 -40
  122. package/docs/components/menu.md +3 -3
  123. package/docs/components/panel.md +189 -0
  124. package/docs/components/radio.md +4 -2
  125. package/docs/components/select-faceted.md +57 -2
  126. package/docs/components/select.md +63 -51
  127. package/docs/components/steps.md +4 -4
  128. package/docs/components/switch.md +12 -6
  129. package/docs/components/tabs.md +21 -8
  130. package/docs/components/tooltip.md +6 -4
  131. package/docs/index.md +1 -0
  132. package/docs/install-config.md +5 -4
  133. package/docs/normalization.md +3 -1
  134. package/docs/theming.md +1 -1
  135. package/package.json +4 -1
@@ -14,6 +14,12 @@ application-defined facet keys, not Anta terms. Each facet has one of four
14
14
  Use `value` with `onValueChange` to control it, or use `defaultValue` for an
15
15
  uncontrolled filter.
16
16
 
17
+ Without `searchable`, the trigger opens a menu of facets. With `searchable`, it
18
+ opens a dialog containing the search field and that menu. A filtered option
19
+ facet also opens a dialog containing its search field and options menu. Text and
20
+ custom facets use dialogs for their editable content. An unfiltered option facet
21
+ keeps the menu popup pattern.
22
+
17
23
  Pass `placement` to control where the root facet menu opens relative to its
18
24
  trigger. It uses the same values as `Menu` and still flips or clamps when space
19
25
  is limited. `offset` sets the gap in pixels between the trigger and the menu:
@@ -22,11 +28,32 @@ is limited. `offset` sets the gap in pixels between the trigger and the menu:
22
28
  <SelectFaceted placement="top-end" offset={8} facets={FACETS} />
23
29
  ```
24
30
 
31
+ Open **Metadata** in the first demo to choose key–value tags such as
32
+ **env production** or **region us-east**. Its `renderOption` callback reads
33
+ custom option fields and renders a `Tag` with `label` and `value`. Search
34
+ for **production** to see the same tag in global results.
35
+
25
36
  ```tsx
26
37
  const FACETS = [
27
38
  { key: 'assignee', label: 'Assignee', kind: 'multiple', filter: true, options: people },
28
39
  { key: 'owner', label: 'Owner', kind: 'single', filter: true, options: people },
29
40
  { key: 'status', label: 'Status', kind: 'single', options: [{ value: 'open', label: 'Open' }, /* … */] },
41
+ {
42
+ key: 'metadata',
43
+ label: 'Metadata',
44
+ kind: 'multiple',
45
+ icon: 'tag',
46
+ filter: true,
47
+ options: [
48
+ { value: 'env:production', label: 'env: production', dataKey: 'env', dataValue: 'production' },
49
+ { value: 'env:staging', label: 'env: staging', dataKey: 'env', dataValue: 'staging' },
50
+ { value: 'region:us-east', label: 'region: us-east', dataKey: 'region', dataValue: 'us-east' },
51
+ { value: 'team:platform', label: 'team: platform', dataKey: 'team', dataValue: 'platform' },
52
+ ],
53
+ renderOption: (option) => (
54
+ <Tag label={String(option.dataKey)} value={String(option.dataValue)} />
55
+ ),
56
+ },
30
57
  { key: 'title', label: 'Title contains', kind: 'text' },
31
58
  { key: 'duration', label: 'Min duration', kind: 'custom', summary: (v) => `≥ ${v.min}s`, render: /* … */ },
32
59
  ]
@@ -68,6 +95,7 @@ function Demo() {
68
95
  selection={facet.kind}
69
96
  indicator={facet.kind === 'single' ? 'check' : undefined}
70
97
  options={facet.options}
98
+ renderOption={facet.renderOption}
71
99
  value={value[facet.key]}
72
100
  onValueChange={(v) => setFacet(facet.key, v)}
73
101
  leading={`${facet.label}:`}
@@ -120,6 +148,27 @@ The trigger opens the facet menu. The example renders one editable
120
148
  [Select](./select.md) below it for each active facet. Each Select receives the
121
149
  facet label through `leading`, which displays it as a prefix.
122
150
 
151
+ ### Custom option content
152
+
153
+ Pass `renderOption(option, state)` on a `single` or `multiple` facet to replace
154
+ each option's label, hint, and icon layout. It runs in both the facet flyout and
155
+ global search results. `state` contains `value`, `selected`, and `disabled`.
156
+ Selection handling, indicators, and option styling remain on the row.
157
+ Return `null` to use the default layout for an option.
158
+
159
+ The first demo's **Metadata** facet stores the key and value in custom option
160
+ fields. Pass them to Tag's `label` and `value` props:
161
+
162
+ ```tsx
163
+ renderOption: (option) => (
164
+ <Tag label={String(option.dataKey)} value={String(option.dataValue)} />
165
+ )
166
+ ```
167
+
168
+ Filtering still uses `value`, `label`, and `hint`, or the facet's custom `filter`
169
+ function. Keep searchable text in those fields. Single-facet summaries still
170
+ use the option label, and multiple-facet summaries show the selected count.
171
+
123
172
  ### Value and changes
124
173
 
125
174
  The value is a record keyed by facet key: `{ [facetKey]: value }`. Its shape
@@ -373,12 +422,12 @@ const recency: SelectFacetCustom<Recency> = {
373
422
  | `onValueChange?` | (value, attrs) => void | — | Fires after any facet changes. `value` is the whole new record (a facet key → that facet's value; a cleared facet is absent). `attrs` says what changed: `{ facet, kind, value }` for a single facet edit, or `{ all: true }` for the "Clear all" row — narrow on `'all' in attrs` before reading `facet`. |
374
423
  | `placement?` | 'left' \| 'right' \| 'bottom' \| 'top' \| 'bottom-start' \| 'bottom-end' \| 'top-start' \| 'top-end' \| 'right-start' \| 'right-end' \| 'left-start' \| 'left-end' | bottom-start | Preferred placement of the root filter menu relative to its trigger. The menu auto-flips vertically and clamps horizontally when needed. |
375
424
  | `priority?` | 'primary' \| 'secondary' | secondary | Default trigger's button priority. |
376
- | `renderTrigger?` | (state) => ReactNode | — | Replaces the default `Button` with a trigger returned from this function. Receives a `SelectFacetedTriggerState`. Return exactly one focusable element: the menu is positioned relative to that element and opens when it is clicked. Add `aria-haspopup="menu"` and `aria-expanded={state.open}` to the returned element, on a role that supports them (an Anta `Button` already carries `role="button"`; otherwise add `role="combobox"`). `className`, `style`, and other trigger props apply only to the default Button, so add styling and attributes to the returned element. |
425
+ | `renderTrigger?` | (state) => ReactNode | — | Replaces the default `Button` with a trigger returned from this function. Receives a `SelectFacetedTriggerState`. Return exactly one focusable element: the menu is positioned relative to that element and opens when it is clicked. Add `aria-haspopup={searchable ? 'dialog' : 'menu'}` and `aria-expanded={state.open}` to the returned element, on a role that supports them (an Anta `Button` already carries `role="button"`; otherwise add `role="combobox"`). `className`, `style`, and other trigger props apply only to the default Button, so add styling and attributes to the returned element. |
377
426
  | `searchable?` | boolean | — | Adds a search field at the top of the root menu. It searches the options of every `single` and `multiple` facet in one list. For example, "alice" can appear under application-defined Assignee and Owner facets. `text` and `custom` facets remain available when the search is empty. Each facet uses its `filter` function when supplied, or the built-in substring match. |
378
427
  | `searchPlaceholder?` | string | Filter… | Placeholder for the global search field. |
379
428
  | `size?` | 'small' \| 'medium' \| 'large' | medium | Default trigger's button size. |
380
429
  | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | — | Default option-row tone in facet flyouts. An option's own `tone` wins. |
381
- | `toneScope?` | ToneScope | 'all' | Apply the default row tone in every state, or only to selected rows. An option's own `toneScope` wins. |
430
+ | `toneScope?` | 'all' \| 'selected' | 'all' | Apply the default row tone in every state, or only to selected rows. An option's own `toneScope` wins. |
382
431
  | `value?` | SelectFacetedValue | — | Controlled value record, keyed by facet. When provided, update it through `onValueChange`. Leave it undefined for uncontrolled use. |
383
432
 
384
433
  ### The `SelectFacet` type
@@ -391,6 +440,12 @@ properties as [Select](./select.md). They use `kind` in place of Select's
391
440
  `selection`. `text` and `custom` do not use Select options. A `single` facet
392
441
  returns one option value; a `multiple` facet returns an array of option values.
393
442
 
443
+ When `selectAll` is enabled, Alt-clicking an option in a `multiple` facet clears
444
+ the other values and selects that option. Use Option-click on macOS. After a
445
+ 700ms hover, a tooltip beside the row describes the shortcut. Moving to another
446
+ row restarts the delay. An option's `tooltip` replaces that hint; set it
447
+ to `''` to hide the hint.
448
+
394
449
  | Field | Type | Description |
395
450
  |---|---|---|
396
451
  | `key` | `string` | The key for this facet's value in the record. `attrs.facet` returns it. It must be unique in `facets`. |
@@ -4,7 +4,7 @@
4
4
  field shows the selected value. Click the field to open the menu. The menu
5
5
  closes when a user clicks outside it or presses Esc.
6
6
 
7
- Select uses an [Input](./input.md) for its field and a [Menu](./menu.md) for its
7
+ Select uses an [Anta Button](./button.md) for its field and a [Menu](./menu.md) for its
8
8
  dropdown. Pass `options` as an array of strings or objects. Use `value` with
9
9
  **`onValueChange`** to control the selection, or use `defaultValue` for an
10
10
  uncontrolled Select.
@@ -70,7 +70,8 @@ its box goes mixed when only some are on. Pass **`selectAll={false}`** to drop i
70
70
 
71
71
  In a multi-select, **`Alt`+Click** a row to clear every other selection and
72
72
  select that row. On macOS, use Option instead of Alt.
73
- The standard tooltip describes this shortcut when the pointer is over a row.
73
+ After hovering for 700ms, the standard tooltip describes this shortcut beside
74
+ the row. Moving to another row restarts the delay.
74
75
  Set `SelectOption.tooltip` to replace it, or to `''` to hide it. The shortcut
75
76
  is available only when `selectAll` is enabled, so `selectAll={false}` removes it.
76
77
 
@@ -105,12 +106,14 @@ option's **value, label, or hint**. It's **whitespace-flexible** (a typed space
105
106
  matches any run of whitespace) and **bolds the matched substring** in the results.
106
107
  Pass a **function** `(option, query) => boolean` for custom matching (no highlight).
107
108
 
108
- With `filter`, Select behaves as a **combobox**. Opening it focuses the search
109
- field. ↑ and ↓ move through matching options while focus
110
- stays in the field. Enter selects the active option, and Esc
111
- closes the menu. The search field stays visible while the results scroll. A
112
- "No matches" row appears when nothing matches, and the query resets when the
113
- menu closes. In `multiple` mode, `selectAll` affects only the visible matches.
109
+ With `filter`, the trigger opens a dialog containing the search field and an
110
+ options menu. This keeps the textbox outside the menu in the accessibility tree.
111
+ Opening the dialog focuses the search field. ↑ and ↓ move
112
+ through matching options while focus stays in that field. Enter
113
+ selects the active option, and Esc closes the dialog. The search field
114
+ stays visible while the results scroll. A "No matches" row appears when nothing
115
+ matches, and the query resets when the dialog closes. In `multiple` mode,
116
+ `selectAll` affects only the visible matches.
114
117
 
115
118
  ## Value and changes
116
119
 
@@ -229,7 +232,7 @@ section marks the leaf everywhere it occurs (selection is value-keyed).
229
232
  <Select status="info" statusIcon="sparkles" hint="Custom glyph" options={OPTIONS} defaultValue="stream" />
230
233
  ```
231
234
 
232
- The trigger is an [Input](./input.md), so it inherits the field props.
235
+ The trigger is an [Anta Button](./button.md) styled as a field.
233
236
  **`size`** (`small` · `medium` · `large`) sets the height and type scale.
234
237
  **`status`** paints a validation tone on the border, `hint`, and chevron, and
235
238
  prefixes a glyph (`info` / `success` / `warning` / `critical`); **`statusIcon`**
@@ -245,7 +248,7 @@ the glyph and, for `critical`, validity. See the
245
248
  ## Customization
246
249
 
247
250
  Select has no `<a-select>` element. The React and Preact wrapper coordinates an
248
- Input and a Menu. You can use the wrapper, render custom option content with
251
+ Anta Button and a Menu. You can use the wrapper, render custom option content with
249
252
  `renderOption`, replace its selection mark with `renderIndicator`, or build the
250
253
  same interaction from the raw elements.
251
254
 
@@ -259,22 +262,21 @@ uses the same values as `Menu` and still flips or clamps when space is limited.
259
262
 
260
263
  ### Build it from elements
261
264
 
262
- The wrapper renders a read-only `<Input>` and a `<Menu>`. It updates the field
265
+ The wrapper renders a `<Button>` immediately followed by a `<Menu>`, with a label
266
+ and hint inside an `<a-select-field>` layout wrapper. It updates the field
263
267
  and each option's `selected` state from the value, and reports a pick through
264
268
  `onValueChange`. Menu handles positioning, dismissal, and keyboard navigation.
265
269
 
266
270
  The following example shows the equivalent markup without React or Preact. Place
267
- the Menu directly after the Input so it can use that Input as its trigger.
271
+ the Menu directly after the Button so it can use that Button as its trigger.
268
272
 
269
273
  ```html
270
274
  <!-- `.select` scopes this example's CSS. Use an application selector instead. -->
271
275
  <div class="select" style="width: 240px">
272
- <!-- Read-only field trigger; `dim-actions` dims the chevron at rest. The
273
- trailing slot supplies the inline inset (--input-trailing-inset); the
274
- transition on the chevron matches <Select>. -->
275
- <a-input readonly dim-actions value="stream" aria-haspopup="menu">
276
- <a-icon slot="trailing" shape="chevron-down" style="transition: transform 150ms ease"></a-icon>
277
- </a-input>
276
+ <a-button role="button" tabindex="0" aria-label="Field" aria-haspopup="menu" aria-expanded="false">
277
+ <a-button-label>stream</a-button-label>
278
+ <a-icon shape="chevron-down" aria-hidden="true" style="transition: transform 150ms ease"></a-icon>
279
+ </a-button>
278
280
  <a-menu role="menu">
279
281
  <a-menu-item role="menuitemradio" tabindex="0" aria-checked="false" value="output_text"><a-menu-item-label>output_text</a-menu-item-label></a-menu-item>
280
282
  <a-menu-item role="menuitemradio" tabindex="0" aria-checked="true" value="stream" selected><a-menu-item-label>stream</a-menu-item-label></a-menu-item>
@@ -284,12 +286,14 @@ the Menu directly after the Input so it can use that Input as its trigger.
284
286
  <script type="module">
285
287
  import '@antadesign/anta/elements'
286
288
  const root = document.querySelector('.select')
287
- const field = root.querySelector('a-input')
289
+ const field = root.querySelector('a-button')
288
290
  const menu = root.querySelector('a-menu')
289
291
  const chevron = field.querySelector('a-icon')
290
292
  // Flip the chevron with the menu's open state (as <Select> does).
291
293
  menu.addEventListener('statechange', (e) => {
292
- chevron.style.transform = e.detail.next === 'open' ? 'rotate(180deg)' : ''
294
+ const open = e.detail.next === 'open'
295
+ field.setAttribute('aria-expanded', String(open))
296
+ chevron.style.transform = open ? 'rotate(180deg)' : ''
293
297
  })
294
298
  // On pick: mark the row selected + reflect its label into the field.
295
299
  menu.addEventListener('click', (e) => {
@@ -300,12 +304,16 @@ the Menu directly after the Input so it can use that Input as its trigger.
300
304
  el.toggleAttribute('selected', selected)
301
305
  el.setAttribute('aria-checked', String(selected))
302
306
  })
303
- field.value = item.querySelector('a-menu-item-label').textContent
307
+ field.querySelector('a-button-label').textContent = item.querySelector('a-menu-item-label').textContent
304
308
  })
305
309
  </script>
306
310
  ```
307
311
 
308
312
  The React and Preact `Select` wrapper performs those updates for you.
313
+ Because the Menu directly follows the Anta Button, it associates the focused
314
+ field with the popup through a direct ARIA element reference. No popup ID is
315
+ required. If a custom composition supplies its own `aria-controls`, Anta keeps
316
+ that authored relationship.
309
317
 
310
318
  ### Custom option rendering
311
319
 
@@ -461,7 +469,7 @@ empty selection still shows the `placeholder` — and takes the resolved `select
461
469
  options (`selected.length` is the count). Return `undefined` for any case you'd rather
462
470
  leave to the default, as above for the single-label case.
463
471
 
464
- Return a **string**: it flows into the same read-only field the default uses, so a
472
+ Return a **string**: it flows into the same Button label the default uses, so a
465
473
  long summary **ellipsizes at the field's width** the way a long value does
466
474
  (`Engineering, Design, … `). For rich content — chips, several nodes — reach for
467
475
  `renderTrigger` below, which replaces the whole field.
@@ -523,7 +531,8 @@ focusable element, such as the Anta `Button` in this example. Select positions
523
531
  the menu relative to that element and opens it when it is clicked. A fragment,
524
532
  multiple sibling elements, or a non-focusable wrapper prevents Select from finding
525
533
  the trigger. It logs a console warning when that happens. Add
526
- `aria-haspopup="menu"` and `aria-expanded={state.open}` to the element.
534
+ `aria-haspopup={filter ? 'dialog' : 'menu'}` and
535
+ `aria-expanded={state.open}` to the element.
527
536
 
528
537
  `state` includes `open`, `value`, `selected`, `disabled`, and `icon`. `selected`
529
538
  is the resolved option list, so `selected.length` is the multi-select count. The
@@ -633,11 +642,11 @@ scannable list instead of hiding matches behind flyouts.
633
642
  | `defaultValue?` | V \| V[] | — | Initial selected option value for uncontrolled use. |
634
643
  | `disabled?` | boolean | — | Disable the whole select. |
635
644
  | `filter?` | boolean \| (option, query) => boolean | — | Add a search field at the top of the menu that filters the options as you type. `true` uses the built-in matcher — a case-insensitive substring of the option's **value / label / hint**. Pass a **function** `(option, query) => boolean` for custom matching (called per option; return `true` to keep it). |
636
- | `hint?` | string | — | Helper text under the field (Input's `hint`). |
637
- | `icon?` | IconShape | — | Leading icon shown at the left of the field (the default trigger's `Input` `leading` slot). With a custom `renderTrigger`, it's passed through as `state.icon` instead — the consumer places it. |
645
+ | `hint?` | string | — | Helper text under the field. |
646
+ | `icon?` | IconShape | — | Leading icon shown at the left of the field. With a custom `renderTrigger`, it's passed through as `state.icon` instead — the consumer places it. |
638
647
  | `indicator?` | 'none' \| 'check' \| 'radio' | none | The per-row mark for **single**-select: `'none'` (a tint-only highlight), `'check'` (a trailing checkmark on the selected row, keeping the tint — the canonical Select look), or `'radio'` (a leading radio on every row). Multi-select always uses checkboxes. |
639
- | `label?` | string | — | Field label, above the trigger (Input's `label`). |
640
- | `leading?` | ReactNode | — | Content for the default trigger's `leading` slot, such as a key prefix before the value. It replaces the icon derived from `icon`. Include an `<Icon>` in this content when both are needed. Ignored by `renderTrigger`. |
648
+ | `label?` | string | — | Field label, above the trigger. |
649
+ | `leading?` | ReactNode | — | Content before the default trigger's value, such as a key prefix before the value. It replaces the icon derived from `icon`. Include an `<Icon>` in this content when both are needed. Ignored by `renderTrigger`. |
641
650
  | `offset?` | number | 4 | Gap in pixels between the trigger and the options menu. |
642
651
  | `onValueChange?` | (value, attrs) => void | — | Fires after the selection changes, with the new value and a `{ value, option }` snapshot. Select has no discrete element state, so there is no cancelable `onStateChange` (see the Input event-model note). |
643
652
  | `placeholder?` | string | — | Text shown when nothing is selected. |
@@ -645,19 +654,19 @@ scannable list instead of hiding matches behind flyouts.
645
654
  | `renderEmpty?` | (state) => ReactNode | — | Render content in the menu body when the (filtered) option list is empty — a "no results" message, a loading indicator (gated on your own external loading state), or a "create from the query" row. Receives an `EmptyState` (`query`, trimmed). There is no built-in empty message: when omitted, an empty list renders nothing. Whatever you return goes where the option rows would — a plain node is inert; return a `MenuItem` (e.g. a "Create" row) to make it focusable and selectable. |
646
655
  | `renderIndicator?` | (state) => ReactNode | — | Replace each row's selection **mark** with your own node, drawn at the leading edge. The row stays the control (`role` + `aria-checked` from `indicator` / `selection`); only the drawn mark changes, so pair it with an `indicator` (`'check'` / `'radio'`) or `selection="multiple"` for the semantics. Composes with `renderOption`. |
647
656
  | `renderOption?` | (option, state) => ReactNode | — | Replaces the built-in `label`, `hint`, and `icon` layout for each option row. Select still supplies the row container, click handling, ARIA attributes, and selection indicator. Read extra option fields through `SelectOption`'s index signature. `state` contains `value`, `selected`, and `disabled`. Filtering still matches the option's `value`, `label`, and `hint`, but Select cannot highlight matches within the returned content. |
648
- | `renderSummary?` | (selected) => string \| undefined | — | `multiple` only: build the trigger's selection summary text yourself, replacing the built-in "`All` / one label / `N selected`" logic. Receives the resolved selected options (`selected.length` is the count) and runs only while something is selected — an empty selection still shows the `placeholder`. Return a **string**: it flows into the default trigger's read-only field, so a long summary ellipsizes at the field's width just like a long value (`Engineering, Design, … `). Return `undefined` to fall back to the default for that case (e.g. customize only the count, keeping the single-label case built-in). For rich content (chips, multiple nodes) use `renderTrigger`, which replaces the whole field. |
649
- | `renderTrigger?` | (state) => ReactNode | — | Replaces the default field with a trigger returned from this function. Receives `open`, `value`, `selected`, `disabled`, and `icon`. Return exactly one focusable element, such as an Anta `Button`. The menu is positioned relative to that element and opens when it is clicked. Do not return a fragment, multiple siblings, or a non-focusable wrapper. Add `aria-haspopup="menu"` and `aria-expanded={state.open}` to the element, on a role that supports them (an Anta `Button` already carries `role="button"`; otherwise add `role="combobox"`). Field props (`label`, `hint`, `size`, `status`, `placeholder`, and `round`) and `className` / `style` apply only to the default field. Add styling and attributes to the returned element instead. |
657
+ | `renderSummary?` | (selected) => string \| undefined | — | `multiple` only: build the trigger's selection summary text yourself, replacing the built-in "`All` / one label / `N selected`" logic. Receives the resolved selected options (`selected.length` is the count) and runs only while something is selected — an empty selection still shows the `placeholder`. Return a **string**: it flows into the default trigger's Button label, so a long summary ellipsizes at the field's width just like a long value (`Engineering, Design, … `). Return `undefined` to fall back to the default for that case (e.g. customize only the count, keeping the single-label case built-in). For rich content (chips, multiple nodes) use `renderTrigger`, which replaces the whole field. |
658
+ | `renderTrigger?` | (state) => ReactNode | — | Replaces the default field with a trigger returned from this function. Receives `open`, `value`, `selected`, `disabled`, and `icon`. Return exactly one focusable element, such as an Anta `Button`. The menu is positioned relative to that element and opens when it is clicked. Do not return a fragment, multiple siblings, or a non-focusable wrapper. Add `aria-haspopup={filter ? 'dialog' : 'menu'}` and `aria-expanded={state.open}` to the returned button. An Anta `Button` already carries the correct role. Field props (`label`, `hint`, `size`, `status`, `placeholder`, and `round`) and `className` / `style` apply only to the default field. Add styling and attributes to the returned element instead. |
650
659
  | `round?` | boolean \| number \| string | — | Round the field corners — `true` for fully round, or a number / CSS length. |
651
660
  | `selectAll?` | boolean | true | `multiple` only: shows a "Select all" row that toggles every enabled option, or only the visible options when a filter query is active. Its checkbox is mixed when some options are selected. It is on by default. Set it to `false` to remove the row and the Alt/Option-click shortcut that selects only one row. |
652
661
  | `selectAllLabel?` | string | Select all | Label for the `selectAll` row. |
653
662
  | `selection?` | 'single' \| 'multiple' | single | Selection mode. `'single'` (the default) keeps `value` a single value and closes the menu on pick. Switch to `'multiple'` for checkboxes + an array value. |
654
663
  | `size?` | 'small' \| 'medium' \| 'large' | medium | Field size. |
655
- | `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | neutral | Validation/feedback tone for the field (Input's `status`). |
656
- | `statusIcon?` | (string & {}) \| false \| IconShape | — | Glyph shown before the `hint` when `status` is set (Input's `statusIcon`). Each status has a default; pass a shape to override, or `false` to drop it. |
664
+ | `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | neutral | Validation/feedback tone for the field. |
665
+ | `statusIcon?` | (string & {}) \| false \| IconShape | — | Glyph shown before the `hint` when `status` is set. Each status has a default; pass a shape to override, or `false` to drop it. |
657
666
  | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | — | Default option-row tone. An option's own `tone` wins. A named tone or a custom CSS color. Most visible with tint-based marks (`indicator` `'none'` / `'check'`); with `'radio'` / `'checkbox'` it also tones the indicator. |
658
- | `toneScope?` | ToneScope | 'all' | Apply the default row tone in every state, or only to selected rows. An option's own `toneScope` wins. |
667
+ | `toneScope?` | 'all' \| 'selected' | 'all' | Apply the default row tone in every state, or only to selected rows. An option's own `toneScope` wins. |
659
668
  | `value?` | V \| V[] | — | Controlled value: the selected option's `value`. Update it through `onValueChange`. Leave it undefined for uncontrolled use. |
660
- | `verbose?` | boolean | — | `multiple` only: spell the picks out in the count summary — `3 selected: A, B, C` (labels comma-joined) in place of the bare `3 selected`. Applies to the multi-count case only: `All` stays `All`, a single pick stays its own label, and an empty selection stays the `placeholder`. The list flows into the read-only field, so it ellipsizes at the field's width when long (`3 selected: Engineering, Des… `). `renderSummary` overrides this. |
669
+ | `verbose?` | boolean | — | `multiple` only: spell the picks out in the count summary — `3 selected: A, B, C` (labels comma-joined) in place of the bare `3 selected`. Applies to the multi-count case only: `All` stays `All`, a single pick stays its own label, and an empty selection stays the `placeholder`. The list flows into the Button label, so it ellipsizes at the field's width when long (`3 selected: Engineering, Des… `). `renderSummary` overrides this. |
661
670
 
662
671
  ### The `SelectItem` type
663
672
 
@@ -757,16 +766,16 @@ inline `SelectGroup`, and a flyout `SelectSubmenu` (whose items are themselves
757
766
 
758
767
  Use the web component directly when you are not using React or Preact and a native control does not fit.
759
768
 
760
- `Select` has no host element. Compose a read-only `<a-input>` trigger with an
769
+ `Select` uses a layout wrapper. Compose an `<a-button>` trigger with an
761
770
  `<a-menu>` of options. The menu owns opening and dismissal; the short controller
762
771
  reflects a picked option into the field and selected row.
763
772
 
764
773
  ```html
765
774
  <div data-anta-composition="select" style="display: grid; gap: 4px; width: 280px">
766
- <a-input readonly value="stream">
767
- <span slot="label">Output</span>
768
- <span slot="trailing"><a-icon shape="chevron-down" aria-hidden="true"></a-icon></span>
769
- </a-input>
775
+ <a-button role="button" tabindex="0" aria-label="Output" aria-haspopup="menu" aria-expanded="false">
776
+ <a-button-label>stream</a-button-label>
777
+ <a-icon shape="chevron-down" aria-hidden="true"></a-icon>
778
+ </a-button>
770
779
  <a-menu role="menu">
771
780
  <a-menu-item role="menuitem" tabindex="0" value="output_text"><a-menu-item-label>output_text</a-menu-item-label></a-menu-item>
772
781
  <a-menu-item role="menuitem" tabindex="0" value="stream" selected><a-menu-item-label>stream</a-menu-item-label></a-menu-item>
@@ -776,8 +785,11 @@ reflects a picked option into the field and selected row.
776
785
  import '@antadesign/anta/elements'
777
786
 
778
787
  const root = document.querySelector('[data-anta-composition="select"]')
779
- const field = root.querySelector('a-input')
788
+ const field = root.querySelector('a-button')
780
789
  const menu = root.querySelector('a-menu')
790
+ menu.addEventListener('statechange', (event) => {
791
+ field.setAttribute('aria-expanded', String(event.detail.next === 'open'))
792
+ })
781
793
 
782
794
  menu.addEventListener('click', (event) => {
783
795
  const item = event.target instanceof Element ? event.target.closest('a-menu-item') : null
@@ -786,7 +798,7 @@ reflects a picked option into the field and selected row.
786
798
  const selected = option === item
787
799
  option.toggleAttribute('selected', selected)
788
800
  })
789
- field.value = item.querySelector('a-menu-item-label').textContent.trim()
801
+ field.querySelector('a-button-label').textContent = item.querySelector('a-menu-item-label').textContent.trim()
790
802
  })
791
803
  </script>
792
804
  ```
@@ -824,17 +836,18 @@ corners.
824
836
 
825
837
  ## Styling
826
838
 
827
- Select is an [Input](./input.md) plus a [Menu](./menu.md), so it
828
- inherits both surfaces' hooks. Reach for props first, then plain CSS or `::part` for
839
+ Select composes an [Anta Button](./button.md) and a [Menu](./menu.md). Reach for props first, then plain CSS or `::part` for
829
840
  the rest. Don't override an element's internal `--*` output tokens.
830
841
 
831
842
  **Selection color** routes through `tone`: set it per option or on the Select as a
832
843
  default, then use `toneScope="selected"` for chosen rows only. It takes a named tone or any CSS color (a custom color keeps
833
844
  its hue, with lightness pinned to the brand text).
834
845
 
835
- **The field** is styled through Input's props (`size`, `status`, `round`) and its
836
- `::part`s; set its width with `style` / `className` (forwarded to the field). **The
837
- popover** takes Menu's `::part(menu)`.
846
+ **The field** takes `size`, `status`, and `round`. Set its width with `style` /
847
+ `className`, forwarded to the `<a-select-field>` wrapper. Style the trigger through
848
+ `a-select-field > a-button` and its `a-button-label`. The trigger uses
849
+ `:focus-visible` for its focus ring and ellipsizes long values. **The popover**
850
+ takes Menu's `::part(menu)`.
838
851
 
839
852
  ```tsx
840
853
  // Tone the selection; give the trigger a width.
@@ -844,16 +857,15 @@ popover** takes Menu's `::part(menu)`.
844
857
  **Borderless trigger.** The field's border is an inset `box-shadow`, so you can drop
845
858
  it *at rest* and let the standard hover / focus shadow return on its own; no need to
846
859
  re-declare it. Pair that with hiding the chevron for a field that reads as plain text
847
- until you reach for it. Hide the chevron by targeting the trailing `a-icon` (its slot
848
- wrapper is inline-`display:contents`, so hit the icon itself).
860
+ until you reach for it. Hide the chevron by targeting `a-select-chevron`.
849
861
 
850
862
  ```css
851
863
  /* Drop the resting border-shadow; the standard hover / focus shadow still shows. */
852
- .ghost-select a-input:not(:hover):not(:focus-within)::part(field) {
864
+ .ghost-select a-select-field > a-button:not(:hover):not(:focus-visible) {
853
865
  box-shadow: none;
854
866
  }
855
867
  /* Center the chosen label in the field. */
856
- .ghost-select a-input::part(input) { text-align: center; }
857
- /* The chevron's slot wrapper is inline display:contents; hide the icon itself. */
858
- .ghost-select a-input [slot="trailing"] a-icon { display: none; }
868
+ .ghost-select a-select-field > a-button > a-button-label { text-align: center; }
869
+ /* Hide the chevron. */
870
+ .ghost-select a-select-chevron { display: none; }
859
871
  ```
@@ -260,10 +260,10 @@ Events match [`Tabs`](./tabs.md#events).
260
260
  | `onStateChange?` | (event, detail) => void | — | Fired whenever the active tab changes — event-first. `detail` is `{ next, prev }` (values; `null` = none). Cancelable: `event.preventDefault()` vetoes it (uncontrolled), or in controlled mode answer by updating `value`. |
261
261
  | `onValueChange?` | (event, attrs) => void | — | Like `onChange`, but with a `{ value }` snapshot as the 2nd argument. |
262
262
  | `orientation?` | 'horizontal' \| 'vertical' | 'horizontal' | Layout + arrow-key axis. Horizontal ellipsizes labels when tabs overflow (scroll is opt-in via CSS); vertical stacks them. |
263
- | `priority?` | StepPriority | 'secondary' | Visual emphasis. Primary uses a solid selected marker and a stronger completed connector; secondary is the outlined default; tertiary is compact and borderless. |
263
+ | `priority?` | 'primary' \| 'secondary' \| 'tertiary' | 'secondary' | Visual emphasis. Primary uses a solid selected marker and a stronger completed connector; secondary is the outlined default; tertiary is compact and borderless. |
264
264
  | `renderMarker?` | (option, state) => ReactNode | — | Builds a custom marker from a step and its current state. A returned node replaces `marker` and the built-in state marker; return `undefined` to use those fallbacks, or `null` for an empty ring. |
265
265
  | `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). |
266
- | `tone?` | StepTone \| '' | 'neutral' | Tone applied to every step without its own `tone`. Error steps stay critical and disabled steps stay neutral. |
266
+ | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| '' | 'neutral' | Tone applied to every step without its own `tone`. Error steps stay critical and disabled steps stay neutral. |
267
267
  | `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. |
268
268
 
269
269
  ## Step option props
@@ -271,14 +271,14 @@ Events match [`Tabs`](./tabs.md#events).
271
271
  | Prop | Type | Default | Description |
272
272
  |------|------|---------|-------------|
273
273
  | `label` | ReactNode | — | Visible phase label. It ellipsizes in a constrained step; its full content is available in a tooltip only when clipped. |
274
- | `state` | StepState | — | Application-owned process state. Selection and availability are separate; `error` keeps a critical icon and outline, and selection adds the fill. |
274
+ | `state` | 'incomplete' \| 'loading' \| 'completed' \| 'error' | — | Application-owned process state. Selection and availability are separate; `error` keeps a critical icon and outline, and selection adds the fill. |
275
275
  | `value` | string | — | Stable phase identity. Values must be unique within the sequence. |
276
276
  | `className?` | string | — | CSS class on the option's rendered row. |
277
277
  | `disabled?` | boolean | — | Disables this phase. A custom `marker` or `renderMarker` result is kept; otherwise it uses the incomplete marker. |
278
278
  | `hint?` | ReactNode | — | Secondary text shown below the label. It ellipsizes in a constrained step; its full content is available in a tooltip only when clipped. |
279
279
  | `marker?` | StepMarker | — | Replaces the marker derived from `state`. A number is shown directly; an Anta icon shape is rendered as an `<Icon>`. |
280
280
  | `style?` | CSSProperties | — | Inline styles on the option's rendered row. |
281
- | `tone?` | StepTone \| '' | inherits Steps `tone | Tone for this phase. It colors the label, hint, marker, and completed connector. Overrides the Steps `tone`; error steps stay critical and disabled steps stay neutral. |
281
+ | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| '' | inherits Steps `tone | Tone for this phase. It colors the label, hint, marker, and completed connector. Overrides the Steps `tone`; error steps stay critical and disabled steps stay neutral. |
282
282
 
283
283
  ## Web Component
284
284
 
@@ -6,6 +6,8 @@ The `Switch` wrapper renders a form-associated `<a-switch>` element. It works un
6
6
 
7
7
  ## Tone
8
8
 
9
+ Switch is neutral by default. Pass `tone="brand"` or another tone to color the control.
10
+
9
11
  `tone` colors the whole control: the checked track, plus the unchecked track border and thumb. Six named tones are available, or pass any CSS color for a custom tone. The label and hint stay neutral.
10
12
 
11
13
  ```tsx
@@ -17,7 +19,7 @@ The `Switch` wrapper renders a form-associated `<a-switch>` element. It works un
17
19
 
18
20
  ## Selected-only tone
19
21
 
20
- `toneScope="selected"` applies the switch's `tone` to the checked track only. The unchecked track and thumb stay neutral until the switch is on. Use it when a resting tinted control could imply a validation state.
22
+ To apply a tone only while selected, pass `toneScope="selected"` together with `tone`, such as `tone="brand"`. Only the checked track is tinted; the unchecked track border and thumb stay neutral.
21
23
 
22
24
  ```tsx
23
25
  <Switch tone="brand" toneScope="selected" label="Off stays neutral" />
@@ -119,7 +121,10 @@ Use **`onStateChange`** to intercept a toggle or drive a controlled switch. Use
119
121
 
120
122
  ## Accessibility
121
123
 
122
- The wrapper sets `role="switch"` and keeps `aria-checked` in sync with the current value. It derives the accessible name from `label` or text children. For a label-less switch, pass `aria-label`.
124
+ The wrapper sets `role="switch"`, and the element keeps `aria-checked` in sync
125
+ with the current value. Its light-DOM label supplies the accessible name and its
126
+ hint supplies the accessible description through `ElementInternals`; no generated
127
+ IDs are needed. For a label-less switch, pass `aria-label`.
123
128
 
124
129
  Space toggles the switch. It follows the [WAI-ARIA switch pattern](https://www.w3.org/WAI/ARIA/apg/patterns/switch/).
125
130
 
@@ -130,7 +135,7 @@ Space toggles the switch. It follows the [WAI-ARIA switch pattern](https://www.w
130
135
  | `checked?` | boolean | — | Controlled checked value. In controlled mode, update this in `onStateChange`. |
131
136
  | `defaultChecked?` | boolean | false | Initial checked value for an uncontrolled switch. |
132
137
  | `disabled?` | boolean | — | Disables interaction and removes the switch from the tab order. |
133
- | `hint?` | ReactNode | — | Secondary text rendered under the label. It does not become part of the accessible name. |
138
+ | `hint?` | ReactNode | — | Secondary text rendered under the label and exposed as the switch's accessible description. It does not become part of the accessible name. |
134
139
  | `label?` | string | — | Visible, stable label for the setting. Use `children` for richer label content. |
135
140
  | `labelPosition?` | 'start' \| 'end' | 'end' | Put the visible label before or after the control. Grid layout changes only the visual order, preserving DOM/source order for assistive technologies. |
136
141
  | `name?` | string | — | Form field name. A checked switch submits `value` under this name. |
@@ -139,15 +144,16 @@ Space toggles the switch. It follows the [WAI-ARIA switch pattern](https://www.w
139
144
  | `onValueChange?` | (event, attrs) => void | — | Post-apply callback with the new form-relevant value snapshot. |
140
145
  | `round?` | boolean \| number \| string | — | Fully round the thumb and track. Pass a `number` (px) or CSS length string for a custom track radius; the thumb radius is 3px smaller. |
141
146
  | `size?` | 'small' \| 'medium' \| 'large' | 'medium' | Size variant. small=26×16px, medium=30×18px, large=34×20px. |
142
- | `tone?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'brand' | Color of the track and thumb. A tinted tone also colors the unchecked track border and thumb; use `toneScope="selected"` to color only the checked track. |
143
- | `toneScope?` | ToneScope | 'all' | Apply `tone` to every state, or only while checked so the unchecked track and thumb stay neutral. |
147
+ | `tone?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Color of the track and thumb. A tinted tone also colors the unchecked track border and thumb; use `toneScope="selected"` to color only the checked track. |
148
+ | `toneScope?` | 'all' \| 'selected' | 'all' | Apply `tone` to every state, or only while checked so the unchecked track and thumb stay neutral. |
144
149
  | `value?` | string | "on" | Value submitted while checked. |
145
150
 
146
151
  ## Web Component
147
152
 
148
153
  Use the web component directly when you are not using React or Preact and a native control does not fit.
149
154
 
150
- The focusable host carries the state. Label and hint are light-DOM children.
155
+ The focusable host carries the state. Label and hint are light-DOM children that
156
+ the element exposes as its accessible name and description.
151
157
 
152
158
  ```html
153
159
  <a-switch role="switch" tabindex="0" name="updates" default-state="checked">
@@ -271,15 +271,17 @@ const [tab, setTab] = useState('overview')
271
271
 
272
272
  ## Keyboard & accessibility
273
273
 
274
- Every enabled tab is in the tab order; `Tab` / `Shift`+`Tab` step through them.
275
- The arrow keys also move between enabled tabs (wrapping at the ends), `Home` /
274
+ The selected tab is in the page's tab order. Once focus enters the strip, the
275
+ arrow keys move between enabled tabs (wrapping at the ends), `Home` /
276
276
  `End` jump to the first / last, and `Space` / `Enter` activate the focused tab;
277
277
  arrow / `Home` / `End` navigation activates as it moves (selection follows focus).
278
278
  Disabled tabs are skipped and dropped from the tab order.
279
279
 
280
280
  Pass `label` for the tablist's accessible name. Each `<TabPanel>` names its tab as
281
281
  its accessible label (`aria-labelledby`), set off-DOM so nothing writes the panel's
282
- attributes.
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.
283
285
 
284
286
  ## Routing
285
287
 
@@ -357,7 +359,7 @@ so those suit an uncontrolled strip reacting to its own pick. **`onFocus`** /
357
359
  | `value` | string | — | This tab's identity — pairs it with the `<TabPanel value="…">` of the same value, and the value reported by `onStateChange` / `onChange`. Unique per strip. |
358
360
  | `children?` | ReactNode | — | Tab content when you need more than a string — used if `label` is omitted. |
359
361
  | `className?` | string | — | CSS class on the option's rendered row. |
360
- | `disabled?` | boolean | — | Disable just this tab — skipped by keyboard nav and dropped from the tab order (a disabled-but-selected tab stays reachable, per the ARIA pattern). |
362
+ | `disabled?` | boolean | — | Prevent user activation of this tab. A disabled tab can still indicate the current selection. |
361
363
  | `icon?` | IconShape | — | Leading icon shape, rendered before the label. |
362
364
  | `iconTrailing?` | IconShape | — | Trailing icon shape, rendered after the label. |
363
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). |
@@ -371,17 +373,18 @@ so those suit an uncontrolled strip reacting to its own pick. **`onFocus`** /
371
373
  | Prop | Type | Default | Description |
372
374
  |------|------|---------|-------------|
373
375
  | `value` | string | — | Pairs this panel with the tab (`options` entry) of the same `value`. |
374
- | `children?` | ReactNode | — | Panel content — arbitrary React/Preact. |
375
- | `className?` | string | — | CSS class on the rendered `<a-tabpanel>`. |
376
376
  | `hideMode?` | 'display' \| 'visibility' | display | How this panel hides while inactive: `display` (default — removed from layout and the a11y tree) or `visibility` (keeps its layout box, to measure it or avoid reflow). Both stay mounted; to *not render* an inactive panel, render it conditionally off a controlled `value` (see the Tabs docs). |
377
- | `style?` | CSSProperties | — | Inline style on the rendered `<a-tabpanel>`. |
378
377
 
379
378
  ## Web Component
380
379
 
381
380
  Use the web component directly when you are not using React or Preact and a native control does not fit.
382
381
 
383
382
  Keep the tab strip and panels as siblings under one parent. Panels read the active
384
- value from `<a-tabs>`.
383
+ value from `<a-tabs>`. Give the selected tab `tabindex="0"` and the others
384
+ `tabindex="-1"`; `<a-tabs>` moves focus and selection with the arrow keys, while
385
+ your change handler updates those attributes for the next visit to the strip. Add
386
+ `tabindex="0"` to a text-only panel only when you want the panel itself in the page
387
+ Tab sequence.
385
388
 
386
389
  Add `fill` to make horizontal tabs share the available width equally.
387
390
 
@@ -396,6 +399,16 @@ Add `fill` to make horizontal tabs share the available width equally.
396
399
  </div>
397
400
  ```
398
401
 
402
+ ```js title="Keep the raw DOM tab stop on the selected tab"
403
+ const tabs = document.querySelector('a-tabs')
404
+
405
+ tabs.addEventListener('change', () => {
406
+ for (const tab of tabs.querySelectorAll('a-tab')) {
407
+ tab.tabIndex = tab.getAttribute('value') === tabs.value ? 0 : -1
408
+ }
409
+ })
410
+ ```
411
+
399
412
  ## Styling
400
413
 
401
414
  Reach for the props first: **`tone`**, **`size`**, **`priority`**. The focus ring is
@@ -67,9 +67,11 @@ The underlying `<a-tooltip>` works in plain HTML too, as a child of any element:
67
67
 
68
68
  ### Adjacent and nested tooltips
69
69
 
70
- Only one tooltip shows at a time. Moving between **adjacent** anchors hands off
71
- cleanly — the outgoing bubble cross-fades out as the next fades in (no blink).
72
- With `follow` tooltips (used below to show it), the outgoing bubble also keeps
70
+ Only one tooltip shows at a time. Moving between **adjacent** anchors with
71
+ `follow` tooltips hands off cleanly — the outgoing bubble cross-fades out as the
72
+ next fades in (no blink). If either tooltip does not have `follow`, the next
73
+ tooltip waits for its configured delay. With `follow` tooltips (used below to
74
+ show it), the outgoing bubble also keeps
73
75
  trailing the cursor and fades by distance from its anchor (transparent by ~100px
74
76
  away), so a near hop reads as a smooth cross-fade and a far one is already gone.
75
77
  With **nested** anchors, the inner (descendant) tooltip wins while you're over
@@ -125,7 +127,7 @@ shows.
125
127
  | `delay?` | number | 300 | Show delay in milliseconds after hover / focus. Never use `0` — use ~`50` for a near-instant tooltip (0 has caused issues in practice). |
126
128
  | `follow?` | boolean | — | Follow the cursor instead of pinning under the anchor. The bubble is pinned (anchored beneath the target) by default; pass `follow` for the cursor-tracking behavior, which fades by distance as the cursor leaves. |
127
129
  | `interactive?` | boolean | — | Make the bubble hoverable and clickable — enables pointer events and keeps it open while the cursor is over it, so its content (links, buttons) can be interacted with. Always pinned (an interactive bubble can't follow the cursor, even with `follow`). |
128
- | `placement?` | 'top' \| 'bottom' | bottom | Which side of the anchor the bubble prefers. Auto-flips to the other side when there isn't room. |
130
+ | `placement?` | 'top' \| 'bottom' \| 'left' | bottom | Which side of the anchor the bubble prefers. Auto-flips to the other side when there isn't room. |
129
131
  | `round?` | boolean \| number \| string | — | Round the bubble to a 20px radius (matching a round menu). Pass a `number` (px) or a CSS length string for a custom radius. |
130
132
  | `truncatedOnly?` | boolean | — | Only show when the target is actually truncated (its text overflows and is ellipsized); a label that fits gets no tooltip. The check is a UI-thread layout read, re-measured on each show. By default it measures every Anta ellipsizing label part inside the anchor, then the anchor itself — override with `truncatedSelector`. |
131
133
  | `truncatedSelector?` | string | — | CSS selector (resolved within the anchor) for the element or elements whose overflow decides whether a `truncatedOnly` tooltip shows. |
package/docs/index.md CHANGED
@@ -50,6 +50,7 @@ Anta is designed for a clean DOM. Prefer the props and attributes exposed by eac
50
50
 
51
51
  - [Box](./components/box.md)
52
52
  - [Capture](./components/capture.md)
53
+ - [Panel](./components/panel.md)
53
54
 
54
55
  ## Packages
55
56
 
@@ -61,8 +61,9 @@ seed-derived default palette or provide your own theme.
61
61
 
62
62
  ### Cascade layers
63
63
 
64
- Anta's reset and element CSS use child layers inside `@layer anta`. `tokens.css`
65
- orders them above preflight resets and below your component and utility layers:
64
+ Anta's reset and element CSS use child layers inside `@layer anta`. Every Anta
65
+ stylesheet reserves the same order, so granular stylesheets remain safe when a
66
+ bundler loads them before `tokens.css`:
66
67
 
67
68
  ```css
68
69
  @layer base, anta, components, utilities;
@@ -72,8 +73,8 @@ orders them above preflight resets and below your component and utility layers:
72
73
  `anta.theme` lets the optional reference palette replace component formulas. The
73
74
  outer `anta` layer keeps its public cascade position.
74
75
 
75
- To change that order, declare it in CSS loaded **before** `tokens.css`. The
76
- first declaration fixes a layer's position:
76
+ To change that order, declare it in CSS loaded **before any Anta stylesheet**.
77
+ The first declaration fixes a layer's position:
77
78
 
78
79
  ```css
79
80
  /* your global.css, loaded before anta */