<!-- Generated by scripts/build-agent-kit.ts for @aistrike-dev/ui@5.0.1. Do not edit. -->

# TextField vs Select vs Autocomplete vs Search

<!-- use-when: A box the user types into or picks from: which input, and whether it is a field or a filter. -->

All four render as a box the user types into or clicks, so mockups rarely distinguish them. The question
that decides it is: **is the set of acceptable values known and finite, and how many are there?**

| Control | Acceptable values | Typing does | Best at |
| --- | --- | --- | --- |
| `TextField` | Anything | Enters the value | Free-form text, numbers, emails |
| `Select` | A known, fixed list | Nothing — you click to open | 3 to ~15 options |
| `Autocomplete` | A known list, possibly large or async | Filters the list | ~15+ options, tagging, async lookup |
| `Search` | Anything — it is a query, not a value | Filters other content | Narrowing a list or table |

The distinction that matters most: **`Search` does not collect a value, it filters a view.** Nothing the
user types into a `Search` is submitted with a form. If the typed text ends up as a field on a record,
you wanted `TextField` or `Autocomplete`.

## Decision flow

```
Does the input narrow a list or table that is already on screen,
rather than collecting a value?
        │
        ├── Yes ─────────────────────────────────► Search
        └── No — it collects a value
                │
                ├── Is the set of acceptable values known and finite?
                │        │
                │        ├── No ─────────────────► TextField
                │        └── Yes
                │              │
                │              ├── Fewer than 3 ──► Radio (or Switch for a binary)
                │              ├── 3 to ~15 ──────► Select
                │              └── ~15+, async, or
                │                  multi-select with
                │                  many values ───► Autocomplete
```

## TextField

The default for **any value the system cannot enumerate**: names, descriptions, IP addresses, ports,
email addresses, numbers.

```tsx
import { TextField } from '@aistrike-dev/ui';

<TextField
  label="Asset name"
  value={name}
  onChange={(e) => setName(e.target.value)}
  helperText="Shown throughout the console."
  required
/>
```

**Practical limits:** always pass `label` — a `placeholder` disappears the moment the user types, so
placeholder-only labelling leaves them with no idea what the field was. Use `multiline` for prose, and
`error` plus `helperText` together so a validation failure is both visible and announced. A `TextField`
with a magnifier icon in an `InputAdornment` is not a search field; that is `Search`.

## Select

A dropdown over a **known, fixed, modest list**. The user clicks to open; typing does nothing useful.

```tsx
import { FormField, Select, MenuItem } from '@aistrike-dev/ui';

<FormField label="Environment" htmlFor="env">
  <Select id="env" value={env} onChange={(e) => setEnv(e.target.value)} fullWidth size="small">
    <MenuItem value="prod">Production</MenuItem>
    <MenuItem value="staging">Staging</MenuItem>
    <MenuItem value="dev">Development</MenuItem>
  </Select>
</FormField>
```

**Practical limits:** below three options a `Select` hides choices behind a click for no benefit — use
`Radio`, or `Switch` for a binary. Above roughly fifteen, scrolling an unfilterable list is painful —
use `Autocomplete`. `multiple` works, but once users routinely pick several from a long list,
`Autocomplete multiple` reads better. Label it with `FormField` and matching `htmlFor` / `id`, not a
bare `InputLabel`: `FormField` is the design system's composed `FormControl`, and it is what gives the
field its label, helper text and error state. A `Select` on its own has no accessible name.

## Autocomplete

A combobox where **typing filters a known list**. This is the control for long option sets, for
server-side lookups, and for tagging.

```tsx
import { Autocomplete, TextField } from '@aistrike-dev/ui';

<Autocomplete
  multiple
  options={assets}
  loading={isLoading}
  value={selected}
  onChange={(_, next) => setSelected(next)}
  renderInput={(params) => <TextField {...params} label="Assets" />}
/>
```

**Practical limits:** `options` and `renderInput` are both required, and `renderInput` must render a
labelled `TextField` — that is where the accessible name comes from. Use `loading` for async fetches so
the user sees why the list is empty, and `groupBy` when options fall into categories. `freeSolo` accepts
values outside the list; only use it when that is genuinely valid, since it removes the guarantee that
the value is a known option. For a small static list this is heavier than `Select` with no gain.

## Search

A query box that **filters content already on screen**. Debounced by default, with a clear button, and
never a form field.

```tsx
import { Search } from '@aistrike-dev/ui';

<Search placeholder="Search findings..." onSearch={(q) => setQuery(q)} debounceMs={300} />
```

**Practical limits:** use `onSearch` for anything expensive — it is debounced via `debounceMs` (default
300). `onChange` fires on every keystroke, so only reach for it when you genuinely need each one. Give
it a placeholder that names what is being searched, and an `aria-label` when no visible label exists.
Every search or filter input is this component: in new UI, and in any UI you refactor.

## Worked examples

| Scenario | Control | Why |
| --- | --- | --- |
| Asset name on a create form | `TextField` | Free-form, unenumerable |
| Environment: Production / Staging / Development | `Select` | Three known options |
| Severity threshold: Critical / High / Medium / Low | `Select` | Small known list, single choice |
| Enable notifications | `Switch` | Binary, not a list |
| Scan type: Quick or Deep | `Radio` | Fewer than three options, both worth showing |
| Owner, picked from 400 users | `Autocomplete` | Too many to scroll; typing must filter |
| Tags on a finding, several at once | `Autocomplete multiple` | Multi-select from a large set |
| Assets, fetched from the API as the user types | `Autocomplete` with `loading` | Async lookup |
| Narrowing a findings table | `Search` | Filters a view, collects no value |
| Global console search in the app bar | `Search` | A query, not a field |
| Free-text notes on a finding | `TextField multiline` | Prose |
| CVE ID, which must exist but is not enumerable client-side | `TextField` with validation | The valid set is not available to the UI |

## Anti-patterns

- **A `TextField` with a magnifier adornment used as a search box.** That is `Search`, which already
  ships the icon, the clear button and the debounce. Replace these on sight when refactoring.
- **A raw `<input>` or `OutlinedInput`** anywhere a design-system input exists.
- **`Select` for fewer than three options.** It hides the choices for nothing. Use `Radio` or `Switch`.
- **`Select` for a long list.** Users cannot type to narrow it; they scroll blind. Use `Autocomplete`.
- **`Autocomplete` for four static options.** All the machinery, none of the benefit.
- **Placeholder as the only label.** It vanishes on first keystroke and screen readers may skip it.
- **`Search` used as a form field.** It is a filter with debounced callbacks, not a value input.
- **A `Select` not wrapped in `FormField`.** The field ends up with no accessible name. Reaching for
  `FormControl` or `InputLabel` instead is the same mistake twice: neither is exported from the
  package, and `FormField` is what composes them.
- **Firing a network request from `onChange`.** Use `onSearch`, which is debounced.

## Accessibility

- `TextField` associates `label` automatically. Placeholder-only labelling fails; pair `error` with
  `helperText` so validation is announced, not only coloured.
- `Select` gets its accessible name from the `FormField` wrapping it, whose `htmlFor` must match the
  `Select`'s `id`. Keyboard: Enter or Space opens, arrows navigate, Esc closes.
- `Autocomplete` renders an ARIA combobox, but only has a name if `renderInput` renders a labelled
  `TextField`.
- `Search` labels its clear button already; you supply the field's own label or `aria-label`.
- Never convey a validation failure with colour alone — the message text is what a screen reader reads.

## When you are unsure, ask

The ambiguous cases are **`Select` vs `Autocomplete`**, which depends on how many options exist in
production rather than in the mockup, and **whether an input is a filter or a field** when the design
shows a lone box above a table. **If you cannot confidently choose, stop and ask, naming the options and
the one you lean toward.**

> "The owner field shows three names in the mockup. If that list is really every user in the org I'll
> use an `Autocomplete` so it can be typed and fetched; if it is a fixed short list, `Select` is
> simpler. Which is it?"

> "There's a text box above the findings table. I'm treating it as `Search` that filters the table
> rather than a field saved with anything. Is that right?"

A single clarifying question is far cheaper than shipping a control whose behaviour surprises the user.

## References

- **Atoms → TextField**, **Atoms → Select**, **Molecules → Autocomplete**, **Molecules → Search** —
  stories for each.
- **Molecules → FormField** — the label, helper-text and error wrapper every non-`TextField` control
  needs.
- **Atoms → Radio**, **Atoms → Switch** — the controls that beat a `Select` at small option counts.
- [MUI TextField](https://mui.com/material-ui/react-text-field/) · [MUI Select](https://mui.com/material-ui/react-select/) · [MUI Autocomplete](https://mui.com/material-ui/react-autocomplete/)
