@antadesign/anta 0.3.18 → 0.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +38 -242
  2. package/dist/components/Input.d.ts +4 -1
  3. package/dist/components/Input.js +2 -0
  4. package/dist/components/InputDate.css +1 -0
  5. package/dist/components/InputDate.d.ts +1 -0
  6. package/dist/components/InputDate.js +5 -5
  7. package/dist/components/Menu.d.ts +5 -1
  8. package/dist/components/Menu.js +3 -2
  9. package/dist/components/Select.d.ts +1 -0
  10. package/dist/components/Select.js +9 -13
  11. package/dist/components/SelectFaceted.d.ts +1 -0
  12. package/dist/components/SelectFaceted.js +29 -23
  13. package/dist/components/select-parts.css +1 -0
  14. package/dist/elements/a-input.css +1 -1
  15. package/dist/elements/a-menu.js +12 -0
  16. package/dist/elements/a-slider.css +1 -1
  17. package/dist/elements/a-tabs.css +1 -1
  18. package/dist/elements/a-tabs.d.ts +3 -0
  19. package/dist/elements/a-tabs.js +40 -1
  20. package/dist/general_types.d.ts +2 -0
  21. package/dist/jsx-runtime.d.ts +9 -0
  22. package/docs/accessibility.md +34 -0
  23. package/docs/changelog.md +551 -0
  24. package/docs/colors.md +42 -0
  25. package/docs/comparison.md +28 -0
  26. package/docs/components/banner.md +267 -0
  27. package/docs/components/button.md +603 -0
  28. package/docs/components/card.md +322 -0
  29. package/docs/components/checkbox.md +297 -0
  30. package/docs/components/dialog.md +314 -0
  31. package/docs/components/expander.md +529 -0
  32. package/docs/components/icon.md +155 -0
  33. package/docs/components/input-autocomplete.md +101 -0
  34. package/docs/components/input-date.md +406 -0
  35. package/docs/components/input-time.md +174 -0
  36. package/docs/components/input.md +610 -0
  37. package/docs/components/loader.md +139 -0
  38. package/docs/components/menu.md +447 -0
  39. package/docs/components/progress.md +182 -0
  40. package/docs/components/radio.md +369 -0
  41. package/docs/components/select-faceted.md +456 -0
  42. package/docs/components/select.md +931 -0
  43. package/docs/components/slider.md +142 -0
  44. package/docs/components/switch.md +198 -0
  45. package/docs/components/tabs.md +471 -0
  46. package/docs/components/tag.md +185 -0
  47. package/docs/components/text.md +148 -0
  48. package/docs/components/title.md +146 -0
  49. package/docs/components/toaster.md +313 -0
  50. package/docs/components/tooltip.md +177 -0
  51. package/docs/credits.md +22 -0
  52. package/docs/index.md +52 -0
  53. package/docs/install-config.md +240 -0
  54. package/docs/normalization.md +105 -0
  55. package/docs/overview.md +10 -0
  56. package/docs/packages/stickers.md +187 -0
  57. package/docs/packages/table.md +153 -0
  58. package/docs/theming.md +13 -0
  59. package/package.json +10 -6
@@ -0,0 +1,610 @@
1
+ # Input
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)`.
8
+
9
+ ## Sizes
10
+
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" />
15
+ ```
16
+
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 |
26
+
27
+ ## Label & hint
28
+
29
+ ```tsx
30
+ <Input label="Display name" defaultValue="Ada Lovelace" />
31
+ <Input label="API key" hint="Find this in Settings → Developers." placeholder="sk-…" />
32
+ ```
33
+
34
+ `label` sits above the field; `hint` is neutral helper text below it
35
+ (`--text-3`, no icon). Both accept a **string or a React node**, so you can
36
+ embed links, `<code>`, or a `<Text>` for full control.
37
+
38
+ The label is wired as the control's accessible name automatically — the element
39
+ mirrors the label text into `aria-label`, since a native `<label for>` can't
40
+ cross the shadow boundary. The `hint` (or error) is mirrored the same way into
41
+ `aria-description`, so a screen reader announces the message too.
42
+
43
+ ## Tooltip
44
+
45
+ ```tsx
46
+ {/* Drop a <Tooltip> in as a child — it attaches to the field and shows on hover,
47
+ exactly like a tooltip on any other element. */}
48
+ <Input label="API key" placeholder="sk-…">
49
+ <Tooltip>Find this in Settings → Developers.</Tooltip>
50
+ </Input>
51
+ ```
52
+
53
+ Attach a tooltip by dropping an Anta `<Tooltip>` in as a **child** — no slot or
54
+ prop needed. It anchors to the field and shows on hover, consistent with how
55
+ tooltips attach to a `<Button>` or any other element. (Children with no `slot`
56
+ are projected into a default slot; use `leading` / `trailing` for *in-field*
57
+ content.)
58
+
59
+ ## Status
60
+
61
+ ```tsx
62
+ {/* 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
+ ```
70
+
71
+ `status` tints the border and the `hint`, and prefixes a glyph — `critical`,
72
+ `warning`, `success`, `info`, or `brand`. Each pulls its tone's role tokens, so
73
+ dark mode is automatic. The message always rides in `hint`; `status` only
74
+ recolors it.
75
+
76
+ It reuses the tone palette but isn't `tone`: **`status` is a validation *state*** (it
77
+ also carries the glyph and, for `critical`, validity), whereas `tone` — elsewhere in
78
+ Anta — is decorative colour. On `Input` the named tones live on `status`; `tone` is
79
+ reserved for a one-off custom border colour.
80
+
81
+ Only `status="critical"` carries validity — it sets `aria-invalid`, flips the
82
+ `:state(invalid)` hook, and reports invalid to a surrounding `<form>` through
83
+ `ElementInternals`. The other tones are advisory and stay valid.
84
+
85
+ Each status has a default glyph (`critical` → `warning-diamond`, `warning` →
86
+ `warning-triangle`, `success` → `circle-check`, `info` → `info`, `brand` →
87
+ `circle-small-solid`). Override it with **`statusIcon`** (any shape), or drop it
88
+ with `statusIcon={false}`.
89
+
90
+ ## Leading, trailing & clear
91
+
92
+ ```tsx
93
+ {/* clearable — the clear button shows once there's a value */}
94
+ {/* dimActions — adornments rest quiet, brighten when the field is hovered/focused */}
95
+ <Input label="Search" placeholder="Search…" clearable dimActions defaultValue="design tokens" />
96
+
97
+ {/* type="search" is a shorthand for the leading search icon + clearable below */}
98
+ <Input label="Search" type="search" placeholder="Search…" />
99
+
100
+ {/* leading / trailing take any node — see the live playground above */}
101
+ <Input label="Search" leading={<Icon shape="search" />} clearable dimActions />
102
+ <Input
103
+ label="Date"
104
+ leading={<Icon shape="calendar" />}
105
+ trailing={<Button priority="quaternary" size="small" icon="eye" aria-label="Pick" />}
106
+ dimActions
107
+ />
108
+ ```
109
+
110
+ `leading` and `trailing` paste any node at the field edges (like the expander's
111
+ `actions`) — an icon, a button, a unit label. A leading item is inset from the
112
+ left by the same `pad-x` as the text, so it lines up on the field's rhythm; the
113
+ playground at the top of this page shows a live leading icon alongside the clear
114
+ button.
115
+
116
+ **`type="search"`** is a shorthand for that search pattern: it defaults a leading
117
+ search icon and `clearable` (override either — pass your own `leading`, or
118
+ `clearable={false}`). It's wrapper-only — the native `search` type never reaches the
119
+ DOM input, so the browser's own clear/search chrome never appears; Anta owns it.
120
+
121
+ **`dimActions`** rests the adornments quiet (60% opacity) and brightens them to
122
+ full when the field is hovered or focused — a "quiet until engaged" look for
123
+ trailing actions. It dims the whole slot, so it covers icons and buttons alike.
124
+
125
+ `clearable` adds a clear button at the right edge, shown once the field has a
126
+ value. It's a real `<a-button>` in the `clear` slot — full button styling,
127
+ **keyboard-focusable** — and it works **without framework hydration**: a-button's
128
+ global handler fires a `clearrequest` trigger on click and Enter/Space, which the
129
+ element turns into the lifecycle below.
130
+
131
+ Two hooks bracket the clear. **`onClearClick`** fires first and is **cancelable** —
132
+ `e.preventDefault()` keeps the value (e.g. a "discard unsaved text?" confirm). If
133
+ it isn't prevented, the field clears: it empties, fires `input` / `change` (a
134
+ controlled `onChange` runs with the empty value), refocuses, then fires
135
+ **`onClearInput`**. On a raw `<a-input>`, bind both lowercase (`onclearclick` /
136
+ `onclearinput`) — that form binds in React and Preact alike.
137
+
138
+ Note: there's no `type="search"` — that type makes browsers inject their own
139
+ clear button and magnifier (and style them inconsistently). Anta owns every
140
+ in-field control, and the element resets the native search/number/reveal
141
+ decorations across browsers.
142
+
143
+ ### Password reveal
144
+
145
+ ```tsx
146
+ // Reveal toggle = flip `type` between password and text. Native masking when
147
+ // hidden, plain text when shown; password managers keep working.
148
+ const [reveal, setReveal] = useState(false)
149
+
150
+ <Input
151
+ label="Password"
152
+ type={reveal ? 'text' : 'password'}
153
+ defaultValue="hunter2"
154
+ dimActions
155
+ trailing={
156
+ <Button
157
+ priority="tertiary"
158
+ icon={reveal ? 'eye-closed' : 'eye'}
159
+ aria-label={reveal ? 'Hide password' : 'Show password'}
160
+ onClick={() => setReveal(v => !v)}
161
+ />
162
+ }
163
+ />
164
+ ```
165
+
166
+ A `trailing` eye `<Button>` flips the field's `type` between `password` and
167
+ `text` — native disc masking when hidden, plain text when revealed. Because it
168
+ stays a real `type="password"` when hidden, password managers and autofill keep
169
+ working, and the uncontrolled value is preserved across the toggle.
170
+
171
+ The mask glyph stays the browser default — restyling it means dropping
172
+ `type="password"`, which loses password-manager support and isn't masked in
173
+ Firefox. So Anta keeps the native field.
174
+
175
+ ## Multiline
176
+
177
+ ```tsx
178
+ {/* Grows with content, capped at 6 rows, then scrolls */}
179
+ <Input multiline maxRows={6} label="Bio (autogrows)" placeholder="Tell us…" />
180
+
181
+ {/* Constant height of 3 rows */}
182
+ <Input multiline rows={3} label="Notes (fixed 3 rows)" placeholder="…" />
183
+ ```
184
+
185
+ `multiline` renders a `<textarea>` under the same API. With **no `rows`** it
186
+ **autogrows** with its content via CSS `field-sizing: content` — capped by
187
+ `maxRows` if you set one (omit for unbounded growth), then it scrolls. Pass a
188
+ fixed **`rows`** count for a constant-height box instead.
189
+
190
+ Autogrow uses CSS `field-sizing` where supported (Chromium, Safari ≥ 26.2) and a
191
+ built-in JS resize everywhere else (Firefox, older Safari), so it grows in every
192
+ browser.
193
+
194
+ ## Controlled & uncontrolled
195
+
196
+ ```tsx
197
+ // Uncontrolled — the element owns the value
198
+ <Input label="Name" defaultValue="Ada" onChange={(e) => log(e.target.value)} />
199
+
200
+ // Controlled — you own the value
201
+ const [v, setV] = useState('')
202
+ <Input label="Name" value={v} onInput={(e) => setV(e.target.value)} />
203
+ ```
204
+
205
+ Like a native input: pass `defaultValue` for **uncontrolled**, or `value` +
206
+ `onInput` / `onChange` for **controlled**. The controlled value is reflected to
207
+ the shadow control only when it differs from what's there, so the caret never
208
+ jumps mid-edit.
209
+
210
+ Read the value off `e.target.value` in either handler — the event retargets to
211
+ the `<a-input>` host, whose `value` getter returns the control's value.
212
+
213
+ Note: like other custom-element form controls (and unlike React's built-in
214
+ `<input>`), a controlled value that you *transform* applies on the next render,
215
+ but one you *reject* — set state back to the same string — isn't auto-reverted,
216
+ since the framework skips the re-render. Constrain hard with `maxLength` /
217
+ `pattern` / `type`, or transform in the handler.
218
+
219
+ ## Events
220
+
221
+ | handler | fires | payload |
222
+ |---|---|---|
223
+ | `onInput(e)` | on **every keystroke** | read `e.target.value` |
224
+ | `onChange(e)` | on **commit** (blur / Enter) — native `change` semantics, *not* React's per-keystroke `onChange` | read `e.target.value` |
225
+ | `onValueChange(e, attrs)` | on `input` **and** `change` (and clear) | `{ value, name, empty, valid, validationMessage }` |
226
+ | `onClearClick(e)` | before the `clearable` × clears | ✅ `e.preventDefault()` keeps the value |
227
+
228
+ **Input has no `onStateChange`, on purpose.** That event belongs to components
229
+ with a *discrete* state — a small enum like `Checkbox`'s `checked · unchecked ·
230
+ indeterminate` or a menu's `open · closed` — where a transition can be vetoed as
231
+ a unit before it applies. A text field's value is **free-form**, not a discrete
232
+ state, so there's nothing to model as `state` and nothing to veto wholesale;
233
+ you control it the native way (`value` + `onInput`/`onChange`, reject by not
234
+ updating). Reach for `onValueChange` as the one unified "the value changed"
235
+ handler — it's the same callback `Checkbox`, `RadioGroup`, and `Select` expose,
236
+ so form code reads the same across all of them. (`Select`, likewise composed and
237
+ value-based, also has only `onValueChange`.)
238
+
239
+ ## Forms
240
+
241
+ ```tsx
242
+ <form onSubmit={handle}>
243
+ <Input name="email" label="Email" type="email" required clearable />
244
+ <Button tone="brand" type="submit" label="Sign up" />
245
+ <Button priority="tertiary" type="reset" label="Reset" />
246
+ </form>
247
+ ```
248
+
249
+ Because `<a-input>` is form-associated, it submits with the surrounding
250
+ `<form>` under its `name` — `new FormData(form)` includes it, no hidden mirror
251
+ needed. `required` participates in native validity, and `<Button type="reset">`
252
+ restores the field to its `defaultValue` via `formResetCallback`.
253
+
254
+ ## Types & validation
255
+
256
+ ```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} />
263
+ ```
264
+
265
+ `type` accepts `text` (default), `email`, `password`, `tel`, `url`, and
266
+ `number` — each gets the right mobile keyboard, autofill behaviour, and native
267
+ constraint validation. (`search` is omitted on purpose — see above.)
268
+ `number` also takes `min` / `max` / `step`.
269
+
270
+ For a **controlled** numeric field — one whose `value` you hold in state — prefer
271
+ `type="text"` with `inputMode="decimal"` (or `"numeric"`) over `type="number"`. A
272
+ number input runs value sanitization, so a mid-typing `"1."` or `"1.0"` reports an
273
+ empty `value` and can't be entered; storing the parsed `Number` and rendering it
274
+ back drops the decimal for the same reason. Keep the raw string in state and parse
275
+ to a number where you use it — `inputMode` still brings up the numeric keypad. An
276
+ **uncontrolled** `type="number"` (no bound `value`) has neither problem.
277
+
278
+ Every type carries its native constraints (`required`, the `type` itself,
279
+ `pattern`, `min`/`max`, `min`/`maxLength`). The field reports them to the
280
+ surrounding `<form>` automatically (via `ElementInternals`), so submission is
281
+ blocked while invalid — no extra wiring.
282
+
283
+ ### Native validation
284
+
285
+ ```tsx
286
+ // Surface the browser's own validity messages as Anta's critical-status UI.
287
+ const [errors, setErrors] = useState({})
288
+
289
+ function onSubmit(e) {
290
+ e.preventDefault()
291
+ const next = {}
292
+ e.currentTarget.querySelectorAll('a-input').forEach((el) => {
293
+ if (!el.checkValidity()) next[el.name] = el.validationMessage
294
+ })
295
+ setErrors(next)
296
+ }
297
+
298
+ <form noValidate onSubmit={onSubmit}>
299
+ <Input name="email" type="email" required hint={errors.email}
300
+ status={errors.email ? 'critical' : undefined}
301
+ onInput={() => setErrors(p => ({ ...p, email: undefined }))} />
302
+ <Input name="age" type="number" min="18" max="120" hint={errors.age}
303
+ status={errors.age ? 'critical' : undefined}
304
+ onInput={() => setErrors(p => ({ ...p, age: undefined }))} />
305
+ <Button type="submit" label="Submit" />
306
+ </form>
307
+ ```
308
+
309
+ The element proxies native validity: `<a-input>` exposes `checkValidity()` and
310
+ `validationMessage` (the browser's localized message).
311
+
312
+ Pass `validationMessage` to **`hint`** and set **`status="critical"`** — the
313
+ field goes red, shows the warning glyph, and reports invalid to the form. Mark
314
+ the form `noValidate` so the browser's default bubbles step aside for Anta's
315
+ styling. Try submitting empty, or with a bad email / out-of-range age.
316
+
317
+ **Enter doesn't submit the form.** The real `<input>` lives in shadow DOM, so
318
+ it isn't a light-DOM member of the `<form>`, and pressing Enter won't trigger
319
+ native implicit submission — a deliberate choice, since auto-submitting a
320
+ half-filled multi-field form is usually more annoyance than help. To submit on
321
+ Enter, handle it yourself (`onKeyDown` → `form.requestSubmit()`), or reach for
322
+ Anta's upcoming `<Form>` component. Everything else — value submission under
323
+ `name`, `checkValidity()`, `formReset` — works natively.
324
+
325
+ ## Layout
326
+
327
+ The label, field, and hint **aren't hardcoded to "stacked."** The element lays
328
+ them out as a single-column **grid** (hence the default label-above / hint-below
329
+ rhythm) and exposes each region as a shadow part (`label`, `field`, `hint`).
330
+ Rearrange them from your own CSS: re-template the grid on `a-input`, then place
331
+ the parts. No layout prop to learn — your rule wins because the element's
332
+ defaults live in `@layer anta`.
333
+
334
+ And because each `<a-input>` is itself a normal grid/flex item, **wrapping** is
335
+ just sizing: give the inputs a `min-width` (and maybe `max-width`) inside any
336
+ wrapping container and they reflow by available width — the field's internal
337
+ grid is independent of how the fields themselves flow.
338
+
339
+ The `.label-side` / `.aligned-form` / `.wrap-fields` classes below are only to
340
+ scope each demo — swap your own selector.
341
+
342
+ ### Label on the left
343
+
344
+ Re-template the host to two columns and drop each part into one. The hint stays
345
+ under the field by landing in the same column.
346
+
347
+ ```tsx
348
+ <Input className="label-side" label="Display name" hint="Shown on your profile" />
349
+
350
+ {/* .label-side is just for the demo — use your own selector */}
351
+ <style>{`
352
+ a-input.label-side {
353
+ grid-template-columns: max-content minmax(0, 1fr);
354
+ column-gap: 12px;
355
+ align-items: center;
356
+ }
357
+ a-input.label-side::part(label) { grid-column: 1; }
358
+ a-input.label-side::part(field) { grid-column: 2; }
359
+ a-input.label-side::part(hint) { grid-column: 2; }
360
+ `}</style>
361
+ ```
362
+
363
+ ### Label column
364
+
365
+ Put the fields in a grid and let each one adopt the form's tracks with
366
+ `grid-template-columns: subgrid` — which reaches *through* the shadow boundary,
367
+ so every label shares one column sized to the longest label, and every field
368
+ left-edge lines up. Each hint still tucks under its own field.
369
+
370
+ ```tsx
371
+ <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" />
375
+ </div>
376
+
377
+ <style>{`
378
+ /* one shared label column; subgrid lets each field adopt the form's tracks */
379
+ .aligned-form { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 16px 12px; }
380
+ .aligned-form > a-input { grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; }
381
+ .aligned-form > a-input::part(label) { grid-column: 1; }
382
+ .aligned-form > a-input::part(field),
383
+ .aligned-form > a-input::part(hint) { grid-column: 2; }
384
+ `}</style>
385
+ ```
386
+
387
+ ### Hint on the right
388
+
389
+ Put the hint beside the field instead of under it — a short note, a counter, a
390
+ unit. Two columns (field, then hint); the label spans the top, and the hint
391
+ centers against the field.
392
+
393
+ ```tsx
394
+ <Input className="hint-right" label="Username" hint="3–20 characters" placeholder="ada" />
395
+
396
+ <style>{`
397
+ a-input.hint-right {
398
+ grid-template-columns: minmax(0, 1fr) max-content;
399
+ column-gap: 10px;
400
+ }
401
+ a-input.hint-right::part(label) { grid-column: 1 / -1; }
402
+ a-input.hint-right::part(field) { grid-column: 1; grid-row: 2; }
403
+ a-input.hint-right::part(hint) { grid-column: 2; grid-row: 2; align-self: center; padding-top: 0; }
404
+ `}</style>
405
+ ```
406
+
407
+ ### Wrap by field width
408
+
409
+ Leave the fields stacked and let an outer grid flow them into as many columns as
410
+ fit — `minmax(200px, 1fr)` makes each field claim at least 200px, so the row
411
+ wraps to fewer columns as it narrows. Resize the preview to see it reflow.
412
+
413
+ ```tsx
414
+ <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" />
419
+ </div>
420
+
421
+ <style>{`
422
+ /* fields stay stacked; the grid wraps them by available width */
423
+ .wrap-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; }
424
+ `}</style>
425
+ ```
426
+
427
+ ### Props
428
+
429
+ | Prop | Type | Default | Description |
430
+ |------|------|---------|-------------|
431
+ | `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`
432
+ (`email` / `tel` / `url`) — set it for the cases `type` can't express, e.g.
433
+ `username`, `current-password`, `new-password`, `one-time-code`, or `off`. |
434
+ | `children?` | ReactNode | — | Extra content rendered directly under the field, above the hint/error (it
435
+ pushes the message down). A no-box child like an Anta `<Tooltip>` takes no
436
+ space and just anchors to the field — consistent with how tooltips attach
437
+ to any other element. Use the named `leading` / `trailing` props for
438
+ in-field content. |
439
+ | `clearable?` | boolean | — | Show a clear button as the first trailing item once the field has a
440
+ value. |
441
+ | `defaultValue?` | string | — | Initial value for the uncontrolled case. |
442
+ | `dimActions?` | boolean | — | Dim the `leading` / `trailing` adornments at rest; they brighten to full
443
+ when the field is hovered or focused (a quiet-until-engaged affordance for
444
+ trailing actions). |
445
+ | `disabled?` | boolean | — | Disable the field. |
446
+ | `hint?` | ReactNode | — | Message below the field. Neutral helper text by default; `status` recolors
447
+ it and prefixes the matching glyph. |
448
+ | `inputMode?` | 'none' \| 'text' \| 'decimal' \| 'numeric' \| 'tel' \| 'search' \| 'email' \| 'url' | — | Virtual-keyboard hint. Overrides the value derived from `type`. |
449
+ | `label?` | ReactNode | — | Field label, shown above the control. A string is rendered with the
450
+ label type scale; pass a node for full control. Associated with the
451
+ control as its accessible name (the element mirrors the label text to
452
+ `aria-label`, since `<label for>` can't cross the shadow boundary). |
453
+ | `leading?` | ReactNode | — | Content pinned to the start of the field (e.g. an icon). |
454
+ | `max?` | number \| string | — | |
455
+ | `maxLength?` | number | — | Max input length. |
456
+ | `maxRows?` | number | — | Cap the autogrow height (in rows) of a `multiline` field with no `rows`.
457
+ Omit for unbounded growth. |
458
+ | `min?` | number \| string | — | Min / max / step — for `type="number"`. |
459
+ | `minLength?` | number | — | Min input length. |
460
+ | `multiline?` | boolean | — | Render a `<textarea>` instead of an `<input>`. Without `rows` it grows
461
+ with its content from one line (capped by `maxRows` if set). Autogrow uses
462
+ CSS `field-sizing` where supported (Chrome/Edge, Safari ≥ 26.2) and falls
463
+ back to a built-in JS resize elsewhere (Firefox, older Safari), so it grows
464
+ in every browser. |
465
+ | `name?` | string | — | Form field name — submitted with the form via ElementInternals. |
466
+ | `onBlur?` | (e) => void | — | Fires when the field loses focus. |
467
+ | `onChange?` | (e) => void | — | Fires on **commit** (blur / Enter) — the platform `change` semantics, **not**
468
+ React's per-keystroke `onChange`. This is a web component, so `onChange` keeps
469
+ the native meaning; reach for `onInput` (every keystroke) or `onValueChange`
470
+ (both) for live updates. Read `e.target.value`. |
471
+ | `onClearClick?` | (e) => void | — | Fires when the built-in clear button (`clearable`) is clicked, *before*
472
+ the field is cleared. Call `e.preventDefault()` to keep the current value
473
+ — the clear is cancelled and `onClearInput` won't fire. Backed by the
474
+ element's cancelable, bubbling `clearclick` event. |
475
+ | `onClearInput?` | (e) => void | — | Fires after the built-in clear button (`clearable`) has cleared the field
476
+ — so `onInput` / `onChange` fire too — making this useful for reacting
477
+ specifically to a clear. Doesn't fire if `onClearClick` cancelled the
478
+ clear. Backed by the element's bubbling `clearinput` event. |
479
+ | `onFocus?` | (e) => void | — | Fires when the field gains focus. |
480
+ | `onInput?` | (e) => void | — | Fires on every keystroke. Read `e.target.value`. |
481
+ | `onValueChange?` | (event, attrs) => void | — | Unified value-change handler — the easy path for state. Fires on `input`
482
+ *and* `change` (and on clear), with the native `event` plus a convenience
483
+ `attrs` snapshot (`value`, `name`, `empty`, `valid`, `validationMessage`) so
484
+ you can do `setForm(s => ({ ...s, [attrs.name]: attrs.value }))` without
485
+ digging into the event. Use `event.type` to tell a live edit (`input`) from
486
+ a commit (`change`); read `id` / `type` / `className` off `event.target`. |
487
+ | `pattern?` | string | — | Validation pattern (single-line). |
488
+ | `placeholder?` | string | — | Placeholder shown when empty. |
489
+ | `readOnly?` | boolean | — | Make the field read-only. |
490
+ | `required?` | boolean | — | Mark the field required (drives native validity). |
491
+ | `role?` | string | — | ARIA `role` for the field — e.g. `combobox` when the input drives a
492
+ suggestion `listbox` (see `InputAutocomplete`). Left unset by default. |
493
+ | `round?` | boolean \| number \| string | — | Fully-round the field (`border-radius: 999px`). Pass a `number` (px) or a CSS
494
+ length string for a custom radius. The `clearable` × button always rounds to
495
+ a circle to match (it isn't sized by a custom field value). |
496
+ | `rows?` | number | — | Fixed visible row count — a constant-height `<textarea>` (implies
497
+ `multiline`). |
498
+ | `size?` | 'small' \| 'medium' \| 'large' | medium | Size variant. small=24px, medium=28px, large=32px tall; the type scale and
499
+ icon track the size (small 13/16 + 14px icon · medium 15/20 + 16px ·
500
+ large 17/22 + 18px). |
501
+ | `spellCheck?` | boolean | — | Toggle native spell-checking. |
502
+ | `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | — | Validation / feedback tone — colors the border + `hint` and prefixes a
503
+ glyph. Only `critical` marks the field invalid (`aria-invalid`, blocks form
504
+ submission, `:state(invalid)`); `success` / `warning` / `info` / `brand`
505
+ are advisory and stay valid. Omit (or `neutral`) for a plain field. |
506
+ | `statusIcon?` | (string & {}) \| false \| IconShape | — | Glyph shown before the `hint` when `status` is set. Each status has a
507
+ default (critical → `warning-diamond`, warning → `warning-triangle`,
508
+ success → `circle-check`, info → `info`, brand → `circle-small-solid`); pass a
509
+ shape to override, or `false` to drop it. `neutral` has no default glyph. |
510
+ | `step?` | number \| string | — | |
511
+ | `tone?` | string | — | Custom accent colour — any literal CSS colour tints the resting + hover
512
+ border (focus ring stays the global `--focus-ring`). For consistency with the
513
+ other controls' custom-tone knob; a `status` still overrides for validation. |
514
+ | `trailing?` | ReactNode | — | Content pinned to the end of the field (e.g. icons, buttons), after the
515
+ clear button when `clearable`. |
516
+ | `truncate?` | boolean | — | Ellipsize an overflowing single-line value. Read-only inputs already do
517
+ this; use `truncate` when an editable field should keep the same treatment. |
518
+ | `type?` | 'text' \| 'search' \| 'email' \| 'password' \| 'tel' \| 'url' \| 'number' | text | Single-line input type. Ignored when `multiline`. `search` is a
519
+ **wrapper-only** shorthand: it defaults a leading search icon and a clear
520
+ button (both overridable — pass your own `leading`, or `clearable={false}`)
521
+ and sets `inputmode="search"`, but the DOM input stays `type="text"`. The
522
+ native `search` type never reaches the element, so the browser's own
523
+ clear/search affordances never appear — Anta owns that chrome. |
524
+ | `value?` | string | — | Controlled value. Pair with `onChange` / `onInput`. |
525
+
526
+ Use this when you are not using the React or Preact wrapper and a native HTML control does not fit: construct the equivalent Anta web component from the elements below.
527
+
528
+ Slots hold the label, hint, and controls around the native field.
529
+
530
+ ```html
531
+ <a-input name="email" placeholder="you@example.com">
532
+ <span slot="label">Email</span>
533
+ <span slot="hint">We only use it for account notices.</span>
534
+ </a-input>
535
+ ```
536
+
537
+ ### Native HTML inputs
538
+
539
+ For a standard HTML form, add `data-anta` to a native input. Text, search,
540
+ email, telephone, URL, password, and number inputs keep their native editing,
541
+ keyboard, validation, and form behavior while using Anta's field chrome. Search
542
+ keeps its browser-supplied affordances; file keeps its native chooser and selected
543
+ file name, with an Anta-styled chooser button. `submit` and `reset` inputs use the
544
+ matching Anta button chrome; set `priority="primary"` on a submit control when it
545
+ is the form's main action.
546
+
547
+ Use `data-anta-size="small"` or `"large"` for field sizing. Native inputs own
548
+ their `size` attribute, so Anta does not reuse it. `round` and a custom-color
549
+ `tone` work the same way as Input.
550
+
551
+ ```html
552
+ <input data-anta data-anta-size="small" type="text" name="name" placeholder="Small text">
553
+ <input data-anta type="search" name="search" placeholder="Search">
554
+ <input data-anta type="email" name="email" placeholder="Email">
555
+ <input data-anta type="tel" name="phone" placeholder="Telephone">
556
+ <input data-anta round type="url" name="site" placeholder="Rounded URL">
557
+ <input data-anta type="password" name="password" value="password">
558
+ <input data-anta tone="rebeccapurple" type="number" name="seats" value="3" min="1">
559
+ <input data-anta type="file" name="attachment" accept=".pdf,.docx">
560
+
561
+ <input data-anta type="submit" value="Submit" priority="primary">
562
+ <input data-anta type="reset" value="Reset">
563
+ ```
564
+
565
+ Reach for the props first: **`status`** sets a validation tone (border + message),
566
+ **`tone`** a custom accent colour for the border (any CSS colour), **`size`** the
567
+ dimensions. The focus ring is the global [`--focus-ring`](../colors.md#focus-ring).
568
+
569
+ ```tsx
570
+ <Input tone="#e0457b" label="Custom accent" />
571
+ ```
572
+
573
+ For everything else, `<a-input>` is shadow-DOM — style its **parts** with plain
574
+ CSS: `::part(field | input | label | leading | trailing | clear | hint)`, plus the
575
+ `:state(filled)` / `:state(invalid)` state hooks and `:focus-within::part(field)`
576
+ for the focused state. The clear button is a real `<a-button>` in the `clear` slot,
577
+ so style it directly. `light-dark()` works too (Anta sets `color-scheme` from its
578
+ theme toggle).
579
+
580
+ ```css
581
+ a-input::part(field) { border-radius: 999px; }
582
+ a-input::part(input) { font-variant-numeric: tabular-nums; }
583
+ a-input::part(label) { text-transform: uppercase; }
584
+ ```
585
+
586
+ The border is drawn as an **inset `box-shadow`**, not a real `border`, so its width
587
+ (`0.5px` → `1px` when a `status` is set) and placement never change the box size —
588
+ the field height stays locked to the matching Button. Drop the leading `inset` to
589
+ draw the border as an outset ring instead. You can also re-point an `--input-*`
590
+ colour token on one instance (`style={{ '--input-border': 'var(--border-1)' }}`)
591
+ or a wrapping selector; the resolver lives in `@layer anta`, so any un-layered rule
592
+ of yours wins.
593
+
594
+ The `.fancy` class is just for the demo — a borderless pill that fills dark-grey
595
+ and darkens on focus in light mode, and the inverse in dark:
596
+
597
+ ```css
598
+ /* a borderless pill — the resting border is a box-shadow, so clear it. Dark-grey
599
+ fill that darkens on focus, white text; the inverse in dark mode via light-dark()
600
+ (Anta drives color-scheme from its theme toggle). */
601
+ a-input.fancy::part(field) {
602
+ border-radius: 999px;
603
+ box-shadow: none; /* remove the box-shadow border */
604
+ background: light-dark(#3a3a3a, #ececec);
605
+ padding-block: 10px;
606
+ padding-inline: 20px; /* roomier */
607
+ }
608
+ a-input.fancy::part(input) { color: light-dark(#ffffff, #1a1a1a); }
609
+ a-input.fancy:focus-within::part(field) { background: light-dark(#161616, #ffffff); }
610
+ ```