# @spark-web/text-input — AI Context

## What this is

A styled single-line text input. Must always be wrapped in a `Field` from
`@spark-web/field` — `Field` provides the label, description, message, and all
accessibility wiring. Supports leading/trailing adornments (icons, buttons,
secondary selects) via `InputAdornment`.

## What this is NOT

- Not a textarea — for multi-line input, use a different component
- Not usable without `Field` — always wrap in `<Field label="…">`
- Not responsible for its own label or validation message

## Exports

- `TextInput` — the input element
- `InputAdornment` — wrapper for icons/elements placed inside the input

## TextInput props

| Prop               | Type                                                                                  | Notes                                                                |
| ------------------ | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `type`             | `'text' \| 'password' \| 'email' \| 'search' \| 'number' \| 'tel' \| 'url'`           | Defaults to `'text'`                                                 |
| `placeholder`      | `string`                                                                              | —                                                                    |
| `value`            | `string`                                                                              | Controlled value                                                     |
| `onChange`         | `ChangeEventHandler<HTMLInputElement>`                                                | —                                                                    |
| `name`             | `string`                                                                              | —                                                                    |
| `autoComplete`     | `string`                                                                              | —                                                                    |
| `onBlur`           | `FocusEventHandler<HTMLInputElement>`                                                 | —                                                                    |
| `onFocus`          | `FocusEventHandler<HTMLInputElement>`                                                 | —                                                                    |
| `onInput`          | `FormEventHandler<HTMLInputElement>`                                                  | —                                                                    |
| `inputMode`        | `'none' \| 'text' \| 'tel' \| 'url' \| 'email' \| 'numeric' \| 'decimal' \| 'search'` | Virtual keyboard hint for mobile                                     |
| `required`         | `boolean`                                                                             | —                                                                    |
| `children`         | `InputAdornment` elements                                                             | Adornments; must be `InputAdornment` — anything else is filtered out |
| `overflowStrategy` | `'truncate' \| 'nowrap'`                                                              | Defaults to `'truncate'`                                             |
| `data`             | `DataAttributeMap`                                                                    | Test/analytics attributes                                            |

`disabled` and `readOnly` are injected via `Field` context — do not pass them
directly to `TextInput`.

## InputAdornment props

| Prop         | Type               | Notes                                                                        |
| ------------ | ------------------ | ---------------------------------------------------------------------------- |
| `placement`  | `'start' \| 'end'` | required — where to place the adornment                                      |
| `children`   | `ReactElement`     | The adornment element (icon, button, etc.)                                   |
| `raw`        | `boolean`          | Opt out of automatic alignment/spacing wrapper                               |
| `fieldLabel` | `string`           | Override the parent field label for an inner input (e.g. a select adornment) |

Only one adornment per placement is allowed.

## Common patterns

### Search field with icon and clear button

```tsx
<Field label="Search users" labelVisibility="hidden">
  <TextInput
    placeholder="Search users"
    value={search}
    onChange={e => setSearch(e.target.value)}
  >
    <InputAdornment placement="start">
      <SearchIcon size="xsmall" tone="placeholder" />
    </InputAdornment>
    {search && (
      <InputAdornment placement="end">
        <Button
          label="Clear search"
          prominence="none"
          tone="neutral"
          onClick={() => setSearch('')}
        >
          <XIcon />
        </Button>
      </InputAdornment>
    )}
  </TextInput>
</Field>
```

### Plain search (icon only, no clear)

```tsx
<Field label="Search" labelVisibility="hidden">
  <TextInput
    placeholder="Search…"
    value={q}
    onChange={e => setQ(e.target.value)}
  >
    <InputAdornment placement="start">
      <SearchIcon size="xsmall" tone="placeholder" />
    </InputAdornment>
  </TextInput>
</Field>
```

### Controlled usage with form state

`TextInput` is a controlled input — wire `value` and `onChange` to your form
state and keep the `Field` wrapper for the label, description, and validation
message. Pass `placeholder` directly to `TextInput`, and use the `description`
prop on `Field` for muted hint text below the label. Some consumer repos provide
their own `Field`-wrapped form bindings — check your repo's surface overlay
before hand-rolling one.

```tsx
<Field
  label="Subject"
  description="Enter a brief summary of the issue"
  tone={error ? 'critical' : 'neutral'}
  message={error?.message}
>
  <TextInput
    placeholder="Type here..."
    value={subject}
    onChange={e => setSubject(e.target.value)}
  />
</Field>
```

## Do NOTs

- NEVER use `TextInput` without a parent `Field`
- NEVER pass non-`InputAdornment` elements as children — they will not render
- NEVER put more than one `InputAdornment` at the same placement
- NEVER manage `disabled` state directly on `TextInput` — set it on `Field`
- NEVER use a raw `<input>` element — always use `TextInput`
