# Input

**When to use:** Text input with floating label, validation state, and type variants. The primary form input for text, email, password, phone, search, and multiline content.

**Keywords:** text field, form field, text box

**Import:** `import { Input, InputType } from '@ahrowe/ui'`

```tsx
import { Input, InputType, FormValidator, Validators } from '@ahrowe/ui';

// Basic
<Input label="First name" value={name} onChange={(value) => setName(value)} />

// Email with validation
const emailValidator = new FormValidator('', [Validators.required(), Validators.email()]);
<Input label="Email" type={InputType.Email} formValidator={emailValidator} />

// Password
<Input label="Password" type={InputType.Password} value={pw} onChange={setPw} />

// Search — clear button appears automatically once there's a value, and clears itself;
// onClear is optional, only needed to react to the clear beyond the value itself
<Input
  type={InputType.Search}
  label="Search"
  value={query}
  onChange={setQuery}
/>

// Multiline (textarea)
<Input label="Notes" multiline rows={4} value={notes} onChange={setNotes} />

// Phone — sets type="tel" and the numeric dial-pad keyboard, no formatting of its own
<Input label="Phone" type={InputType.Phone} formValidator={phoneValidator} />

// Formatted while typing, via parse — an IBAN grouped in blocks of four
<Input label="IBAN" parse={formatIban} value={iban} onChange={setIban} />

// With suffix
<Input label="Amount" suffix="€" value={amount} onChange={setAmount} />

// Clear button, shown whenever there is a value to clear
<Input label="City" clearable value={city} onChange={setCity} />

// Row layout
<Input label="Name" isRow value={name} onChange={setName} />

// Custom icon on the left instead of the default right — the field's padding, the material
// label (resting and floated), and the native placeholder all shift out of its way automatically
<Input
  label="Search"
  icon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
  iconPosition={InputIconPosition.Left}
  value={query}
  onChange={setQuery}
/>

// A decorative type icon (Email/Phone/Edit) on the left, no custom icon needed
<Input label="Email" type={InputType.Email} iconPosition={InputIconPosition.Left} />

// A custom icon replaces Email's built-in envelope outright — there's nothing functional to lose
<Input label="Email" type={InputType.Email} icon={<FontAwesomeIcon icon={faAt} />} />
```

**InputType enum:** `Edit` | `Phone` | `Email` | `Iban` | `Date` | `HasNoIcon` | `Search` | `Password`

**InputIconPosition enum:** `Left` | `Right` — which side the input's icon renders on, whichever icon is currently showing (built-in type icon or custom `icon`). Defaults to `Left` for `Email`/`Phone`/`Search` once `useMatLabelStyle` is off (their historical placement), `Right` otherwise; pass it explicitly to move any type's icon to either side regardless of `useMatLabelStyle`.

**Custom icon vs. built-in type icon:** passing `icon` alongside a decorative type (`Edit`, `Phone`, `Email`) replaces that type's built-in icon outright, since there's nothing functional attached to it to lose. `Search` and `Password` ignore `icon` — their icon is functional (clear button, show/hide toggle), not decorative, so it always renders regardless of whether `icon` is set.

**Search clear icon:** `InputType.Search` shows a magnifying glass that animates into a clear (×) button once the field has a value it can change, rather than the browser's own native search-cancel decoration, which is suppressed so it doesn't clash with the theme.

**Icon accessibility:** any icon with an active click handler (`onIconClick`, the search clear button, the password show/hide toggle, the `clearable` button) is keyboard-focusable (`Tab`) and activates on Enter or Space, with a visible focus outline. The ones with a fixed meaning (search clear, password toggle, clear) also carry an `aria-label`. A purely decorative icon (no click handler) stays out of the tab order. Either clear button puts the cursor back in the field, so a keyboard user does not lose their place.

**Mobile keyboard layout:** `Email`, `Phone`, and `Search` each set the matching `inputMode` (`email` / `tel` / `search`), so mobile browsers show the right on-screen keyboard — an `@`-and-`.com` layout for email, a numeric dial pad for phone, a "Go"/search action key for search. `Email`'s native `type` is deliberately left as `text` rather than `email`, to avoid the browser's own "please match the requested format" validation UI fighting the `formValidator`-driven error tooltip this component already shows — `inputMode="email"` alone is enough to get the correct keyboard. Pass `inputMode` directly to override the computed default for any other case.

**Formatting while typing (`parse`):** `parse` runs on every change, before the value reaches `onChange` and `formValidator`, and its return value is what gets stored and rendered. That makes it the hook for keeping a field in a canonical shape as the user types, rather than fixing it up on blur or submit:

```tsx
// Uppercase, drop anything that isn't a letter or digit, group in blocks of four,
// cap at the 34-character IBAN maximum
const formatIban = (value: string) =>
  value
    .replace(/[^a-zA-Z0-9]/g, '')
    .toUpperCase()
    .slice(0, 34)
    .replace(/(.{4})/g, '$1 ')
    .trim();

<Input label="IBAN" type={InputType.Iban} parse={formatIban} value={iban} onChange={setIban} />
```

The stored value is the formatted one, spaces included, so strip them before a length check or checksum (`value.replace(/\s/g, '')`). Two limits worth knowing: inserting characters in the middle of an already-formatted value can move the caret to the end, since the whole value is rewritten (append-only typing is unaffected), and `parse` can't reject a keystroke, only rewrite the result. For a fixed-width field where per-position rules and caret handling matter more (a date, a card number), drive a mask library through `customInput` instead.

**Beside a button:** the field keeps no room for its error, which shows as a tooltip, and the floated
label sits in the border's notch, so a `Button` next to it lines up with `align-items: center`.

**Key props:**

| Prop | Type | Description |
|------|------|-------------|
| `label` | `string` | Floating label |
| `value` | `string \| number` | Controlled value |
| `onChange` | `(value: string, event?) => void` | Change handler |
| `type` | `InputType` | Input variant |
| `formValidator` | `FormValidator` | Connects to validation |
| `isRequired` | `boolean` | |
| `errorMessage` | `string` | Manual error (overrides formValidator). A non-empty message puts the field in error by itself, so pass it only while there is one |
| `isValid` | `boolean` | Manual valid state |
| `multiline` | `boolean` | Renders as textarea, resizable from the field's corner; `styles={{ container: { resize: 'none' } }}` turns that off |
| `rows` | `number` | Textarea row count |
| `autoResize` | `boolean` | Textarea grows with content |
| `suffix` | `string \| ReactNode` | Right-side suffix |
| `icon` | `ReactElement` | Custom icon, on the side set by `iconPosition`; clickable via `onIconClick`. On the right it follows the `suffix` and the clear button, each in its own space. Replaces the built-in icon for `Edit`/`Phone`/`Email`; ignored for `Search`/`Password` (see below) |
| `iconPosition` | `InputIconPosition` | Side the input's icon renders on — built-in or custom, whichever is showing (see enum above for the default). On the left, the material label (resting and floated) and the fieldset notch shift right to clear it too |
| `onIconClick` | `() => void` | Click handler for the custom `icon` |
| `upperRightLabel` | `string \| ReactNode` | Small label above right |
| `clearable` | `boolean` | Show a clear button whenever the field has a value it can change (not while `disabled` or `readOnly`). While the field is empty it takes no space, so a `suffix` sits at the edge; the first character grows it in. `InputType.Search` has its own, see below |
| `onClear` | `() => void` | Called after a clear button empties the field. Optional: clearing always empties the field itself |
| `isRow` | `boolean` | Horizontal layout |
| `labelInBorder` | `boolean` | Label inside fieldset border |
| `alwaysFloatLabel` | `boolean` | Keep the material label floated in the border notch even when empty and unfocused, so an empty field reads as blank instead of showing the label as if it were a value. Only applies with `useMatLabelStyle` (default) |
| `readOnly` | `boolean` | |
| `autoFocus` | `boolean` | |
| `placeholder` | `string` | |
| `parse` | `(value: string) => string` | Transform the value on every change, before `onChange`/`formValidator` — also what gets rendered (see above) |
| `isUppercase` | `boolean` | Uppercases the field's rendering (CSS only — the stored value is unchanged; use `parse` to uppercase the value itself) |
| `customInput` | `ReactElement` | Element rendered in place of the internal `<input>`, cloned with all of Input's props. The escape hatch for a mask library; note `autoFocus` and `autoResize` stop working, since Input cannot attach its ref |
| `ref` | `Ref<HTMLInputElement \| HTMLTextAreaElement>` | Ref to the inner field, not the wrapper. Not forwarded to a `customInput` |

**Slots:** `label` `container` `input` `suffix` `upperRightLabel` `fieldset`

**Labels:** `clear` `clearSearch` `showPassword` `hidePassword` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `InputLabels`.

## Browser autofill

`autoComplete` is the plain HTML attribute and is passed straight through — use `'one-time-code'`, `'new-password'`, `'street-address'` and the rest as you would on a bare `<input>`. Input does **not** set it: autofill and password managers are usability and accessibility features, and a plain field has no suggestion list of its own for them to collide with.

The combobox components — `InputDropdown`, `SearchInput`, `MultiSelect`, `DatePicker`, `TimeInput` — always set `autocomplete="off"` and do not take the prop, because the browser's dropdown would otherwise open on top of the list they render themselves.

**The floating label knows about autofill.** Chrome fills a field on page load by writing the DOM value directly, without firing anything React can observe — so the component's value stayed empty and the label sat on top of the autofilled text. Input detects it through a no-op animation bound to `:-webkit-autofill`, whose `animationstart` is the only notification the browser gives, and floats the label from that as well as from its value.

**Autofilled fields stay on the theme.** Chrome paints `:-webkit-autofill` with its own background and text colour, overriding `background` and `color` outright, which turned a filled field pale blue with near-black text on a dark theme. Input clips that background to the text instead of covering it: the page cannot win on colour, but it can say where a background paints, and `-webkit-text-fill-color` then beats the text colour. The field itself is transparent, so what shows through is whatever is genuinely behind it — inside a Modal, a Card, or a surface of your own — and nothing has to be told what that is.

A custom `--input-background` is covered by the same mechanism, because the container paints it and the transparent field reveals it. Setting a background on the field itself with the `background` shorthand — `styles={{ input: { background: '…' } }}` — resets `background-clip` along with it, and an autofilled field there falls back to Chrome's own colours; `background-color` does not. Either way it covers only the text: a background meant for the whole field, under the icons and the suffix too, goes on `styles.container`.

## Migration from `showCancel`

Before 0.47 the clear button came from two props, and its callback had a name that missed key
presses. TypeScript points at every use:

| Before | After |
|--------|-------|
| `showCancel` | `clearable` |
| `alwaysShowCancel` | `clearable` |
| `onClearClicked={…}` | `onClear={…}` |

What changes on screen: `showCancel` showed the button only on hover or focus, and
`alwaysShowCancel` showed it even on an empty field, where it did nothing. `clearable` shows it
whenever the field has a value, and never while it is `disabled` or `readOnly`, where both old
props would still clear it.
