@spark-web/text-input
Version:
--- title: Text Input storybookPath: forms-textinput--default isExperimentalPackage: false ---
123 lines (100 loc) • 6.29 kB
Markdown
# @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>
```
### With react-hook-form (via ui-components)
Prefer `TextInputField` from `@brighte/ui-components` when used with
`react-hook-form`. Pass `placeholder` directly, and use `FieldProps` to pass
`description` (renders as muted hint text below the label):
```tsx
<TextInputField
control={control}
name="subject"
label="Subject"
placeholder="Type here..."
FieldProps={{ description: 'Enter a brief summary of the issue' }}
/>
```
## 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`