@antadesign/anta 0.3.28 → 0.3.30

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 (142) 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 +66 -29
  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/Breadcrumbs.d.ts +1 -1
  13. package/dist/components/Button.d.ts +3 -3
  14. package/dist/components/ButtonCopy.d.ts +1 -1
  15. package/dist/components/Calendar.js +3 -5
  16. package/dist/components/Checkbox.d.ts +2 -2
  17. package/dist/components/Input.d.ts +8 -2
  18. package/dist/components/Input.js +4 -0
  19. package/dist/components/InputAutocomplete.js +3 -12
  20. package/dist/components/InputDate.css +1 -1
  21. package/dist/components/InputDate.js +2 -0
  22. package/dist/components/Menu.d.ts +4 -4
  23. package/dist/components/Menu.js +1 -1
  24. package/dist/components/Panel.d.ts +17 -0
  25. package/dist/components/Panel.js +22 -0
  26. package/dist/components/RadioGroup.d.ts +2 -1
  27. package/dist/components/Select.d.ts +14 -14
  28. package/dist/components/Select.js +47 -35
  29. package/dist/components/SelectFaceted.d.ts +17 -6
  30. package/dist/components/SelectFaceted.js +61 -41
  31. package/dist/components/Steps.css +1 -1
  32. package/dist/components/Switch.d.ts +3 -3
  33. package/dist/components/TabPanel.d.ts +3 -8
  34. package/dist/components/TabPanel.js +2 -1
  35. package/dist/components/Tabs.d.ts +2 -2
  36. package/dist/components/Tabs.js +2 -1
  37. package/dist/components/Tooltip.d.ts +1 -1
  38. package/dist/components/Tooltip.js +1 -1
  39. package/dist/components/select-parts.css +1 -1
  40. package/dist/elements/a-avatar.css +1 -1
  41. package/dist/elements/a-banner.css +1 -1
  42. package/dist/elements/a-box.css +1 -1
  43. package/dist/elements/a-box.d.ts +6 -6
  44. package/dist/elements/a-box.js +76 -23
  45. package/dist/elements/a-button.css +1 -1
  46. package/dist/elements/a-button.d.ts +9 -1
  47. package/dist/elements/a-button.js +64 -1
  48. package/dist/elements/a-calendar.css +1 -1
  49. package/dist/elements/a-capture.css +1 -1
  50. package/dist/elements/a-card.css +1 -1
  51. package/dist/elements/a-checkbox.css +1 -1
  52. package/dist/elements/a-checkbox.d.ts +3 -0
  53. package/dist/elements/a-checkbox.js +17 -1
  54. package/dist/elements/a-copy.css +1 -1
  55. package/dist/elements/a-dialog.css +1 -1
  56. package/dist/elements/a-expander.css +1 -1
  57. package/dist/elements/a-icon.css +1 -1
  58. package/dist/elements/a-icon.shapes.css +1 -1
  59. package/dist/elements/a-icon.shapes.d.ts +1 -1
  60. package/dist/elements/a-input-time.css +1 -1
  61. package/dist/elements/a-input-time.d.ts +8 -0
  62. package/dist/elements/a-input-time.js +115 -8
  63. package/dist/elements/a-input.css +1 -1
  64. package/dist/elements/a-input.d.ts +27 -1
  65. package/dist/elements/a-input.js +176 -35
  66. package/dist/elements/a-loader.css +1 -1
  67. package/dist/elements/a-menu-group.css +1 -1
  68. package/dist/elements/a-menu-item.css +1 -1
  69. package/dist/elements/a-menu-item.js +1 -0
  70. package/dist/elements/a-menu-separator.css +1 -1
  71. package/dist/elements/a-menu.css +1 -1
  72. package/dist/elements/a-menu.d.ts +8 -13
  73. package/dist/elements/a-menu.js +146 -45
  74. package/dist/elements/a-panel.css +1 -0
  75. package/dist/elements/a-panel.d.ts +16 -0
  76. package/dist/elements/a-panel.js +153 -0
  77. package/dist/elements/a-progress.css +1 -1
  78. package/dist/elements/a-radio-group.css +1 -1
  79. package/dist/elements/a-radio.css +1 -1
  80. package/dist/elements/a-radio.d.ts +4 -0
  81. package/dist/elements/a-radio.js +18 -0
  82. package/dist/elements/a-slider.css +1 -1
  83. package/dist/elements/a-slider.d.ts +1 -0
  84. package/dist/elements/a-slider.js +3 -3
  85. package/dist/elements/a-switch.css +1 -1
  86. package/dist/elements/a-switch.d.ts +3 -0
  87. package/dist/elements/a-switch.js +17 -1
  88. package/dist/elements/a-tab.css +1 -1
  89. package/dist/elements/a-tabpanel.css +1 -1
  90. package/dist/elements/a-tabs.css +1 -1
  91. package/dist/elements/a-tag.css +1 -1
  92. package/dist/elements/a-text.css +1 -1
  93. package/dist/elements/a-title.css +1 -1
  94. package/dist/elements/a-toast.css +1 -1
  95. package/dist/elements/a-toaster.css +1 -1
  96. package/dist/elements/a-tooltip.css +1 -1
  97. package/dist/elements/a-tooltip.js +30 -7
  98. package/dist/elements/focus.d.ts +9 -0
  99. package/dist/elements/focus.js +41 -0
  100. package/dist/elements/index.d.ts +1 -0
  101. package/dist/elements/index.js +3 -0
  102. package/dist/elements/panel-focus.d.ts +10 -0
  103. package/dist/elements/panel-focus.js +86 -0
  104. package/dist/elements/shadow-aria.d.ts +10 -0
  105. package/dist/elements/shadow-aria.js +155 -0
  106. package/dist/general_types.d.ts +48 -19
  107. package/dist/generate-icons.mjs +3 -0
  108. package/dist/index.d.ts +4 -1
  109. package/dist/index.js +2 -0
  110. package/dist/jsx-runtime.d.ts +20 -1
  111. package/dist/jsx-runtime.js +12 -0
  112. package/dist/reset.css +1 -1
  113. package/dist/theme-antithesis.css +1 -1
  114. package/dist/theme-antune.css +1 -1
  115. package/dist/toaster.d.ts +1 -1
  116. package/dist/tokens.css +1 -1
  117. package/docs/accessibility.md +4 -2
  118. package/docs/changelog.md +89 -4
  119. package/docs/colors.md +6 -6
  120. package/docs/components/box.md +98 -34
  121. package/docs/components/breadcrumbs.md +33 -0
  122. package/docs/components/button.md +2 -2
  123. package/docs/components/capture.md +8 -8
  124. package/docs/components/checkbox.md +9 -4
  125. package/docs/components/input-autocomplete.md +6 -0
  126. package/docs/components/input-date.md +8 -2
  127. package/docs/components/input.md +99 -40
  128. package/docs/components/menu.md +3 -3
  129. package/docs/components/panel.md +189 -0
  130. package/docs/components/radio.md +4 -2
  131. package/docs/components/select-faceted.md +57 -2
  132. package/docs/components/select.md +63 -51
  133. package/docs/components/steps.md +4 -4
  134. package/docs/components/switch.md +12 -6
  135. package/docs/components/tabs.md +21 -8
  136. package/docs/components/tooltip.md +6 -4
  137. package/docs/index.md +2 -0
  138. package/docs/install-config.md +5 -4
  139. package/docs/normalization.md +3 -1
  140. package/docs/packages/plot.md +3771 -0
  141. package/docs/theming.md +1 -1
  142. package/package.json +16 -9
@@ -4,6 +4,9 @@ Hierarchy navigation assembled from Anta Buttons. Breadcrumb entries can be
4
4
  links, actions, or copy controls. Set `maxItems` to fold one middle range into a
5
5
  More menu without measuring the available width.
6
6
 
7
+ Breadcrumbs do not scroll horizontally by default. Use `maxItems` to limit
8
+ visible entries, or enable scrolling with the [scrolling recipe](#horizontal-scrolling).
9
+
7
10
  ## Items
8
11
 
9
12
  Pass ordered `BreadcrumbItem` objects through `items`. Each item is a link,
@@ -212,3 +215,33 @@ with your own selector.
212
215
  color: var(--text-2);
213
216
  }
214
217
  ```
218
+
219
+ ### Horizontal scrolling
220
+
221
+ Set `overflow-x: auto` on the trail to scroll when its contents exceed the
222
+ available width. `maxItems` limits the item count; long labels can still overflow.
223
+
224
+ Horizontal scrolling also makes vertical overflow compute to `auto`, which
225
+ clips the default button focus ring above and below the row. Set
226
+ `outline-offset: -1px` on the trail's focused controls to draw the ring inside
227
+ their bounds. The selector below covers breadcrumb links, action and copy
228
+ buttons, and the More control.
229
+
230
+ `scrolling-breadcrumbs` is an example class; replace it with your own selector.
231
+ Use Tab to move through the controls and see the focus ring.
232
+
233
+ ```tsx
234
+ <Box style={{ width: 280, maxWidth: '100%' }}>
235
+ <Breadcrumbs className="scrolling-breadcrumbs" maxItems={3} items={[
236
+ { label: 'Anta', href: '/' },
237
+ { label: 'Documentation', href: '/box/' },
238
+ { label: 'Layout components', href: '/panel/' },
239
+ { label: 'Breadcrumbs', current: true },
240
+ ]} />
241
+ </Box>
242
+ ```
243
+
244
+ ```css
245
+ .scrolling-breadcrumbs { overflow-x: auto; }
246
+ .scrolling-breadcrumbs > :focus-visible { outline-offset: -1px; }
247
+ ```
@@ -166,8 +166,8 @@ name "trash". Pass your own `aria-label` to override.
166
166
  ### Children
167
167
 
168
168
  ```tsx
169
- <Button tone="brand" label="Save" icon="check">
170
- <span style={{ opacity: 0.7 }}>⌘S</span>
169
+ <Button tone="brand" label="Save" icon="check" aria-keyshortcuts="Meta+s">
170
+ <kbd aria-hidden="true" style={{ font: 'inherit', fontVariationSettings: 'inherit', opacity: 0.7 }}>⌘S</kbd>
171
171
  </Button>
172
172
  ```
173
173
 
@@ -214,9 +214,9 @@ native text selection or touch gestures.
214
214
  | `onWheelInput?` | (event, detail) => void | — | Accepted wheel input, with a serialized original event, Capture-relative geometry, focus state, and activation reason. Cancellation is already complete. |
215
215
  | `pan?` | boolean \| CapturePan | — | Emit custom pan motion. `true` enables touch panning on both axes without momentum. Options select devices, axes, bounds directions, and optional inertia. Captures the pointer internally; `pointerCapture` is not required. Sets CSS touch-action through attributes before the gesture starts. |
216
216
  | `pointerCapture?` | boolean \| CapturePointerCapture | — | Emit raw data for a primary pointer until release or cancellation. An options object filters devices/buttons and configures activation. Nested interactive controls are excluded unless explicitly included. A listener alone enables nothing. |
217
- | `wheelActivation?` | CaptureWheelActivation | settled | Pointer or focus condition required before wheel input can be captured. Focus applies only to input targeted within this Capture. |
217
+ | `wheelActivation?` | 'hover' \| 'settled' \| 'focus' \| 'settled-or-focus' | settled | Pointer or focus condition required before wheel input can be captured. Focus applies only to input targeted within this Capture. |
218
218
  | `wheelCapture?` | CaptureInputDirections | — | Capture wheel input in the enabled directions and emit `onWheelInput`. `true` accepts all directions. Omit or pass `false` to leave wheel input alone. Nested native wheel controls and Anta menus are excluded. All-false direction bounds preserve pointer settling while declining input. A listener alone never enables capture. |
219
- | `wheelModifier?` | CaptureInputModifier | none | Required modifier for wheel capture. `none` preserves browser Ctrl/pinch zoom. |
219
+ | `wheelModifier?` | 'none' \| 'any' \| 'alt' \| 'ctrl' \| 'meta' \| 'shift' | none | Required modifier for wheel capture. `none` preserves browser Ctrl/pinch zoom. |
220
220
  | `wheelSettle?` | CaptureWheelSettle | { delay: 150, tolerance: 5, resetOnMove: false } | Dwell delay, movement tolerance, and whether movement resets eligibility. |
221
221
 
222
222
  ### CaptureWheelSettle
@@ -233,7 +233,7 @@ native text selection or touch gestures.
233
233
  |------|------|---------|-------------|
234
234
  | `buttons?` | readonly number[] | [0] | Accepted initiating buttons, using PointerEvent.button values. |
235
235
  | `includeInteractive?` | boolean | false | Allow capture to start on nested native or ARIA controls, links, or editable regions. |
236
- | `modifier?` | CaptureInputModifier | any | Modifier required to start a capture session. |
236
+ | `modifier?` | 'none' \| 'any' \| 'alt' \| 'ctrl' \| 'meta' \| 'shift' | any | Modifier required to start a capture session. |
237
237
  | `pointerTypes?` | readonly CapturePointerType[] | ['mouse', 'pen', 'touch'] | Accepted pointer devices. One primary pointer is tracked per Capture. |
238
238
  | `threshold?` | number | 0 | Movement required before activation, in viewport CSS pixels. |
239
239
 
@@ -258,7 +258,7 @@ native text selection or touch gestures.
258
258
 
259
259
  | Field | Type | Default | Description |
260
260
  |------|------|---------|-------------|
261
- | `activationReason` | CaptureWheelActivationReason | — | |
261
+ | `activationReason` | 'immediate' \| 'settled' \| 'focus' | — | |
262
262
  | `boxHeight` | number | — | |
263
263
  | `boxWidth` | number | — | Dimensions of the same viewport bounding rectangle, in CSS pixels. |
264
264
  | `focusWithin` | boolean | — | |
@@ -271,7 +271,7 @@ native text selection or touch gestures.
271
271
 
272
272
  | Field | Type | Default | Description |
273
273
  |------|------|---------|-------------|
274
- | `activationReason` | CapturePointerActivationReason | — | |
274
+ | `activationReason` | 'pointer-down' \| 'drag-threshold' | — | |
275
275
  | `boxHeight` | number | — | |
276
276
  | `boxWidth` | number | — | Dimensions of the same viewport bounding rectangle, in CSS pixels. |
277
277
  | `deltaX` | number | — | Movement since the previous delivered sample, in viewport CSS pixels. |
@@ -285,13 +285,13 @@ native text selection or touch gestures.
285
285
  | `phase` | 'start' \| 'move' \| 'end' \| 'cancel' | — | |
286
286
  | `pointerEvent` | SerializedPointerEvent \| null | — | Null when cancellation comes from lifecycle or configuration changes. |
287
287
  | `start` | CapturePointerStart | — | |
288
- | `cancelReason?` | CaptureInputCancelReason | — | |
288
+ | `cancelReason?` | 'pointer-cancel' \| 'lost-capture' \| 'disabled' \| 'disconnected' \| 'blur' \| 'interrupted' | — | |
289
289
 
290
290
  ### CapturePanInput
291
291
 
292
292
  | Field | Type | Default | Description |
293
293
  |------|------|---------|-------------|
294
- | `activationReason` | CapturePointerActivationReason | — | |
294
+ | `activationReason` | 'pointer-down' \| 'drag-threshold' | — | |
295
295
  | `boxHeight` | number | — | |
296
296
  | `boxWidth` | number | — | Dimensions of the same viewport bounding rectangle, in CSS pixels. |
297
297
  | `deltaX` | number | — | Incremental scroll motion in viewport CSS pixels. |
@@ -305,7 +305,7 @@ native text selection or touch gestures.
305
305
  | `start` | CapturePointerStart | — | |
306
306
  | `velocityX` | number | — | Scroll velocity in viewport CSS pixels/ms. |
307
307
  | `velocityY` | number | — | |
308
- | `cancelReason?` | CaptureInputCancelReason | — | |
308
+ | `cancelReason?` | 'pointer-cancel' \| 'lost-capture' \| 'disabled' \| 'disconnected' \| 'blur' \| 'interrupted' | — | |
309
309
 
310
310
  ## Web component
311
311
 
@@ -163,7 +163,11 @@ function ParentChild() {
163
163
 
164
164
  ## Accessibility
165
165
 
166
- The wrapper sets `role="checkbox"` and `aria-checked` (`"true"` / `"false"` / `"mixed"`), and derives the accessible name from `label` or `children`. For a label-less checkbox, pass `aria-label`.
166
+ The wrapper sets `role="checkbox"`, and the element keeps `aria-checked`
167
+ (`"true"` / `"false"` / `"mixed"`) in sync. Its light-DOM label supplies the
168
+ accessible name and its hint supplies the accessible description through
169
+ `ElementInternals`; no generated IDs are needed. For a label-less checkbox, pass
170
+ `aria-label`.
167
171
 
168
172
  Space toggles the checkbox. Follows the [WAI-ARIA checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/).
169
173
 
@@ -174,7 +178,7 @@ Space toggles the checkbox. Follows the [WAI-ARIA checkbox pattern](https://www.
174
178
  | `checked?` | CheckboxValue | — | Controlled checked state. When provided the checkbox is controlled — it renders exactly this and never self-applies; `onStateChange` is a *request* the consumer accepts by updating this prop. Use `defaultChecked` for an uncontrolled checkbox. `'indeterminate'` shows the minus glyph and takes visual precedence; clicking it requests `true`. |
175
179
  | `defaultChecked?` | CheckboxValue | false | Initial checked state for an uncontrolled checkbox. Read once; later changes are ignored and the element updates its state after interaction. |
176
180
  | `disabled?` | boolean | — | Disable the checkbox (no interaction, dropped from the tab order). |
177
- | `hint?` | ReactNode | — | Secondary text rendered under the label — explanatory copy, like Input's hint. Not part of the accessible name. |
181
+ | `hint?` | ReactNode | — | Secondary text rendered under the label and exposed as the checkbox's accessible description. Not part of the accessible name. |
178
182
  | `label?` | string | — | Visible label — the *value* of the checkbox (clicked along with the box). Convenience for the common single-string case; for richer content (markup, a link, an info icon) use `children`. When both are supplied, `label` renders first. Required unless `children` or `aria-label` is provided (a `role="checkbox"` takes its name from the author, not the markup). |
179
183
  | `name?` | string | — | Form field name. Inside a `<form>` the checkbox submits under this name, contributing `value` when checked — like a native checkbox. |
180
184
  | `onChange?` | (event) => void | — | Fired *after* the checked state changes — a native `change` event (the post-apply counterpart to `onStateChange`). Not cancelable. For a controlled checkbox this fires once you've updated `checked`. |
@@ -183,14 +187,15 @@ Space toggles the checkbox. Follows the [WAI-ARIA checkbox pattern](https://www.
183
187
  | `round?` | boolean \| number \| string | — | Round the checkbox mark to a circle (`border-radius: 999px` on the box). Pass a `number` (px) or a CSS length string for a rounded-square mark instead. |
184
188
  | `size?` | 'small' \| 'medium' \| 'large' | 'medium' | Size variant. small=14px, medium=16px, large=18px box. |
185
189
  | `tone?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Color of the **mark**. In the default `all` scope this colors the checked-box fill and unselected box border. A named tone or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for a one-off custom tone. Named tones track light/dark mode automatically; a custom color keeps its hue + chroma and pins lightness to the fill curve. Set `toneScope="selected"` to tone only the checked mark and leave the empty box neutral. The label + hint stay neutral — recolor them in plain CSS via the theme-aware `--text-N-{tone}` tokens. |
186
- | `toneScope?` | ToneScope | 'all' | Apply `tone` to every state, or only while checked so the empty box stays neutral. `selected` is useful when a tinted resting border would read as a validation state. |
190
+ | `toneScope?` | 'all' \| 'selected' | 'all' | Apply `tone` to every state, or only while checked so the empty box stays neutral. `selected` is useful when a tinted resting border would read as a validation state. |
187
191
  | `value?` | string | "on" | Value submitted with the form when checked — like a native checkbox. |
188
192
 
189
193
  ## Web Component
190
194
 
191
195
  Use the web component directly when you are not using React or Preact and a native control does not fit.
192
196
 
193
- The focusable host carries the state. Label and hint are light-DOM children.
197
+ The focusable host carries the state. Label and hint are light-DOM children that
198
+ the element exposes as its accessible name and description.
194
199
 
195
200
  ```html
196
201
  <a-checkbox role="checkbox" tabindex="0" name="updates" default-state="checked">
@@ -23,6 +23,12 @@ the first suggestion (then ↑/↓ move through them), and
23
23
  Enter on a highlighted row picks it. Esc or an outside click
24
24
  closes the list; clicking the field to place the caret keeps it open.
25
25
 
26
+ The focused field is associated directly with the suggestion popup and its
27
+ active option through ARIA element reflection. These relationships do not
28
+ require generated popup or option IDs, so separate application roots cannot
29
+ collide. Explicit ARIA relationships authored on a raw Anta Input take
30
+ precedence.
31
+
26
32
  ## Filtering
27
33
 
28
34
  `filter` decides how suggestions match the text:
@@ -212,9 +212,15 @@ the standard constraint-validation pipeline.
212
212
 
213
213
  ## Accessibility
214
214
 
215
+ The focused Input field is associated directly with its Calendar dialog through
216
+ ARIA element reflection. The relationship does not require a generated popup ID,
217
+ so separate application roots can render InputDate instances without ID
218
+ collisions. An explicitly authored relationship on a raw Anta Input takes
219
+ precedence.
220
+
215
221
  The calendar is a labelled **group** of day buttons, a flat grid rather than a
216
- nested `grid`/`row`/`gridcell` tree. The group is named by the visible month
217
- (`aria-labelledby`). Each day is a `<button>` whose `aria-label` is its full date
222
+ nested `grid`/`row`/`gridcell` tree. The visible month names the group directly.
223
+ Each day is a `<button>` whose `aria-label` is its full date
218
224
  including weekday ("Sunday, June 28, 2026"), with `aria-pressed` on the selected day
219
225
  and `aria-current="date"` on today. The decorative weekday headers are `aria-hidden`,
220
226
  since each day already names its weekday. A roving `tabindex` keeps a single tab
@@ -9,9 +9,9 @@ for styling through `::part(input)`.
9
9
  ## Size
10
10
 
11
11
  ```tsx
12
- <Input size="small" label="Small" placeholder="24px tall" />
13
- <Input size="medium" label="Medium" placeholder="28px tall" /> {/* default */}
14
- <Input size="large" label="Large" placeholder="32px tall" />
12
+ <Input size="small" label="Small" placeholder="24px tall" autoComplete="off" />
13
+ <Input size="medium" label="Medium" placeholder="28px tall" autoComplete="off" /> {/* default */}
14
+ <Input size="large" label="Large" placeholder="32px tall" autoComplete="off" />
15
15
  ```
16
16
 
17
17
  Three sizes (`medium` is the default). The field **height**, the **type scale**
@@ -27,8 +27,8 @@ Anta's text scale, so a field lines up with same-size `Text` / `Button`.
27
27
  ## Label and hint
28
28
 
29
29
  ```tsx
30
- <Input label="Display name" defaultValue="Ada Lovelace" />
31
- <Input label="API key" hint="Find this in Settings → Developers." placeholder="sk-…" />
30
+ <Input label="Display name" defaultValue="Ada Lovelace" autoComplete="name" />
31
+ <Input label="API key" hint="Find this in Settings → Developers." placeholder="sk-…" autoComplete="off" />
32
32
  ```
33
33
 
34
34
  `label` sits above the field; `hint` is neutral helper text below it
@@ -40,12 +40,18 @@ mirrors the label text into `aria-label`, since a native `<label for>` can't
40
40
  cross the shadow boundary. The `hint` (or error) is mirrored the same way into
41
41
  `aria-description`, so a screen reader announces the message too.
42
42
 
43
+ Standard `role` and `aria-*` attributes on `<Input>` or raw `<a-input>` are
44
+ applied to the native input, textarea, or button inside its shadow root. This
45
+ keeps the focused native control as the single accessible control. If you query
46
+ the DOM directly, read those semantics from `a-input.control` rather than from
47
+ the custom-element host.
48
+
43
49
  ## Tooltip
44
50
 
45
51
  ```tsx
46
52
  {/* Drop a <Tooltip> in as a child — it attaches to the field and shows on hover,
47
53
  exactly like a tooltip on any other element. */}
48
- <Input label="API key" placeholder="sk-…">
54
+ <Input label="API key" placeholder="sk-…" autoComplete="off">
49
55
  <Tooltip>Find this in Settings → Developers.</Tooltip>
50
56
  </Input>
51
57
  ```
@@ -60,12 +66,12 @@ content.)
60
66
 
61
67
  ```tsx
62
68
  {/* No status = neutral helper text */}
63
- <Input label="Display name" defaultValue="Ada Lovelace" hint="This is your public name." />
64
- <Input label="Workspace" defaultValue="acme" status="info" hint="Lowercase letters and dashes only." />
65
- <Input label="Username" defaultValue="ada" status="success" hint="Username is available." />
66
- <Input label="Password" type="password" defaultValue="hunter2" status="warning" hint="Weak — add more characters." />
67
- <Input label="Email" defaultValue="not-an-email" status="critical" hint="Enter a valid email address." />
68
- <Input label="Plan" defaultValue="Pro" status="brand" hint="You're on the Pro plan." />
69
+ <Input label="Display name" defaultValue="Ada Lovelace" hint="This is your public name." autoComplete="name" />
70
+ <Input label="Workspace" defaultValue="acme" status="info" hint="Lowercase letters and dashes only." autoComplete="off" />
71
+ <Input label="Username" defaultValue="ada" status="success" hint="Username is available." autoComplete="username" />
72
+ <Input label="Password" type="password" defaultValue="hunter2" status="warning" hint="Weak — add more characters." autoComplete="new-password" />
73
+ <Input label="Email" type="email" defaultValue="not-an-email" status="critical" hint="Enter a valid email address." autoComplete="email" />
74
+ <Input label="Plan" defaultValue="Pro" status="brand" hint="You're on the Pro plan." autoComplete="off" />
69
75
  ```
70
76
 
71
77
  `status` tints the border and the `hint`, and prefixes a glyph — `critical`,
@@ -92,15 +98,16 @@ with `statusIcon={false}`.
92
98
  ```tsx
93
99
  {/* clearable — the clear button shows once there's a value */}
94
100
  {/* dimActions — adornments rest quiet, brighten when the field is hovered/focused */}
95
- <Input label="Search" placeholder="Search…" clearable dimActions defaultValue="design tokens" />
101
+ <Input label="Search" placeholder="Search…" clearable dimActions defaultValue="design tokens" autoComplete="off" />
96
102
 
97
103
  {/* type="search" is a shorthand for the leading search icon + clearable below */}
98
- <Input label="Search" type="search" placeholder="Search…" />
104
+ <Input label="Search" type="search" placeholder="Search…" autoComplete="off" />
99
105
 
100
106
  {/* leading / trailing take any node — see the live playground above */}
101
- <Input label="Search" leading={<Icon shape="search" />} clearable dimActions />
107
+ <Input label="Search" leading={<Icon shape="search" />} clearable dimActions autoComplete="off" />
102
108
  <Input
103
109
  label="Date"
110
+ autoComplete="off"
104
111
  leading={<Icon shape="calendar" />}
105
112
  trailing={<Button priority="quaternary" size="small" icon="eye" aria-label="Pick" />}
106
113
  dimActions
@@ -149,6 +156,7 @@ const [reveal, setReveal] = useState(false)
149
156
 
150
157
  <Input
151
158
  label="Password"
159
+ autoComplete="current-password"
152
160
  type={reveal ? 'text' : 'password'}
153
161
  defaultValue="hunter2"
154
162
  dimActions
@@ -176,10 +184,10 @@ Firefox. So Anta keeps the native field.
176
184
 
177
185
  ```tsx
178
186
  {/* Grows with content, capped at 6 rows, then scrolls */}
179
- <Input multiline maxRows={6} label="Bio (autogrows)" placeholder="Tell us…" />
187
+ <Input multiline maxRows={6} label="Bio (autogrows)" placeholder="Tell us…" autoComplete="off" />
180
188
 
181
189
  {/* Constant height of 3 rows */}
182
- <Input multiline rows={3} label="Notes (fixed 3 rows)" placeholder="…" />
190
+ <Input multiline rows={3} label="Notes (fixed 3 rows)" placeholder="…" autoComplete="off" />
183
191
  ```
184
192
 
185
193
  `multiline` renders a `<textarea>` under the same API. With **no `rows`** it
@@ -195,11 +203,11 @@ browser.
195
203
 
196
204
  ```tsx
197
205
  // Uncontrolled: the element updates its value after input.
198
- <Input label="Name" defaultValue="Ada" onChange={(e) => log(e.target.value)} />
206
+ <Input label="Name" defaultValue="Ada" autoComplete="name" onChange={(e) => log(e.target.value)} />
199
207
 
200
208
  // Controlled: application state supplies the value.
201
209
  const [v, setV] = useState('')
202
- <Input label="Name" value={v} onInput={(e) => setV(e.target.value)} />
210
+ <Input label="Name" value={v} autoComplete="name" onInput={(e) => setV(e.target.value)} />
203
211
  ```
204
212
 
205
213
  Like a native input: pass `defaultValue` for **uncontrolled**, or `value` +
@@ -240,7 +248,7 @@ value-based, also has only `onValueChange`.)
240
248
 
241
249
  ```tsx
242
250
  <form onSubmit={handle}>
243
- <Input name="email" label="Email" type="email" required clearable />
251
+ <Input name="email" label="Email" type="email" autoComplete="email" required clearable />
244
252
  <Button tone="brand" type="submit" label="Sign up" />
245
253
  <Button priority="tertiary" type="reset" label="Reset" />
246
254
  </form>
@@ -254,12 +262,12 @@ restores the field to its `defaultValue` via `formResetCallback`.
254
262
  ## Types and validation
255
263
 
256
264
  ```tsx
257
- <Input type="text" label="Text" />
258
- <Input type="email" label="Email" />
259
- <Input type="password" label="Password" />
260
- <Input type="tel" label="Tel" />
261
- <Input type="url" label="URL" />
262
- <Input type="number" label="Number" min={0} max={100} />
265
+ <Input type="text" label="Text" autoComplete="off" />
266
+ <Input type="email" label="Email" autoComplete="email" />
267
+ <Input type="password" label="Password" autoComplete="current-password" />
268
+ <Input type="tel" label="Tel" autoComplete="tel" />
269
+ <Input type="url" label="URL" autoComplete="url" />
270
+ <Input type="number" label="Number" autoComplete="off" min={0} max={100} />
263
271
  ```
264
272
 
265
273
  `type` accepts `text` (default), `email`, `password`, `tel`, `url`, and
@@ -267,6 +275,12 @@ restores the field to its `defaultValue` via `formResetCallback`.
267
275
  constraint validation. (`search` is omitted on purpose — see above.)
268
276
  `number` also takes `min` / `max` / `step`.
269
277
 
278
+ Set `autoComplete` to describe the field's purpose. `email`, `tel`, and `url`
279
+ derive their matching token from `type`. Other purposes need an explicit token,
280
+ such as `name`, `username`, `current-password`, `new-password`, or
281
+ `one-time-code`. Use `autoComplete="off"` for examples and application fields
282
+ that do not represent autofill data.
283
+
270
284
  For a **controlled** numeric field — one whose `value` you hold in state — prefer
271
285
  `type="text"` with `inputMode="decimal"` (or `"numeric"`) over `type="number"`. A
272
286
  number input runs value sanitization, so a mid-typing `"1."` or `"1.0"` reports an
@@ -296,10 +310,10 @@ function onSubmit(e) {
296
310
  }
297
311
 
298
312
  <form noValidate onSubmit={onSubmit}>
299
- <Input name="email" type="email" required hint={errors.email}
313
+ <Input name="email" type="email" autoComplete="email" required hint={errors.email}
300
314
  status={errors.email ? 'critical' : undefined}
301
315
  onInput={() => setErrors(p => ({ ...p, email: undefined }))} />
302
- <Input name="age" type="number" min="18" max="120" hint={errors.age}
316
+ <Input name="age" type="number" autoComplete="off" min="18" max="120" hint={errors.age}
303
317
  status={errors.age ? 'critical' : undefined}
304
318
  onInput={() => setErrors(p => ({ ...p, age: undefined }))} />
305
319
  <Button type="submit" label="Submit" />
@@ -345,7 +359,7 @@ Re-template the host to two columns and drop each part into one. The hint stays
345
359
  under the field by landing in the same column.
346
360
 
347
361
  ```tsx
348
- <Input className="label-side" label="Display name" hint="Shown on your profile" />
362
+ <Input className="label-side" label="Display name" hint="Shown on your profile" autoComplete="name" />
349
363
 
350
364
  {/* .label-side is just for the demo — use your own selector */}
351
365
  <style>{`
@@ -369,9 +383,9 @@ left-edge lines up. Each hint still tucks under its own field.
369
383
 
370
384
  ```tsx
371
385
  <div className="aligned-form">
372
- <Input label="Email" type="email" hint="We'll never share it." />
373
- <Input label="Confirm password" type="password" />
374
- <Input label="PIN" />
386
+ <Input label="Email" type="email" autoComplete="email" hint="We'll never share it." />
387
+ <Input label="Confirm password" type="password" autoComplete="new-password" />
388
+ <Input label="PIN" autoComplete="one-time-code" />
375
389
  </div>
376
390
 
377
391
  <style>{`
@@ -391,7 +405,7 @@ unit. Two columns (field, then hint); the label spans the top, and the hint
391
405
  centers against the field.
392
406
 
393
407
  ```tsx
394
- <Input className="hint-right" label="Username" hint="3–20 characters" placeholder="ada" />
408
+ <Input className="hint-right" label="Username" hint="3–20 characters" placeholder="ada" autoComplete="username" />
395
409
 
396
410
  <style>{`
397
411
  a-input.hint-right {
@@ -412,10 +426,10 @@ wraps to fewer columns as it narrows. Resize the preview to see it reflow.
412
426
 
413
427
  ```tsx
414
428
  <div className="wrap-fields">
415
- <Input label="First name" />
416
- <Input label="Last name" />
417
- <Input label="Email" type="email" />
418
- <Input label="Phone" type="tel" />
429
+ <Input label="First name" autoComplete="given-name" />
430
+ <Input label="Last name" autoComplete="family-name" />
431
+ <Input label="Email" type="email" autoComplete="email" />
432
+ <Input label="Phone" type="tel" autoComplete="tel" />
419
433
  </div>
420
434
 
421
435
  <style>{`
@@ -430,6 +444,7 @@ wraps to fewer columns as it narrows. Resize the preview to see it reflow.
430
444
  |------|------|---------|-------------|
431
445
  | `autoComplete?` | 'off' \| 'on' \| 'name' \| 'username' \| 'email' \| 'current-password' \| 'new-password' \| 'one-time-code' \| 'tel' \| 'url' \| (string & {}) | — | Native autocomplete token. Overrides the value derived from `type` (`email` / `tel` / `url`) — set it for the cases `type` can't express, e.g. `username`, `current-password`, `new-password`, `one-time-code`, or `off`. |
432
446
  | `autoFocus?` | boolean | — | Focus this field when its containing `Dialog` opens. |
447
+ | `button?` | boolean | — | Render the field surface with a native button as its focus target. This is available for Input-styled popup triggers; the value becomes the button text and the placeholder is shown while it is empty. |
433
448
  | `children?` | ReactNode | — | Extra content rendered directly under the field, above the hint/error (it pushes the message down). A no-box child like an Anta `<Tooltip>` takes no space and anchors to the field — consistent with how tooltips attach to any other element. Use the named `leading` / `trailing` props for in-field content. |
434
449
  | `clearable?` | boolean | — | Show a clear button as the first trailing item once the field has a value. |
435
450
  | `defaultValue?` | string | — | Initial value for the uncontrolled case. |
@@ -457,7 +472,7 @@ wraps to fewer columns as it narrows. Resize the preview to see it reflow.
457
472
  | `placeholder?` | string | — | Placeholder shown when empty. |
458
473
  | `readOnly?` | boolean | — | Make the field read-only. |
459
474
  | `required?` | boolean | — | Mark the field required (drives native validity). |
460
- | `role?` | string | — | ARIA `role` for the field — e.g. `combobox` when the input drives a suggestion `listbox` (see `InputAutocomplete`). Left unset by default. |
475
+ | `role?` | string | — | ARIA `role` for the field — e.g. `combobox` when the input drives a suggestion `listbox` (see `InputAutocomplete`). The custom element delegates it, together with standard `aria-*` props, to the focused native shadow control. Left unset by default. |
461
476
  | `round?` | boolean \| number \| string | — | Fully-round the field (`border-radius: 999px`). Pass a `number` (px) or a CSS length string for a custom radius. The `clearable` × button always rounds to a circle to match (it isn't sized by a custom field value). |
462
477
  | `rows?` | number | — | Fixed visible row count — a constant-height `<textarea>` (implies `multiline`). |
463
478
  | `size?` | 'small' \| 'medium' \| 'large' | medium | Size variant. small=24px, medium=28px, large=32px tall; the type scale and icon track the size (small 13/16 + 14px icon · medium 15/20 + 16px · large 17/22 + 18px). |
@@ -478,7 +493,7 @@ Use the web component directly when you are not using React or Preact and a nati
478
493
  Slots hold the label, hint, and controls around the native field.
479
494
 
480
495
  ```html
481
- <a-input name="email" placeholder="you@example.com">
496
+ <a-input name="email" type="email" autocomplete="email" placeholder="you@example.com">
482
497
  <span slot="label">Email</span>
483
498
  <span slot="hint">We only use it for account notices.</span>
484
499
  </a-input>
@@ -524,7 +539,7 @@ Reach for the props first: **`status`** sets a validation tone (border + message
524
539
  dimensions. The focus ring is the global [`--focus-ring`](../colors.md#focus-ring).
525
540
 
526
541
  ```tsx
527
- <Input tone="#e0457b" label="Custom accent" />
542
+ <Input tone="#e0457b" label="Custom accent" autoComplete="off" />
528
543
  ```
529
544
 
530
545
  For everything else, `<a-input>` is shadow-DOM — style its **parts** with plain
@@ -563,5 +578,49 @@ a-input.fancy::part(field) {
563
578
  padding-inline: 20px; /* roomier */
564
579
  }
565
580
  a-input.fancy::part(input) { color: light-dark(#ffffff, #1a1a1a); }
581
+ a-input.fancy::part(input)::placeholder {
582
+ color: light-dark(#b8b8b8, #666666);
583
+ opacity: 1;
584
+ }
566
585
  a-input.fancy:focus-within::part(field) { background: light-dark(#161616, #ffffff); }
567
586
  ```
587
+
588
+ ### Placeholder contrast
589
+
590
+ If the label, marking, context, or hint leaves the field's purpose unclear,
591
+ a stronger placeholder can help. Keep an accessible name and put essential
592
+ instructions in a persistent label or hint.
593
+
594
+ Antune uses `--text-4` for placeholders. These ratios compare the default neutral
595
+ Input backgrounds with the example's literal color:
596
+
597
+ | Style | Placeholder | Background | Contrast |
598
+ | --- | --- | --- | --- |
599
+ | Antune light theme | `#7a737c` | `#ffffff` | 4.59:1 |
600
+ | Antune dark theme | `#7c737c` | `#000000` | 4.60:1 |
601
+ | Example | `#767676` | `#ffffff` | 4.54:1 |
602
+
603
+ `#767676` is the lightest opaque 8-bit neutral gray that passes 4.5:1 on white.
604
+ W3C recommends [sufficient placeholder contrast](https://www.w3.org/WAI/tutorials/forms/instructions/#styling).
605
+ Redundant placeholders remain debated in the
606
+ [meeting discussion](https://www.w3.org/2025/04/16-matf-minutes.html) and
607
+ [open W3C issue](https://github.com/w3c/wcag/issues/4343); there is no agreed exemption.
608
+
609
+ This demo uses `#767676` on white in every state and theme.
610
+ Replace `.placeholder-contrast` with your own class.
611
+
612
+ ```tsx
613
+ <Input className="placeholder-contrast" label="Email" type="email" autoComplete="email" placeholder="you@example.com" />
614
+ ```
615
+
616
+ ```css
617
+ a-input.placeholder-contrast::part(field) { background: #ffffff; }
618
+ a-input.placeholder-contrast::part(input) { color: #161616; }
619
+ a-input.placeholder-contrast::part(input)::placeholder {
620
+ color: #767676;
621
+ opacity: 1;
622
+ }
623
+ ```
624
+
625
+ Check contrast separately if you use a different background.
626
+ Native inputs and textareas use `::placeholder` without `::part(input)`.
@@ -415,7 +415,7 @@ navigating links.
415
415
  | `selectionIndicator?` | 'checkbox' \| 'radio' \| 'check' | — | Turn the row into a checkable item, driven by `selected` (the row stays the control and carries `aria-checked`): - `'checkbox'` → `role="menuitemcheckbox"`, a leading passive `<a-checkbox>` (before `icon`); the tint is dropped (the box carries state). - `'radio'` → `role="menuitemradio"`, a leading passive `<a-radio>`; tint dropped. - `'check'` → `role="menuitemradio"`, a trailing check glyph on the selected row *and* the background tint (the canonical single-select look). Omit for a plain row (the default). |
416
416
  | `submenu?` | boolean | — | Marks this item as a submenu parent: adds the trailing chevron and `aria-haspopup="menu"`. Nest the flyout as a `<Menu>` child. |
417
417
  | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | neutral | Semantic tone — colors the label, icon, and hover/selected tint (and the `checkbox`/`radio` indicator, which adopts it). A named tone, or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for a one-off custom tone whose hue + chroma are kept while the lightness is pinned to match the brand text. `critical` is the destructive action; `neutral` (the default) is the standard gray. |
418
- | `toneScope?` | ToneScope | 'all' | Apply `tone` to every row state, or only while the row is selected. In `selected` scope, an unselected row and its checkbox/radio indicator stay neutral. |
418
+ | `toneScope?` | 'all' \| 'selected' | 'all' | Apply `tone` to every row state, or only while the row is selected. In `selected` scope, an unselected row and its checkbox/radio indicator stay neutral. |
419
419
  | `value?` | string \| number | — | An opaque value identifying this item, handed back in `onSelect`'s detail so a shared handler can tell which row was chosen without a per-item closure. |
420
420
 
421
421
  ## Keyboard
@@ -461,7 +461,7 @@ or do nothing to reject. (Submenus are always uncontrolled, regardless of `open`
461
461
  | `inset?` | number \| string | 4 | Inner inset for the scroll body and named header/footer content. A number is pixels; a CSS length string is used as written. |
462
462
  | `nohover?` | boolean | — | Submenu-only (a `<Menu>` nested inside a `MenuItem`); ignored on a root menu. Submenus open on hover by default (with intent timing) as well as on click — set `nohover` to make this submenu click-only. Hover-intent is mouse-only regardless: on touch (and pen) a submenu always opens on tap and stays open until dismissed. |
463
463
  | `offset?` | number | 4 | Gap in pixels between the trigger and the menu. |
464
- | `onactivedescendant?` | (e) => void | — | Combobox-mode cursor report (raw element event). Fires when the active option changes as arrow keys move the cursor while focus stays in a `[data-menu-search]` filter field; `detail.id` is the active option's `id` (`null` when none). The reactive layer that renders the field reflects this onto its `aria-activedescendant` — the element can't write that light-DOM attribute itself. Passed straight through to the element. |
464
+ | `onactivedescendant?` | (e) => void | — | Combobox-mode cursor report (raw element event). Fires when the active option changes as arrow keys move the cursor while focus stays in a `[data-menu-search]` filter field; `detail.id` is an authored option `id`, or `null`. Built-in Anta Inputs receive the active option through a direct ARIA element relationship; this remains available to custom fields and observers. Passed straight through to the element. |
465
465
  | `onStateChange?` | (event, detail) => void | — | Fired before the open state changes — on open, and on every dismiss (Esc, outside-click, scroll, selecting an item). `event` is the cancelable `statechange`; `detail.next`/`detail.prev` are the requested/previous open state (booleans). It's the declarative way to observe a menu, and the handler you pair with `open` to drive a controlled menu (apply `detail.next` to `open`). Uncontrolled, `event.preventDefault()` vetoes the transition (e.g. keep the menu open). |
466
466
  | `open?` | boolean | — | Controlled open state. Omit for the default **uncontrolled** menu (it opens/closes itself via its triggers). Pass a boolean to **control** it: the menu's visibility follows `open`, and user dismiss (Esc, outside-click, select) fires `onStateChange` *without* self-closing — you update `open` in response. Submenus are always uncontrolled regardless of this. See STATEFUL-COMPONENTS.md. |
467
467
  | `placement?` | 'bottom-start' \| 'bottom-end' \| 'top-start' \| 'top-end' \| 'bottom' \| 'top' \| 'right-start' \| 'right-end' \| 'left-start' \| 'left-end' \| 'right' \| 'left' | bottom-start | Preferred placement relative to the trigger. `bottom` / `top` place the menu below / above; `right` / `left` place it beside (like a submenu flyout). The cross-axis suffix (`-start` / `-end`) aligns the near / far edges, and no suffix centers on the trigger. The menu flips to the opposite side when the preferred one lacks room, and clamps to the viewport. |
@@ -494,7 +494,7 @@ or do nothing to reject. (Submenus are always uncontrolled, regardless of `open`
494
494
  | `submenu?` | boolean | — | Marks this item as a submenu parent: adds the trailing chevron and `aria-haspopup="menu"`. Nest the flyout as a `<Menu>` child. |
495
495
  | `target?` | string | — | Anchor target, e.g. `'_blank'`. |
496
496
  | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | neutral | Semantic tone — colors the label, icon, and hover/selected tint (and the `checkbox`/`radio` indicator, which adopts it). A named tone, or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for a one-off custom tone whose hue + chroma are kept while the lightness is pinned to match the brand text. `critical` is the destructive action; `neutral` (the default) is the standard gray. |
497
- | `toneScope?` | ToneScope | 'all' | Apply `tone` to every row state, or only while the row is selected. In `selected` scope, an unselected row and its checkbox/radio indicator stay neutral. |
497
+ | `toneScope?` | 'all' \| 'selected' | 'all' | Apply `tone` to every row state, or only while the row is selected. In `selected` scope, an unselected row and its checkbox/radio indicator stay neutral. |
498
498
  | `value?` | string \| number | — | An opaque value identifying this item, handed back in `onSelect`'s detail so a shared handler can tell which row was chosen without a per-item closure. |
499
499
 
500
500
  ## Web Component