@antadesign/anta 0.3.34 → 0.3.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/LICENSE.md +14 -1
  2. package/README.md +9 -0
  3. package/dist/anta_helpers.d.ts +46 -30
  4. package/dist/anta_helpers.js +66 -25
  5. package/dist/bundle.css +1 -1
  6. package/dist/bundle.js +2 -1333
  7. package/dist/components/Banner.d.ts +25 -40
  8. package/dist/components/Banner.js +15 -17
  9. package/dist/components/Button.js +1 -0
  10. package/dist/components/ButtonCopy.js +3 -2
  11. package/dist/components/Calendar.js +13 -13
  12. package/dist/components/Card.d.ts +9 -17
  13. package/dist/components/Card.js +50 -46
  14. package/dist/components/Dialog.d.ts +1 -0
  15. package/dist/components/Dialog.js +16 -10
  16. package/dist/components/Expander.d.ts +6 -21
  17. package/dist/components/Expander.js +24 -23
  18. package/dist/components/Icon.d.ts +8 -14
  19. package/dist/components/Input.d.ts +77 -96
  20. package/dist/components/Input.js +23 -27
  21. package/dist/components/InputAutocomplete.d.ts +29 -10
  22. package/dist/components/InputAutocomplete.js +25 -8
  23. package/dist/components/InputDate.js +2 -2
  24. package/dist/components/InputTime.d.ts +10 -4
  25. package/dist/components/InputTime.js +20 -16
  26. package/dist/components/Loader.d.ts +2 -2
  27. package/dist/components/Menu.d.ts +5 -4
  28. package/dist/components/Menu.js +13 -8
  29. package/dist/components/MenuItem.js +1 -14
  30. package/dist/components/MenuItemCopy.js +2 -2
  31. package/dist/components/Panel.d.ts +3 -1
  32. package/dist/components/Panel.js +17 -11
  33. package/dist/components/Progress.d.ts +21 -23
  34. package/dist/components/Progress.js +13 -9
  35. package/dist/components/RadioGroup.js +6 -8
  36. package/dist/components/{select-parts.css → Select.css} +1 -1
  37. package/dist/components/Select.d.ts +53 -35
  38. package/dist/components/Select.js +53 -97
  39. package/dist/components/SelectFaceted.d.ts +7 -3
  40. package/dist/components/SelectFaceted.js +21 -9
  41. package/dist/components/Slider.d.ts +28 -11
  42. package/dist/components/Slider.js +20 -20
  43. package/dist/components/Steps.css +1 -1
  44. package/dist/components/Steps.js +1 -11
  45. package/dist/components/Tabs.d.ts +17 -20
  46. package/dist/components/Tabs.js +5 -8
  47. package/dist/components/Text.d.ts +55 -36
  48. package/dist/components/Text.js +35 -22
  49. package/dist/components/Title.d.ts +16 -26
  50. package/dist/components/Title.js +2 -1
  51. package/dist/components/Toaster.js +9 -2
  52. package/dist/components/Tooltip.d.ts +10 -15
  53. package/dist/components/copy-props.d.ts +14 -3
  54. package/dist/components/copy-props.js +14 -3
  55. package/dist/components/select-tree.d.ts +24 -0
  56. package/dist/components/select-tree.js +35 -0
  57. package/dist/element-base.d.ts +10 -0
  58. package/dist/element-base.js +15 -0
  59. package/dist/elements/a-banner.css +1 -1
  60. package/dist/elements/a-banner.d.ts +4 -1
  61. package/dist/elements/a-banner.js +22 -112
  62. package/dist/elements/a-button.d.ts +2 -1
  63. package/dist/elements/a-button.js +18 -1
  64. package/dist/elements/a-calendar.d.ts +14 -13
  65. package/dist/elements/a-calendar.js +29 -24
  66. package/dist/elements/a-card.css +1 -1
  67. package/dist/elements/a-card.d.ts +0 -9
  68. package/dist/elements/a-card.js +0 -184
  69. package/dist/elements/a-checkbox.css +1 -1
  70. package/dist/elements/a-copy.css +1 -1
  71. package/dist/elements/a-copy.d.ts +1 -5
  72. package/dist/elements/a-copy.js +129 -108
  73. package/dist/elements/a-dialog.css +1 -1
  74. package/dist/elements/a-dialog.d.ts +1 -1
  75. package/dist/elements/a-dialog.js +66 -194
  76. package/dist/elements/a-expander.css +1 -1
  77. package/dist/elements/a-expander.d.ts +4 -30
  78. package/dist/elements/a-expander.js +85 -344
  79. package/dist/elements/a-icon.d.ts +5 -23
  80. package/dist/elements/a-icon.js +1 -2
  81. package/dist/elements/a-input-time.css +1 -1
  82. package/dist/elements/a-input-time.d.ts +2 -1
  83. package/dist/elements/a-input-time.js +101 -168
  84. package/dist/elements/a-input.css +1 -1
  85. package/dist/elements/a-input.d.ts +10 -8
  86. package/dist/elements/a-input.js +155 -230
  87. package/dist/elements/a-loader.d.ts +5 -8
  88. package/dist/elements/a-loader.js +1 -2
  89. package/dist/elements/a-menu-item.css +1 -1
  90. package/dist/elements/a-menu-item.d.ts +5 -3
  91. package/dist/elements/a-menu-item.js +5 -1
  92. package/dist/elements/a-menu.css +1 -1
  93. package/dist/elements/a-menu.d.ts +5 -7
  94. package/dist/elements/a-menu.js +383 -299
  95. package/dist/elements/a-panel.css +1 -1
  96. package/dist/elements/a-panel.js +19 -37
  97. package/dist/elements/a-progress.css +1 -1
  98. package/dist/elements/a-progress.d.ts +0 -22
  99. package/dist/elements/a-progress.js +0 -100
  100. package/dist/elements/a-radio-group.d.ts +1 -1
  101. package/dist/elements/a-radio-group.js +6 -4
  102. package/dist/elements/a-select-faceted.d.ts +1 -1
  103. package/dist/elements/a-select-faceted.js +1 -1
  104. package/dist/elements/a-select.d.ts +1 -1
  105. package/dist/elements/a-select.js +1 -1
  106. package/dist/elements/a-slider.css +1 -1
  107. package/dist/elements/a-slider.d.ts +11 -3
  108. package/dist/elements/a-slider.js +244 -242
  109. package/dist/elements/a-switch.css +1 -1
  110. package/dist/elements/a-tab.css +1 -1
  111. package/dist/elements/a-tabs.css +1 -1
  112. package/dist/elements/a-tabs.d.ts +1 -1
  113. package/dist/elements/a-tabs.js +6 -4
  114. package/dist/elements/a-tag.css +1 -1
  115. package/dist/elements/a-text.css +1 -1
  116. package/dist/elements/a-text.d.ts +6 -43
  117. package/dist/elements/a-text.js +110 -193
  118. package/dist/elements/a-title.css +1 -1
  119. package/dist/elements/a-toast.css +1 -1
  120. package/dist/elements/a-toast.d.ts +10 -45
  121. package/dist/elements/a-toast.js +80 -66
  122. package/dist/elements/a-toaster.css +1 -1
  123. package/dist/elements/a-toaster.js +9 -44
  124. package/dist/elements/a-tooltip.css +1 -1
  125. package/dist/elements/a-tooltip.d.ts +1 -1
  126. package/dist/elements/a-tooltip.js +142 -136
  127. package/dist/elements/copy-behavior.d.ts +1 -1
  128. package/dist/elements/copy-behavior.js +21 -21
  129. package/dist/elements/index.d.ts +3 -3
  130. package/dist/elements/index.js +3 -7
  131. package/dist/elements/input-focus-modality.d.ts +15 -0
  132. package/dist/elements/input-focus-modality.js +44 -0
  133. package/dist/elements/input-trailing-actions.d.ts +5 -0
  134. package/dist/elements/input-trailing-actions.js +38 -0
  135. package/dist/elements/shadow-aria.d.ts +2 -0
  136. package/dist/elements/shadow-aria.js +2 -1
  137. package/dist/elements/value-input.d.ts +3 -0
  138. package/dist/elements/value-input.js +12 -0
  139. package/dist/general_types.d.ts +103 -96
  140. package/dist/index.d.ts +1 -1
  141. package/dist/input-events.d.ts +4 -0
  142. package/dist/input-events.js +15 -0
  143. package/dist/jsx-runtime.d.ts +70 -23
  144. package/dist/jsx-runtime.js +26 -19
  145. package/dist/native-event-props.d.ts +2 -0
  146. package/dist/native-event-props.js +10 -0
  147. package/dist/reset.css +1 -1
  148. package/dist/theme-antithesis.css +10 -1
  149. package/dist/theme-antune.css +1 -1
  150. package/docs/accessibility.md +17 -3
  151. package/docs/changelog.md +348 -2
  152. package/docs/comparison.md +2 -2
  153. package/docs/components/avatar.md +5 -0
  154. package/docs/components/banner.md +117 -187
  155. package/docs/components/button.md +22 -0
  156. package/docs/components/card.md +74 -28
  157. package/docs/components/checkbox.md +9 -10
  158. package/docs/components/dialog.md +83 -32
  159. package/docs/components/expander.md +198 -435
  160. package/docs/components/icon.md +11 -17
  161. package/docs/components/input-autocomplete.md +152 -52
  162. package/docs/components/input-date.md +175 -148
  163. package/docs/components/input-time.md +81 -38
  164. package/docs/components/input.md +343 -349
  165. package/docs/components/loader.md +2 -1
  166. package/docs/components/menu.md +35 -6
  167. package/docs/components/panel.md +18 -0
  168. package/docs/components/progress.md +83 -49
  169. package/docs/components/radio.md +39 -43
  170. package/docs/components/select-faceted.md +54 -7
  171. package/docs/components/select.md +390 -386
  172. package/docs/components/slider.md +136 -19
  173. package/docs/components/steps.md +2 -1
  174. package/docs/components/switch.md +8 -9
  175. package/docs/components/tabs.md +110 -104
  176. package/docs/components/text.md +110 -86
  177. package/docs/components/title.md +47 -33
  178. package/docs/components/toaster.md +242 -84
  179. package/docs/components/tooltip.md +204 -113
  180. package/docs/install-config.md +183 -26
  181. package/docs/packages/plot.md +170 -148
  182. package/docs/theming.md +7 -0
  183. package/package.json +41 -3
  184. package/theme-antithesis-license.md +66 -0
@@ -1,45 +1,33 @@
1
1
  # Input
2
2
 
3
- A text field. The `Input` wrapper renders an `<a-input>` web component whose
4
- real `<input>` / `<textarea>` lives in **shadow DOM** — so the field is
5
- self-contained (you never pass a control in) yet fully native underneath:
6
- focus, IME, autofill, and form submission all work, and the control is reachable
7
- for styling through `::part(input)`.
3
+ Use Input for forms, search, passwords, and longer text. Add a label to explain
4
+ what to enter, a hint for guidance, and actions such as clearing or revealing a
5
+ password.
8
6
 
9
7
  ## Size
10
8
 
11
9
  ```tsx
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" />
10
+ <Input size="small" label="Small" placeholder="Enter text…" autoComplete="off" />
11
+ <Input size="medium" label="Medium" placeholder="Enter text…" autoComplete="off" /> {/* default */}
12
+ <Input size="large" label="Large" placeholder="Enter text…" autoComplete="off" />
15
13
  ```
16
14
 
17
- Three sizes (`medium` is the default). The field **height**, the **type scale**
18
- (font / line-height), and the **icon** all track the size. Each step matches
19
- Anta's text scale, so a field lines up with same-size `Text` / `Button`.
20
-
21
- | size | height | font / line | icon |
22
- |---|---|---|---|
23
- | `small` | 24px | 13 / 16 | 14px |
24
- | `medium` | 28px | 15 / 20 | 16px |
25
- | `large` | 32px | 17 / 22 | 18px |
15
+ Choose `small`, `medium`, or `large` to suit the surrounding interface.
16
+ Text, icons, and action buttons adjust together. The default is `medium`.
26
17
 
27
18
  ## Priority
28
19
 
29
- `priority` changes the field surface without changing its size or behavior.
30
- `primary` (the default) has a fill and border. `secondary` has a transparent
31
- background and visible border. `tertiary` keeps both transparent at rest, then
32
- uses secondary's border on hover or focus. Use it where the label or surrounding
33
- content makes the field easy to find. All three show the same focus ring.
34
- On an enabled field, a non-neutral `status` keeps its tinted fill and border
35
- visible at rest.
36
-
37
20
  ```tsx
38
21
  <Input label="Primary" placeholder="Filled field" autoComplete="off" />
39
22
  <Input priority="secondary" label="Secondary" placeholder="Outlined field" autoComplete="off" />
40
23
  <Input priority="tertiary" label="Tertiary" placeholder="Field on hover or focus" autoComplete="off" />
41
24
  ```
42
25
 
26
+ Use `priority` to choose how much the field stands out. `primary` has a filled
27
+ background, `secondary` has an outline, and `tertiary` stays subtle until someone
28
+ interacts with it. Use tertiary where the label or context makes the field easy
29
+ to find. Status feedback and focus remain visible with every priority.
30
+
43
31
  ## Label and hint
44
32
 
45
33
  ```tsx
@@ -47,36 +35,23 @@ visible at rest.
47
35
  <Input label="API key" hint="Find this in Settings → Developers." placeholder="sk-…" autoComplete="off" />
48
36
  ```
49
37
 
50
- `label` sits above the field; `hint` is neutral helper text below it
51
- (`--text-3`, no icon). Both accept a **string or a React node**, so you can
52
- embed links, `<code>`, or a `<Text>` for full control.
38
+ `label` names the field and appears above it. `hint` provides persistent help
39
+ below it. Both accept text or JSX, including links and emphasis. Keep essential
40
+ instructions here so they remain available while someone types.
53
41
 
54
- The label is wired as the control's accessible name automatically — the element
55
- mirrors the label text into `aria-label`, since a native `<label for>` can't
56
- cross the shadow boundary. The `hint` (or error) is mirrored the same way into
57
- `aria-description`, so a screen reader announces the message too.
58
-
59
- Standard `role` and `aria-*` attributes on `<Input>` or raw `<a-input>` are
60
- applied to the native input, textarea, or button inside its shadow root. This
61
- keeps the focused native control as the single accessible control. If you query
62
- the DOM directly, read those semantics from `a-input.control` rather than from
63
- the custom-element host.
42
+ The label also names the field for screen readers, and the hint provides its
43
+ description.
64
44
 
65
45
  ## Tooltip
66
46
 
67
47
  ```tsx
68
- {/* Drop a <Tooltip> in as a child — it attaches to the field and shows on hover,
69
- exactly like a tooltip on any other element. */}
70
48
  <Input label="API key" placeholder="sk-…" autoComplete="off">
71
49
  <Tooltip>Find this in Settings → Developers.</Tooltip>
72
50
  </Input>
73
51
  ```
74
52
 
75
- Attach a tooltip by dropping an Anta `<Tooltip>` in as a **child** — no slot or
76
- prop needed. It anchors to the field and shows on hover, consistent with how
77
- tooltips attach to a `<Button>` or any other element. (Children with no `slot`
78
- are projected into a default slot; use `leading` / `trailing` for *in-field*
79
- content.)
53
+ Place a `Tooltip` inside `Input` to anchor it to the field. It appears on hover
54
+ or keyboard focus. Use `leading` and `trailing` for content inside the field itself.
80
55
 
81
56
  ## Status
82
57
 
@@ -90,84 +65,74 @@ content.)
90
65
  <Input label="Plan" defaultValue="Pro" status="brand" hint="You're on the Pro plan." autoComplete="off" />
91
66
  ```
92
67
 
93
- `status` tints the border and the `hint`, and prefixes a glyph — `critical`,
94
- `warning`, `success`, `info`, or `brand`. Each pulls its tone's role tokens, so
95
- dark mode is automatic. The message always rides in `hint`; `status` only
96
- recolors it.
97
-
98
- It reuses the tone palette but isn't `tone`: **`status` is a validation *state*** (it
99
- also carries the glyph and, for `critical`, validity), whereas `tone` — elsewhere in
100
- Anta — is decorative color. On `Input` the named tones live on `status`; `tone` is
101
- reserved for a one-off custom border color.
68
+ `status` colors the field and hint and adds a status icon. Choose `critical`,
69
+ `warning`, `success`, `info`, or `brand`; each adapts to the active theme.
70
+ Put the message in `hint`.
102
71
 
103
- Only `status="critical"` carries validity — it sets `aria-invalid`, flips the
104
- `:state(invalid)` hook, and reports invalid to a surrounding `<form>` through
105
- `ElementInternals`. The other tones are advisory and stay valid.
72
+ Use `critical` for an error that must be fixed before submitting the form.
73
+ Other statuses provide feedback without marking the field invalid. Required
74
+ fields and email fields still need valid values.
106
75
 
107
- Each status has a default glyph (`critical` → `warning-diamond`, `warning` →
108
- `warning-triangle`, `success` → `circle-check`, `info` → `info`, `brand` →
109
- `circle-small-solid`). Override it with **`statusIcon`** (any shape), or drop it
110
- with `statusIcon={false}`.
76
+ Use `statusIcon` to replace the default icon, or `statusIcon={false}` to hide it.
77
+ For a custom border color without status feedback, use `tone`.
111
78
 
112
79
  ## Leading, trailing, and clear
113
80
 
114
81
  ```tsx
115
- {/* clearable — the clear button shows once there's a value */}
116
- {/* dimActions — adornments rest quiet, brighten when the field is hovered/focused */}
117
- <Input label="Search" placeholder="Search…" clearable dimActions defaultValue="design tokens" autoComplete="off" />
118
-
119
- {/* type="search" is a shorthand for the leading search icon + clearable below */}
120
- <Input label="Search" type="search" placeholder="Search…" autoComplete="off" />
82
+ import { useState } from 'preact/hooks'
83
+ import { Input, Icon, Button } from '@antadesign/anta'
84
+
85
+ function SearchActions() {
86
+ const [caseSensitive, setCaseSensitive] = useState(false)
87
+ const [regex, setRegex] = useState(false)
88
+ return (
89
+ <>
90
+ <Button priority="tertiary" icon="case-sensitive"
91
+ aria-label="Case sensitive" title="Case sensitive"
92
+ selected={caseSensitive} aria-pressed={caseSensitive}
93
+ onClick={() => setCaseSensitive(value => !value)} />
94
+ <Button priority="tertiary" icon="regex"
95
+ aria-label="Regular expression" title="Regular expression"
96
+ selected={regex} aria-pressed={regex}
97
+ onClick={() => setRegex(value => !value)} />
98
+ </>
99
+ )
100
+ }
121
101
 
122
- {/* leading / trailing take any node — see the live playground above */}
123
- <Input label="Search" leading={<Icon shape="search" />} clearable dimActions autoComplete="off" />
124
- <Input
125
- label="Date"
126
- autoComplete="off"
127
- leading={<Icon shape="calendar" />}
128
- trailing={<Button priority="quaternary" size="small" icon="eye" aria-label="Pick" />}
129
- dimActions
130
- />
102
+ function Demo() {
103
+ return (
104
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', width: '100%', maxWidth: '320px' }}>
105
+ <Input label="Leading icon" placeholder="Search…" leading={<Icon shape="search" />} autoComplete="off" />
106
+ <Input label="Trailing actions" placeholder="Search…" trailing={<SearchActions />} dimActions autoComplete="off" />
107
+ <Input label="Clear action" placeholder="Search…" clearable dimActions defaultValue="design tokens" autoComplete="off" />
108
+ <Input label="All adornments" leading={<Icon shape="search" />} trailing={<SearchActions />} clearable dimActions defaultValue="design tokens" autoComplete="off" />
109
+ <Input label="Text adornments" leading="from" trailing="each" defaultValue="$20" autoComplete="off" />
110
+ </div>
111
+ )
112
+ }
131
113
  ```
132
114
 
133
- `leading` and `trailing` paste any node at the field edges (like the expander's
134
- `actions`) — an icon, a button, a unit label. A leading item is inset from the
135
- left by the same `pad-x` as the text, so it lines up on the field's rhythm; the
136
- playground at the top of this page shows a live leading icon alongside the clear
137
- button.
115
+ Use `leading` and `trailing` to place icons, buttons, or short text inside the
116
+ field. Use either on its own or combine them with a clear button. The preview
117
+ shows search options and text such as “from” and “each.”
138
118
 
139
- **`type="search"`** is a shorthand for that search pattern: it defaults a leading
140
- search icon and `clearable` (override either — pass your own `leading`, or
141
- `clearable={false}`). It's wrapper-only — the native `search` type never reaches the
142
- DOM input, so the browser's own clear/search chrome never appears; Anta owns it.
143
-
144
- **`dimActions`** rests the adornments quiet (60% opacity) and brightens them to
145
- full when the field is hovered or focused — a "quiet until engaged" look for
146
- trailing actions. It dims the whole slot, so it covers icons and buttons alike.
147
-
148
- `clearable` adds a clear button at the right edge, shown once the field has a
149
- value. It's a real `<a-button>` in the `clear` slot — full button styling,
150
- **keyboard-focusable** — and it works **without framework hydration**: a-button's
151
- global handler fires a `clearrequest` trigger on click and Enter/Space, which the
152
- element turns into the lifecycle below.
153
-
154
- Two hooks bracket the clear. **`onClearClick`** fires first and is **cancelable** —
155
- `e.preventDefault()` keeps the value (e.g. a "discard unsaved text?" confirm). If
156
- it isn't prevented, the field clears: it empties, fires `input` / `change` (a
157
- controlled `onChange` runs with the empty value), refocuses, then fires
158
- **`onClearInput`**. On a raw `<a-input>`, bind both lowercase (`onclearclick` /
159
- `onclearinput`) — that form binds in React and Preact alike.
160
-
161
- Note: there's no `type="search"` — that type makes browsers inject their own
162
- clear button and magnifier (and style them inconsistently). Anta owns every
163
- in-field control, and the element resets the native search/number/reveal
164
- decorations across browsers.
119
+ Trailing icon buttons adjust to the field size, with space around them to keep them
120
+ inside the border. You don't need to size them separately.
121
+
122
+ Use `dimActions` to keep these extras subtle until someone hovers over or
123
+ focuses the field.
124
+
125
+ `clearable` shows a clear button when an editable field has a value. After
126
+ clearing, focus returns to the field so typing can continue. See
127
+ [Events](#events) to cancel a clear or respond after it completes.
128
+
129
+ For a search field, use `type="search"` to add a search icon and clear button.
130
+ You can replace the icon with `leading` or hide the button with
131
+ `clearable={false}`.
165
132
 
166
133
  ### Password reveal
167
134
 
168
135
  ```tsx
169
- // Reveal toggle = flip `type` between password and text. Native masking when
170
- // hidden, plain text when shown; password managers keep working.
171
136
  const [reveal, setReveal] = useState(false)
172
137
 
173
138
  <Input
@@ -187,93 +152,112 @@ const [reveal, setReveal] = useState(false)
187
152
  />
188
153
  ```
189
154
 
190
- A `trailing` eye `<Button>` flips the field's `type` between `password` and
191
- `text` — native disc masking when hidden, plain text when revealed. Because it
192
- stays a real `type="password"` when hidden, password managers and autofill keep
193
- working, and the uncontrolled value is preserved across the toggle.
194
-
195
- The mask glyph stays the browser default — restyling it means dropping
196
- `type="password"`, which loses password-manager support and isn't masked in
197
- Firefox. So Anta keeps the native field.
155
+ Use a trailing button to toggle `type` between `password` and `text` without
156
+ changing the value. The browser masks the password while it is hidden.
157
+ `autoComplete="current-password"` identifies an existing password for autofill.
198
158
 
199
159
  ## Multiline
200
160
 
201
161
  ```tsx
202
- {/* Grows with content, capped at 6 rows, then scrolls */}
162
+ {/* Grows with content, then scrolls */}
203
163
  <Input multiline maxRows={6} label="Bio (autogrows)" placeholder="Tell us…" autoComplete="off" />
204
164
 
205
- {/* Constant height of 3 rows */}
206
- <Input multiline rows={3} label="Notes (fixed 3 rows)" placeholder="…" autoComplete="off" />
165
+ {/* Keeps a fixed height */}
166
+ <Input multiline rows={3} label="Notes (fixed height)" placeholder="…" autoComplete="off" />
207
167
  ```
208
168
 
209
- `multiline` renders a `<textarea>` under the same API. With **no `rows`** it
210
- **autogrows** with its content via CSS `field-sizing: content` — capped by
211
- `maxRows` if you set one (omit for unbounded growth), then it scrolls. Pass a
212
- fixed **`rows`** count for a constant-height box instead.
213
-
214
- Autogrow uses CSS `field-sizing` where supported (Chromium, Safari ≥ 26.2) and a
215
- built-in JS resize everywhere else (Firefox, older Safari), so it grows in every
216
- browser.
169
+ Use `multiline` for longer text. The field grows as someone types.
170
+ Set `maxRows` to limit its growth, or `rows` to keep a fixed height and scroll
171
+ longer content.
217
172
 
218
173
  ## Controlled and uncontrolled
219
174
 
220
175
  ```tsx
221
176
  // Uncontrolled: the element updates its value after input.
222
- <Input label="Name" defaultValue="Ada" autoComplete="name" onChange={(e) => log(e.target.value)} />
177
+ <Input label="Name" defaultValue="Ada" autoComplete="name" onChange={(e) => console.log(e.target.value)} />
223
178
 
224
179
  // Controlled: application state supplies the value.
225
- const [v, setV] = useState('')
226
- <Input label="Name" value={v} autoComplete="name" onInput={(e) => setV(e.target.value)} />
180
+ const [value, setValue] = useState('')
181
+ <Input label="Name" value={value} autoComplete="name" onInput={(e) => setValue(e.target.value)} />
227
182
  ```
228
183
 
229
- Like a native input: pass `defaultValue` for **uncontrolled**, or `value` +
230
- `onInput` / `onChange` for **controlled**. The controlled value is reflected to
231
- the shadow control only when it differs from what's there, so the caret never
232
- jumps mid-edit.
184
+ Use `defaultValue` when the field can manage its own value. Use `value` with
185
+ `onInput` or `onValueChange` when your application needs to keep track of every
186
+ edit, such as updating a search or showing a character count.
233
187
 
234
- Read the value off `e.target.value` in either handler — the event retargets to
235
- the `<a-input>` host, whose `value` getter returns the control's value.
188
+ Read the current text from `e.target.value`, or use the value provided by
189
+ `onValueChange`. Use `onChange` for edits that are committed, such as when the
190
+ field loses focus.
236
191
 
237
- Note: like other custom-element form controls (and unlike React's built-in
238
- `<input>`), a controlled value that you *transform* applies on the next render,
239
- but one you *reject* — set state back to the same string — isn't auto-reverted,
240
- since the framework skips the re-render. Constrain hard with `maxLength` /
241
- `pattern` / `type`, or transform in the handler.
192
+ Changing the value in state updates the field. Setting state to the same value
193
+ may not undo a typed edit. Use `maxLength` for a length limit, and apply any text
194
+ formatting in your change handler.
242
195
 
243
196
  ## Events
244
197
 
245
- | handler | fires | payload |
198
+ | Handler | When it fires | Value or action |
246
199
  |---|---|---|
247
- | `onInput(e)` | on **every keystroke** | read `e.target.value` |
248
- | `onChange(e)` | on **commit** (blur / Enter) — native `change` semantics, *not* React's per-keystroke `onChange` | read `e.target.value` |
249
- | `onValueChange(e, attrs)` | on `input` **and** `change` (and clear) | `{ value, name, empty, valid, validationMessage }` |
250
- | `onClearClick(e)` | before the `clearable` × clears | ✅ `e.preventDefault()` keeps the value |
251
-
252
- **Input has no `onStateChange`, on purpose.** That event belongs to components
253
- with a *discrete* state — a small enum like `Checkbox`'s `checked · unchecked ·
254
- indeterminate` or a menu's `open · closed` — where a transition can be vetoed as
255
- a unit before it applies. A text field's value is **free-form**, not a discrete
256
- state, so there's nothing to model as `state` and nothing to veto wholesale;
257
- you control it the native way (`value` + `onInput`/`onChange`, reject by not
258
- updating). Reach for `onValueChange` as the one unified "the value changed"
259
- handler — it's the same callback `Checkbox`, `RadioGroup`, and `Select` expose,
260
- so form code reads the same across all of them. (`Select`, likewise composed and
261
- value-based, also has only `onValueChange`.)
200
+ | `onInput(e)` | When editing changes the value, or the field is cleared | Read `e.target.value` |
201
+ | `onChange(e)` | On commit, such as blur or Enter, or when cleared | Read `e.target.value` |
202
+ | `onValueChange(e, attrs)` | On both `input` and `change` | `{ value, name, empty, valid, validationMessage }` |
203
+ | `onClearClick(e)` | Before clearing | Call `e.preventDefault()` synchronously to cancel |
204
+ | `onClearInput(e)` | After clearing | The value is empty |
205
+
206
+ Use `onValueChange` to receive the current text and validation result together.
207
+ It runs while editing and when an edit is committed, so it can run more than
208
+ once for the same value.
209
+
210
+ Use `onClearClick` to keep a draft from being cleared. Try **Clear** with
211
+ **Protect draft** enabled, then turn protection off and try again.
212
+
213
+ ```tsx
214
+ import { useState } from 'preact/hooks'
215
+ import { Input, Checkbox, Button } from '@antadesign/anta'
216
+
217
+ function Demo() {
218
+ const [value, setValue] = useState('Keep this draft')
219
+ const [protect, setProtect] = useState(true)
220
+ const [event, setEvent] = useState('Edit the field or select Clear.')
221
+ return (
222
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', width: '320px', maxWidth: '100%' }}>
223
+ <Input
224
+ label="Draft"
225
+ value={value}
226
+ clearable
227
+ hint={event}
228
+ onValueChange={(event, { value }) => {
229
+ setValue(value)
230
+ setEvent(event.type === 'input' ? 'Draft updated.' : 'Draft committed.')
231
+ }}
232
+ onClearClick={event => {
233
+ if (protect) {
234
+ event.preventDefault()
235
+ setEvent('Clear cancelled. Turn off Protect draft to clear it.')
236
+ }
237
+ }}
238
+ onClearInput={() => setEvent('Draft cleared.')}
239
+ />
240
+ <Checkbox label="Protect draft" checked={protect}
241
+ onStateChange={(_, { next }) => setProtect(next)} />
242
+ <Button label="Restore draft" onClick={() => setValue('Keep this draft')} />
243
+ </div>
244
+ )
245
+ }
246
+ ```
262
247
 
263
248
  ## Forms
264
249
 
265
250
  ```tsx
266
- <form onSubmit={handle}>
251
+ <form>
267
252
  <Input name="email" label="Email" type="email" autoComplete="email" required clearable />
268
253
  <Button tone="brand" type="submit" label="Sign up" />
269
254
  <Button priority="tertiary" type="reset" label="Reset" />
270
255
  </form>
271
256
  ```
272
257
 
273
- Because `<a-input>` is form-associated, it submits with the surrounding
274
- `<form>` under its `name` — `new FormData(form)` includes it, no hidden mirror
275
- needed. `required` participates in native validity, and `<Button type="reset">`
276
- restores the field to its `defaultValue` via `formResetCallback`.
258
+ Input submits its value under `name` with the surrounding form, and
259
+ `new FormData(form)` includes it. Native constraints such as `required` block
260
+ normal submission while invalid. A reset button restores `defaultValue`.
277
261
 
278
262
  ## Types and validation
279
263
 
@@ -286,98 +270,92 @@ restores the field to its `defaultValue` via `formResetCallback`.
286
270
  <Input type="number" label="Number" autoComplete="off" min={0} max={100} />
287
271
  ```
288
272
 
289
- `type` accepts `text` (default), `email`, `password`, `tel`, `url`, and
290
- `number` — each gets the right mobile keyboard, autofill behavior, and native
291
- constraint validation. (`search` is omitted on purpose — see above.)
292
- `number` also takes `min` / `max` / `step`.
293
-
294
- Set `autoComplete` to describe the field's purpose. `email`, `tel`, and `url`
295
- derive their matching token from `type`. Other purposes need an explicit token,
296
- such as `name`, `username`, `current-password`, `new-password`, or
297
- `one-time-code`. Use `autoComplete="off"` for examples and application fields
298
- that do not represent autofill data.
299
-
300
- For a **controlled** numeric field — one whose `value` you hold in state — prefer
301
- `type="text"` with `inputMode="decimal"` (or `"numeric"`) over `type="number"`. A
302
- number input runs value sanitization, so a mid-typing `"1."` or `"1.0"` reports an
303
- empty `value` and can't be entered; storing the parsed `Number` and rendering it
304
- back drops the decimal for the same reason. Keep the raw string in state and parse
305
- to a number where you use it — `inputMode` still brings up the numeric keypad. An
306
- **uncontrolled** `type="number"` (no bound `value`) has neither problem.
307
-
308
- Every type carries its native constraints (`required`, the `type` itself,
309
- `pattern`, `min`/`max`, `min`/`maxLength`). The field reports them to the
310
- surrounding `<form>` automatically (via `ElementInternals`), so submission is
311
- blocked while invalid — no extra wiring.
273
+ Choose `type` to match what someone will enter: text, an email address,
274
+ a password, a phone number, a URL, or a number. Use `search` for a search field.
275
+
276
+ Set `autoComplete` to help the browser fill in familiar information, such as a
277
+ name or saved password. Email, telephone, and URL fields already use matching
278
+ autocomplete hints. Use `autoComplete="off"` where autofill isn't useful.
279
+
280
+ For numeric text that needs to keep partial entries or trailing zeros, use
281
+ `type="text"` with `inputMode="decimal"` or `"numeric"`. Keep the text as entered
282
+ and convert it to a number when needed. `inputMode` suggests a keyboard without
283
+ restricting what can be entered.
284
+
285
+ Use `required`, length limits, a `pattern`, or numeric limits to check values
286
+ before submission. Available checks depend on the field type. A `pattern`
287
+ checks the finished value without blocking typing.
312
288
 
313
289
  ### Native validation
314
290
 
315
291
  ```tsx
316
- // Surface the browser's own validity messages as Anta's critical-status UI.
317
- const [errors, setErrors] = useState({})
318
-
319
- function onSubmit(e) {
320
- e.preventDefault()
321
- const next = {}
322
- e.currentTarget.querySelectorAll('a-input').forEach((el) => {
323
- if (!el.checkValidity()) next[el.name] = el.validationMessage
324
- })
325
- setErrors(next)
292
+ import { useState } from 'preact/hooks'
293
+ import { Input, Button } from '@antadesign/anta'
294
+
295
+ function Demo() {
296
+ const [errors, setErrors] = useState<Record<string, string | undefined>>({})
297
+
298
+ function onSubmit(e: SubmitEvent) {
299
+ e.preventDefault()
300
+ const next: Record<string, string | undefined> = {}
301
+ const form = e.currentTarget as HTMLFormElement
302
+ form.querySelectorAll('a-input').forEach((el: any) => {
303
+ if (!el.checkValidity()) next[el.name] = el.validationMessage
304
+ })
305
+ setErrors(next)
306
+ }
307
+ const clearError = (name: string) => setErrors(previous => ({ ...previous, [name]: undefined }))
308
+
309
+ return (
310
+ <form noValidate onSubmit={onSubmit}
311
+ style={{ display: 'flex', flexDirection: 'column', gap: '14px', width: '300px' }}>
312
+ <Input name="email" label="Email" type="email" autoComplete="email"
313
+ placeholder="you@example.com" required hint={errors.email}
314
+ status={errors.email ? 'critical' : undefined}
315
+ onInput={() => clearError('email')} />
316
+ <Input name="site" label="Website" type="url" autoComplete="url"
317
+ placeholder="https://…" hint={errors.site}
318
+ status={errors.site ? 'critical' : undefined}
319
+ onInput={() => clearError('site')} />
320
+ <Input name="age" label="Age" type="number" autoComplete="off"
321
+ placeholder="18 – 120" min="18" max="120" hint={errors.age}
322
+ status={errors.age ? 'critical' : undefined}
323
+ onInput={() => clearError('age')} />
324
+ <div style={{ display: 'flex', gap: '8px' }}>
325
+ <Button type="submit" tone="brand" label="Submit" />
326
+ <Button type="reset" priority="tertiary" label="Reset" onClick={() => setErrors({})} />
327
+ </div>
328
+ </form>
329
+ )
326
330
  }
327
-
328
- <form noValidate onSubmit={onSubmit}>
329
- <Input name="email" type="email" autoComplete="email" required hint={errors.email}
330
- status={errors.email ? 'critical' : undefined}
331
- onInput={() => setErrors(p => ({ ...p, email: undefined }))} />
332
- <Input name="age" type="number" autoComplete="off" min="18" max="120" hint={errors.age}
333
- status={errors.age ? 'critical' : undefined}
334
- onInput={() => setErrors(p => ({ ...p, age: undefined }))} />
335
- <Button type="submit" label="Submit" />
336
- </form>
337
331
  ```
338
332
 
339
- The element proxies native validity: `<a-input>` exposes `checkValidity()` and
340
- `validationMessage` (the browser's localized message).
333
+ You can show the browser's validation messages as hints. Check each field
334
+ with `checkValidity()`, put its `validationMessage` in `hint`, and use
335
+ `status="critical"` for errors. Add `noValidate` to the form when your handler
336
+ will display the messages instead of the browser's popup.
341
337
 
342
- Pass `validationMessage` to **`hint`** and set **`status="critical"`** — the
343
- field goes red, shows the warning glyph, and reports invalid to the form. Mark
344
- the form `noValidate` so the browser's default bubbles step aside for Anta's
345
- styling. Try submitting empty, or with a bad email / out-of-range age.
346
-
347
- **Enter doesn't submit the form.** The real `<input>` lives in shadow DOM, so
348
- it isn't a light-DOM member of the `<form>`, and pressing Enter won't trigger
349
- native implicit submission — a deliberate choice, since auto-submitting a
350
- half-filled multi-field form is usually more annoyance than help. To submit on
351
- Enter, handle it yourself (`onKeyDown` → `form.requestSubmit()`), or reach for
352
- Anta's upcoming `<Form>` component. Everything else — value submission under
353
- `name`, `checkValidity()`, `formReset` — works natively.
338
+ Try submitting an empty email, an invalid address, or an age outside the range.
339
+ Use **Submit** to send the form. Input does not submit automatically on Enter.
354
340
 
355
341
  ## Layout
356
342
 
357
- The label, field, and hint **aren't hardcoded to "stacked."** The element lays
358
- them out as a single-column **grid** (hence the default label-above / hint-below
359
- rhythm) and exposes each region as a shadow part (`label`, `field`, `hint`).
360
- Rearrange them from your own CSS: re-template the grid on `a-input`, then place
361
- the parts. No layout prop to learn — your rule wins because the element's
362
- defaults live in `@layer anta`.
343
+ Labels appear above their fields and hints below. Use CSS to move a label
344
+ beside its field, align labels across a form, or place a hint alongside a field.
345
+ The examples show the selectors to use.
363
346
 
364
- And because each `<a-input>` is itself a normal grid/flex item, **wrapping** is
365
- just sizing: give the inputs a `min-width` (and maybe `max-width`) inside any
366
- wrapping container and they reflow by available width — the field's internal
367
- grid is independent of how the fields themselves flow.
347
+ You can also arrange whole fields in columns or let them wrap as the available
348
+ space changes.
368
349
 
369
- The `.label-side` / `.aligned-form` / `.wrap-fields` classes below are only to
370
- scope each demo — swap your own selector.
350
+ The classes below scope each example. Replace them with your own selectors.
371
351
 
372
352
  ### Label on the left
373
353
 
374
- Re-template the host to two columns and drop each part into one. The hint stays
375
- under the field by landing in the same column.
354
+ Use two columns for the label and field. Place the hint in the field's column.
376
355
 
377
356
  ```tsx
378
357
  <Input className="label-side" label="Display name" hint="Shown on your profile" autoComplete="name" />
379
358
 
380
- {/* .label-side is just for the demo — use your own selector */}
381
359
  <style>{`
382
360
  a-input.label-side {
383
361
  grid-template-columns: max-content minmax(0, 1fr);
@@ -392,10 +370,8 @@ under the field by landing in the same column.
392
370
 
393
371
  ### Label column
394
372
 
395
- Put the fields in a grid and let each one adopt the form's tracks with
396
- `grid-template-columns: subgrid` — which reaches *through* the shadow boundary,
397
- so every label shares one column sized to the longest label, and every field
398
- left-edge lines up. Each hint still tucks under its own field.
373
+ Keep labels in a shared column so fields line up even when their labels have
374
+ different lengths. Each hint stays with its field.
399
375
 
400
376
  ```tsx
401
377
  <div className="aligned-form">
@@ -405,7 +381,6 @@ left-edge lines up. Each hint still tucks under its own field.
405
381
  </div>
406
382
 
407
383
  <style>{`
408
- /* one shared label column; subgrid lets each field adopt the form's tracks */
409
384
  .aligned-form { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 16px 12px; }
410
385
  .aligned-form > a-input { grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; }
411
386
  .aligned-form > a-input::part(label) { grid-column: 1; }
@@ -416,9 +391,8 @@ left-edge lines up. Each hint still tucks under its own field.
416
391
 
417
392
  ### Hint on the right
418
393
 
419
- Put the hint beside the field instead of under it — a short note, a counter, a
420
- unit. Two columns (field, then hint); the label spans the top, and the hint
421
- centers against the field.
394
+ Place a short hint or counter beside the field. The label spans both columns,
395
+ and the hint aligns vertically with the field.
422
396
 
423
397
  ```tsx
424
398
  <Input className="hint-right" label="Username" hint="3–20 characters" placeholder="ada" autoComplete="username" />
@@ -436,9 +410,8 @@ centers against the field.
436
410
 
437
411
  ### Wrap by field width
438
412
 
439
- Leave the fields stacked and let an outer grid flow them into as many columns as
440
- fit — `minmax(200px, 1fr)` makes each field claim at least 200px, so the row
441
- wraps to fewer columns as it narrows. Resize the preview to see it reflow.
413
+ Let fields share a row when there is room and wrap when space is limited.
414
+ Narrow the preview to see the layout adjust.
442
415
 
443
416
  ```tsx
444
417
  <div className="wrap-fields">
@@ -449,7 +422,6 @@ wraps to fewer columns as it narrows. Resize the preview to see it reflow.
449
422
  </div>
450
423
 
451
424
  <style>{`
452
- /* fields stay stacked; the grid wraps them by available width */
453
425
  .wrap-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; }
454
426
  `}</style>
455
427
  ```
@@ -458,56 +430,87 @@ wraps to fewer columns as it narrows. Resize the preview to see it reflow.
458
430
 
459
431
  | Prop | Type | Default | Description |
460
432
  |------|------|---------|-------------|
461
- | `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`. |
433
+ | `autoComplete?` | 'off' \| 'on' \| 'name' \| 'username' \| 'email' \| 'current-password' \| 'new-password' \| 'one-time-code' \| 'tel' \| 'url' \| (string & {}) | — | Native autocomplete token describing the field purpose. Overrides the token derived from the email, tel, or url type. Set an explicit token for other purposes, such as `username`, `current-password`, or `one-time-code`. |
462
434
  | `autoFocus?` | boolean | — | Focus this field when its containing `Dialog` opens. |
463
435
  | `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. |
464
- | `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. |
465
- | `clearable?` | boolean | — | Show a clear button as the first trailing item once the field has a value. |
466
- | `defaultValue?` | string | — | Initial value for the uncontrolled case. |
467
- | `dimActions?` | boolean | — | Dim the `leading` / `trailing` adornments at rest; they brighten to full when the field is hovered or focused (a quiet-until-engaged affordance for trailing actions). |
436
+ | `children?` | ReactNode | — | Extra content between the field and hint. A `Tooltip` child anchors to the field. Use `leading` and `trailing` for content inside the field. |
437
+ | `clearable?` | boolean | — | Show a clear button before trailing content when an editable field has a value. |
438
+ | `defaultValue?` | string | — | Initial value for an uncontrolled field, restored on form reset. |
439
+ | `dimActions?` | boolean | — | Keep leading, trailing, and clear content subtle until someone hovers over or focuses the field. |
468
440
  | `disabled?` | boolean | — | Disable the field. |
469
- | `hint?` | ReactNode | — | Message below the field. Neutral helper text by default; `status` recolors it and prefixes the matching glyph. |
470
- | `inputMode?` | 'none' \| 'text' \| 'decimal' \| 'numeric' \| 'tel' \| 'search' \| 'email' \| 'url' | — | Virtual-keyboard hint. Overrides the value derived from `type`. |
471
- | `label?` | ReactNode | — | Field label, shown above the control. A string is rendered with the label type scale; pass a node for full control. Associated with the control as its accessible name (the element mirrors the label text to `aria-label`, since `<label for>` can't cross the shadow boundary). |
472
- | `leading?` | ReactNode | — | Content pinned to the start of the field (e.g. an icon). |
473
- | `max?` | number \| string | — | |
474
- | `maxLength?` | number | — | Max input length. |
475
- | `maxRows?` | number | — | Cap the autogrow height (in rows) of a `multiline` field with no `rows`. Omit for unbounded growth. |
476
- | `min?` | number \| string | — | Min / max / step — for `type="number"`. |
477
- | `minLength?` | number | — | Min input length. |
478
- | `multiline?` | boolean | — | Render a `<textarea>` instead of an `<input>`. Without `rows` it grows with its content from one line (capped by `maxRows` if set). Autogrow uses CSS `field-sizing` where supported (Chrome/Edge, Safari ≥ 26.2) and falls back to a built-in JS resize elsewhere (Firefox, older Safari), so it grows in every browser. |
479
- | `name?` | string | — | Form field name — submitted with the form via ElementInternals. |
441
+ | `hint?` | ReactNode | — | Persistent help or feedback below the field. `status` colors the message and adds a status icon. |
442
+ | `inputMode?` | 'none' \| 'text' \| 'decimal' \| 'numeric' \| 'tel' \| 'search' \| 'email' \| 'url' | — | Virtual keyboard hint. Overrides the value derived from `type` without changing validation. |
443
+ | `label?` | ReactNode | — | Explain what to enter. Accepts text or JSX and also names the field for screen readers. |
444
+ | `leading?` | ReactNode | — | Content at the start of the field, such as an icon or text. |
445
+ | `max?` | number \| string | — | Maximum value for `type="number"`. |
446
+ | `maxLength?` | number | — | Maximum text length for native types that support it. |
447
+ | `maxRows?` | number | — | Maximum row count for autogrow when `rows` is unset. Omit for unlimited growth. |
448
+ | `min?` | number \| string | — | Minimum value for `type="number"`. |
449
+ | `minLength?` | number | — | Minimum text length for native types that support it. |
450
+ | `multiline?` | boolean | — | Allow longer text and grow the field as someone types. Use `maxRows` to limit growth or `rows` to keep a fixed height. |
451
+ | `name?` | string | — | Form field name for submission, named form lookup, and attribute selectors. |
480
452
  | `onBlur?` | (e) => void | — | Fires when the field loses focus. |
481
- | `onChange?` | (e) => void | — | Fires on **commit** (blur / Enter) — the platform `change` semantics, **not** React's per-keystroke `onChange`. This is a web component, so `onChange` keeps the native meaning; reach for `onInput` (every keystroke) or `onValueChange` (both) for live updates. Read `e.target.value`. |
482
- | `onClearClick?` | (e) => void | — | Fires when the built-in clear button (`clearable`) is clicked, *before* the field is cleared. Call `e.preventDefault()` to keep the current value — the clear is cancelled and `onClearInput` won't fire. Backed by the element's cancelable, bubbling `clearclick` event. |
483
- | `onClearInput?` | (e) => void | — | Fires after the built-in clear button (`clearable`) has cleared the field — so `onInput` / `onChange` fire too — making this useful for reacting specifically to a clear. Doesn't fire if `onClearClick` cancelled the clear. Backed by the element's bubbling `clearinput` event. |
453
+ | `onChange?` | (e) => void | — | Fires on native commit, such as blur or Enter, and on clear. Use `onInput` or `onValueChange` for live edits. Read `e.target.value`. |
454
+ | `onClearClick?` | (e) => void | — | Fires before the built-in clear action. Call `e.preventDefault()` synchronously to keep the value and cancel `onClearInput`. Corresponds to the cancelable, bubbling `clearclick` event. |
455
+ | `onClearInput?` | (e) => void | — | Fires after clearing, following `onInput` and `onChange`. Does not fire when clearing is canceled. Corresponds to the bubbling `clearinput` event. |
484
456
  | `onFocus?` | (e) => void | — | Fires when the field gains focus. |
485
- | `onInput?` | (e) => void | — | Fires on every keystroke. Read `e.target.value`. |
486
- | `onValueChange?` | (event, attrs) => void | — | Unified value-change handler — the easy path for state. Fires on `input` *and* `change` (and on clear), with the native `event` plus a convenience `attrs` snapshot (`value`, `name`, `empty`, `valid`, `validationMessage`) so you can do `setForm(s => ({ ...s, [attrs.name]: attrs.value }))` without digging into the event. Use `event.type` to tell a live edit (`input`) from a commit (`change`); read `id` / `type` / `className` off `event.target`. |
487
- | `pattern?` | string | — | Validation pattern (single-line). |
457
+ | `onInput?` | (e) => void | — | Fires when editing changes the value or the field is cleared. Read `e.target.value`. |
458
+ | `onValueChange?` | (event, attrs) => void | — | Receive the current text, field name, and validation result while editing and when an edit is committed, including clear. Can run more than once for the same value. Use `event.type` to distinguish the events. |
459
+ | `pattern?` | string | — | Validation pattern for supported single-line types. Checks validity without preventing typing. |
488
460
  | `placeholder?` | string | — | Placeholder shown when empty. |
489
461
  | `priority?` | 'primary' \| 'secondary' \| 'tertiary' | primary | Field emphasis. `primary` has a fill and border, `secondary` keeps the border without a fill, and `tertiary` makes the border transparent until hover or focus. Status borders remain visible at rest. |
490
462
  | `readOnly?` | boolean | — | Make the field read-only. |
491
463
  | `required?` | boolean | — | Mark the field required (drives native validity). |
492
- | `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. |
493
- | `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). |
494
- | `rows?` | number | — | Fixed visible row count — a constant-height `<textarea>` (implies `multiline`). |
495
- | `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). |
464
+ | `role?` | string | — | ARIA role for the native control, such as `combobox` for an InputAutocomplete trigger. The element delegates this and standard `aria-*` props to its native shadow control. |
465
+ | `round?` | boolean \| number \| string | — | Give the field fully rounded corners. Pass a number in pixels or a CSS length for a custom radius. The clear button stays circular. |
466
+ | `rows?` | number | — | Fixed visible row count. Also enables `multiline`. |
467
+ | `size?` | 'small' \| 'medium' \| 'large' | medium | Adjust the field, text, icons, and action buttons together to suit the surrounding interface. |
496
468
  | `spellCheck?` | boolean | — | Toggle native spell-checking. |
497
- | `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | — | Validation / feedback tone — colors the border + `hint` and prefixes a glyph. Only `critical` marks the field invalid (`aria-invalid`, blocks form submission, `:state(invalid)`); `success` / `warning` / `info` / `brand` are advisory and stay valid. Omit (or `neutral`) for a plain field. |
498
- | `statusIcon?` | (string & {}) \| false \| IconShape | — | Glyph shown before the `hint` when `status` is set. Each status has a default (critical → `warning-diamond`, warning → `warning-triangle`, success → `circle-check`, info → `info`, brand → `circle-small-solid`); pass a shape to override, or `false` to drop it. `neutral` has no default glyph. |
499
- | `step?` | number \| string | — | |
500
- | `tone?` | string | — | Custom accent color — any literal CSS color tints the border when shown (focus ring stays the global `--focus-ring`). For consistency with the other controls' custom-tone knob; a `status` still overrides for validation. |
501
- | `trailing?` | ReactNode | — | Content pinned to the end of the field (e.g. icons, buttons), after the clear button when `clearable`. |
469
+ | `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | — | Feedback for the field and hint. Use `critical` for an error that must be fixed before submission. Other statuses provide feedback without adding a validation error. Required fields and other validation rules still apply. |
470
+ | `statusIcon?` | (string & {}) \| false \| IconShape | — | Icon before the hint. The defaults are `warning-diamond` for critical, `warning-triangle` for warning, `circle-check` for success, `info` for info, and `circle-small-solid` for brand. Pass a shape to replace it or `false` to hide it. Neutral has no default icon. |
471
+ | `step?` | number \| string | — | Allowed increment for `type="number"`. |
472
+ | `tone?` | string | — | Custom CSS border color. A non-neutral `status` takes precedence. The focus ring uses the global `--focus-ring`. |
473
+ | `trailing?` | ReactNode | — | Content at the end of the field, after the clear button. Anta buttons fit the field height and keep their padding. Icon-only Buttons are square; text buttons keep their natural width. Pass buttons directly or in a fragment for compact spacing. Other wrapping elements keep the text inset. Override either inset with the `--input-trailing-inset` CSS property. |
502
474
  | `truncate?` | boolean | true | Ellipsize an overflowing single-line value. Read-only inputs already do this; pass `false` when an editable field should show the full value. |
503
- | `type?` | 'text' \| 'search' \| 'email' \| 'password' \| 'tel' \| 'url' \| 'number' | text | Single-line input type. Ignored when `multiline`. `search` is a **wrapper-only** shorthand: it defaults a leading search icon and a clear button (both overridable — pass your own `leading`, or `clearable={false}`) and sets `inputmode="search"`, but the DOM input stays `type="text"`. The native `search` type never reaches the element, so the browser's own clear/search affordances never appear — Anta owns that chrome. |
504
- | `value?` | string | — | Controlled value. Pair with `onChange` / `onInput`. |
475
+ | `type?` | 'text' \| 'search' \| 'email' \| 'password' \| 'tel' \| 'url' \| 'number' | text | Choose the kind of information to enter. Ignored for multiline fields. `search` adds a search icon and clear button. Replace the icon with `leading` or hide the button with `clearable={false}`. |
476
+ | `value?` | string | — | Controlled value. Update it with `onInput` or `onValueChange` for live editing. |
505
477
 
506
478
  ## Web Component
507
479
 
508
- Use the web component directly when you are not using React or Preact and a native control does not fit.
480
+ Use `<a-input>` directly outside React or Preact.
481
+
482
+ Slots accept content; shadow parts expose regions for CSS. The `label`,
483
+ `leading`, `clear`, `trailing`, and `hint` slots are independent of the parts
484
+ with matching names. Unslotted children appear after the field and before the hint.
485
+
486
+ Group trailing buttons in `<a-input-trailing slot="trailing">` to use compact
487
+ action spacing. Mixed content keeps the text inset. The grouping tag needs no
488
+ JavaScript registration.
489
+
490
+ Anta buttons fit the field height and keep their own padding. Add
491
+ `data-anta-icon-only` to a raw icon-only button for a square box. Text buttons
492
+ keep their natural width. The JSX Button and ButtonCopy wrappers mark their
493
+ icon-only actions automatically.
494
+
495
+ Set `role` and `aria-*` on `<a-input>` to describe the field to assistive
496
+ technology. Input applies them to the control that receives focus. Explicit
497
+ names and descriptions take precedence over its label and hint.
509
498
 
510
- Slots hold the label, hint, and controls around the native field.
499
+ The JSX `type="search"` shorthand uses a native text input. On a raw `<a-input>`,
500
+ `type="search"` uses a native search input. Listen for `clearclick` and
501
+ `clearinput` with `addEventListener()` to handle clearing without a JSX wrapper.
502
+
503
+ The `name` property and attribute stay in sync. Updating either changes the
504
+ submitted field name, `form.elements.namedItem()` lookup, and attribute selectors.
505
+ Removing the name excludes the field from form submission.
506
+
507
+ `checkValidity()`, `reportValidity()`, `validity`, `validationMessage`, and
508
+ `willValidate` expose form validation. `status="critical"` adds a custom error;
509
+ other statuses remain advisory. There is no public `setCustomValidity()` method.
510
+
511
+ Use `rows` for a fixed height or `maxrows` to limit growth. Some browsers
512
+ recalculate a growing field's height on the next value change after its width
513
+ or font changes.
511
514
 
512
515
  ```html
513
516
  <a-input name="email" type="email" autocomplete="email" placeholder="you@example.com">
@@ -518,32 +521,27 @@ Slots hold the label, hint, and controls around the native field.
518
521
 
519
522
  ### Native HTML inputs
520
523
 
521
- For a standard HTML form, add `data-anta` to a native input. Text, search,
522
- email, telephone, URL, password, and number inputs keep their native editing,
523
- keyboard, validation, and form behavior while using Anta's field chrome. Search
524
- keeps its browser-supplied affordances; file keeps its native chooser and selected
525
- file name, with an Anta-styled chooser button. `submit` and `reset` inputs use the
526
- matching Anta button chrome; set `priority="primary"` on a submit control when it
527
- is the form's main action.
524
+ Add `data-anta` to an HTML input to give it Anta styling while keeping its
525
+ usual browser behavior. Search and file inputs keep their built-in controls.
526
+ Submit and reset inputs look like Anta buttons. Use `priority="primary"` for
527
+ the form's main action.
528
528
 
529
- `<textarea data-anta>` uses the same field chrome. Set `rows` for its height; one
530
- field-height is the minimum, and it resizes vertically. Native textareas do not
531
- autogrow; use Input's [`multiline`](#multiline) instead.
529
+ Use `<textarea data-anta>` for longer text with a manually resizable height.
530
+ For a field that grows as someone types, use [multiline Input](#multiline).
532
531
 
533
- Use `data-anta-size="small"` or `"large"` for field sizing. Native inputs own
534
- their `size` attribute, so Anta does not reuse it. `round` and a custom-color
535
- `tone` work the same way as Input.
532
+ Choose a size with `data-anta-size="small"` or `"large"`.
533
+ Use `round` for rounded corners and `tone` for a custom border color.
536
534
 
537
535
  ```html
538
- <input data-anta data-anta-size="small" type="text" name="name" placeholder="Small text">
539
- <input data-anta type="search" name="search" placeholder="Search">
540
- <input data-anta type="email" name="email" placeholder="Email">
541
- <input data-anta type="tel" name="phone" placeholder="Telephone">
542
- <input data-anta round type="url" name="site" placeholder="Rounded URL">
543
- <input data-anta type="password" name="password" value="password">
544
- <input data-anta tone="rebeccapurple" type="number" name="seats" value="3" min="1">
545
- <textarea data-anta rows="3" name="notes" placeholder="Notes"></textarea>
546
- <input data-anta type="file" name="attachment" accept=".pdf,.docx">
536
+ <input data-anta data-anta-size="small" type="text" name="name" aria-label="Name" placeholder="Small text">
537
+ <input data-anta type="search" name="search" aria-label="Search" placeholder="Search">
538
+ <input data-anta type="email" name="email" aria-label="Email" placeholder="Email">
539
+ <input data-anta type="tel" name="phone" aria-label="Telephone" placeholder="Telephone">
540
+ <input data-anta round type="url" name="site" aria-label="Website" placeholder="Rounded URL">
541
+ <input data-anta type="password" name="password" aria-label="Password" value="password">
542
+ <input data-anta tone="rebeccapurple" type="number" name="seats" aria-label="Seats" value="3" min="1">
543
+ <textarea data-anta rows="3" name="notes" aria-label="Notes" placeholder="Notes"></textarea>
544
+ <input data-anta type="file" name="attachment" aria-label="Attachment" accept=".pdf,.docx">
547
545
 
548
546
  <input data-anta type="submit" value="Submit" priority="primary">
549
547
  <input data-anta type="reset" value="Reset">
@@ -551,82 +549,78 @@ their `size` attribute, so Anta does not reuse it. `round` and a custom-color
551
549
 
552
550
  ## Styling
553
551
 
554
- Reach for the props first: **`priority`** sets the field surface,
555
- **`status`** a validation tone (border + message), **`tone`** a custom accent
556
- color for the border (any CSS color), and **`size`** the dimensions. The focus
557
- ring is the global [`--focus-ring`](../colors.md#focus-ring).
552
+ Use `priority` for the field surface, `status` for feedback, `tone` for a custom
553
+ border color, and `size` for dimensions. The focus ring uses the global
554
+ [`--focus-ring`](../colors.md#focus-ring).
558
555
 
559
556
  ```tsx
560
557
  <Input tone="#e0457b" label="Custom accent" autoComplete="off" />
561
558
  ```
562
559
 
563
- For everything else, `<a-input>` is shadow-DOM — style its **parts** with plain
564
- CSS: `::part(field | input | label | leading | trailing | clear | hint)`, plus the
565
- `:state(filled)` / `:state(invalid)` state hooks and `:focus-within::part(field)`
566
- for the focused state. The clear button is a real `<a-button>` in the `clear` slot,
567
- so style it directly. `light-dark()` works too (Anta sets `color-scheme` from its
568
- theme toggle).
560
+ Style shadow regions with `::part(field)`, `::part(input)`, `::part(input-label)`,
561
+ `::part(label)`, `::part(leading)`, `::part(trailing)`, `::part(clear)`, and
562
+ `::part(hint)`. Use `:state(filled)` for a nonempty value,
563
+ `:state(invalid)` for critical status, and `:focus::part(field)` when the
564
+ editable control is focused. Focus on a slotted action does not match this rule. The clear button lives in the light DOM, so target it
565
+ with a regular selector. Use `light-dark()` for theme-specific colors.
566
+
567
+ Set `--input-trailing-inset` to override the ending space for any trailing
568
+ content. Text and mixed content use the field's text inset by default;
569
+ buttons-only groups use compact spacing.
570
+
571
+ In button fields, `::part(input)` targets the native button, and
572
+ `::part(input-label)` targets its value or placeholder text. `::part(label)`
573
+ targets the separate visible field label.
574
+
575
+ Select uses the same field styling. See [Select styling](./select.md#styling) for
576
+ trigger customization.
569
577
 
570
578
  ```css
571
579
  a-input::part(field) { border-radius: 999px; }
572
580
  a-input::part(input) { font-variant-numeric: tabular-nums; }
573
581
  a-input::part(label) { text-transform: uppercase; }
582
+ a-input.custom-inset { --input-trailing-inset: 4px; }
574
583
  ```
575
584
 
576
- The border is drawn as an **inset `box-shadow`**, so its width (`0.5px` at rest,
577
- including tertiary's transparent border, and `1px` on hover, focus, or
578
- non-neutral status)
579
- never changes the box size. The field height stays aligned with `Button`.
580
- Drop the leading `inset` to draw the border as an outset ring instead. You can
581
- also re-point an `--input-*` color token on one instance
582
- (`style={{ '--input-border': 'var(--border-1)' }}`)
583
- or a wrapping selector; the resolver lives in `@layer anta`, so any un-layered rule
584
- of yours wins.
585
+ To remove the field's border, use `box-shadow: none` as shown in the example.
585
586
 
586
- The `.fancy` class is just for the demo — a borderless pill that fills dark-grey
587
- and darkens on focus in light mode, and the inverse in dark:
587
+ If your policy blocks inline styles, set custom colors and dimensions in an
588
+ external stylesheet. See [Content Security Policy](../install-config.md#content-security-policy)
589
+ for customization props that emit inline styles.
590
+
591
+ Some browsers may show native number steppers or controls on a raw
592
+ `<a-input type="search">`. These keep their browser behavior. The JSX
593
+ `<Input type="search">` uses a text input with Anta's own search icon and clear
594
+ button.
595
+
596
+ This example creates a borderless pill with a dark fill in light mode and a
597
+ light fill in dark mode. The fill changes on focus.
588
598
 
589
599
  ```css
590
- /* a borderless pill — the resting border is a box-shadow, so clear it. Dark-grey
591
- fill that darkens on focus, white text; the inverse in dark mode via light-dark()
592
- (Anta drives color-scheme from its theme toggle). */
593
600
  a-input.fancy::part(field) {
594
601
  border-radius: 999px;
595
- box-shadow: none; /* remove the box-shadow border */
602
+ box-shadow: none;
596
603
  background: light-dark(#3a3a3a, #ececec);
597
604
  padding-block: 10px;
598
- padding-inline: 20px; /* roomier */
605
+ padding-inline: 20px;
599
606
  }
600
607
  a-input.fancy::part(input) { color: light-dark(#ffffff, #1a1a1a); }
601
608
  a-input.fancy::part(input)::placeholder {
602
609
  color: light-dark(#b8b8b8, #666666);
603
610
  opacity: 1;
604
611
  }
605
- a-input.fancy:focus-within::part(field) { background: light-dark(#161616, #ffffff); }
612
+ a-input.fancy:focus::part(field) { background: light-dark(#161616, #ffffff); }
606
613
  ```
607
614
 
608
615
  ### Placeholder contrast
609
616
 
610
- If the label, marking, context, or hint leaves the field's purpose unclear,
611
- a stronger placeholder can help. Keep an accessible name and put essential
612
- instructions in a persistent label or hint.
613
-
614
- Antune uses `--text-4` for placeholders. These ratios compare the default neutral
615
- Input backgrounds with the example's literal color:
616
-
617
- | Style | Placeholder | Background | Contrast |
618
- | --- | --- | --- | --- |
619
- | Antune light theme | `#7a737c` | `#ffffff` | 4.59:1 |
620
- | Antune dark theme | `#7c737c` | `#000000` | 4.60:1 |
621
- | Example | `#767676` | `#ffffff` | 4.54:1 |
617
+ Use placeholders for example values or formatting cues. Keep an accessible
618
+ name and put essential instructions in a persistent label or hint.
622
619
 
623
- `#767676` is the lightest opaque 8-bit neutral gray that passes 4.5:1 on white.
624
- W3C recommends [sufficient placeholder contrast](https://www.w3.org/WAI/tutorials/forms/instructions/#styling).
625
- Redundant placeholders remain debated in the
626
- [meeting discussion](https://www.w3.org/2025/04/16-matf-minutes.html) and
627
- [open W3C issue](https://github.com/w3c/wcag/issues/4343); there is no agreed exemption.
620
+ Keep placeholder text readable against the field background in both light and
621
+ dark themes. See W3C's [placeholder contrast guidance](https://www.w3.org/WAI/tutorials/forms/instructions/#styling).
628
622
 
629
- This demo uses `#767676` on white in every state and theme.
623
+ This example uses a darker placeholder on a fixed light background.
630
624
  Replace `.placeholder-contrast` with your own class.
631
625
 
632
626
  ```tsx