# TallStackUI: Autocomplete
> TallStackUI is a TALL Stack (Tailwind CSS, Alpine.js, Laravel, Livewire)
> component library providing 80+ Blade components for building modern web interfaces.
An input-first single-select component: a regular text `` paired with a floating dropdown of suggestions that filters as the user types. Each item supports a `value`, an optional `description` shown as a subtitle line, an optional `image` rendered as a circular avatar to the left, and an optional `metadata` object carried through untouched for the consumer to read after selection. Free text is allowed by default; an opt-in `strict` mode constrains `wire:model` to predefined values only. Items can be supplied locally via `:items` or fetched on demand with `:request`.
The component layers on top of `Form/Input` (so `floatable`, label, hint, error styling, and the input wrapper come from the same primitives as `Form/Date` and `Form/Password`) and uses `Floating` for the dropdown.
## Basic Usage
```blade
```
```blade
```
```blade
```
```blade
```
Outside Livewire, give it a `name` and it backs a plain form: a hidden input carries the
picked value to the server, and `value` seeds the initial selection. The value is read
as it is, commas included. A string made only of digits becomes a number, so its leading
zeros are dropped and it matches an item whose `value` is that number, not the same
digits as a string. The hidden input belongs to the component that renders it, so the
same `name` can repeat on the page, in another form or as an array name such as
`cities[]`.
```blade
```
## Attributes
| Attribute | Type | Default | Description |
|--------------|-------------------------|-----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
| items | Collection\|array\|null | null | Local source. Mutually exclusive with `request`. |
| request | string\|array\|null | null | Remote source. String shorthand uses `GET `; array form: `['url' => ..., 'method' => 'get'\|'post', 'params' => [...]]`. |
| id | string\|null | null | Forwarded to the underlying ``. |
| label | string\|null | null | Rendered above the input. |
| hint | string\|null | null | Rendered below the input when there is no error. |
| placeholder | string\|null | from translations | Input placeholder text. |
| prefix | string\|null | null | Adornment on the left of the input (forwarded to `Form/Input`). |
| suffix | string\|null | null | Adornment on the right of the input. Renders before the loading/clear icons. |
| clearable | bool\|null | null | Shows an `×` to empty the input. Dispatches the `clear` event. |
| invalidate | bool\|null | null | Same semantics as `Form/Input`: opts the component into Livewire validation styling. |
| strict | bool\|null | null (or global from config) | Constrains `wire:model` to values that exist in the items list. See "Strict mode". |
| select | string\|null | null (or global from config) | Remaps the item keys, e.g. `value:name\|description:email\|image:avatar`. See "Field Mapping". |
| lazy | int\|null | null | Minimum chars before the dropdown opens / a remote request is fired. |
| disabled | bool\|null | null | Locks the input and the dropdown. The value is not submitted. |
| readonly | bool\|null | null | Locks the input and the dropdown. The value is still submitted. |
| placeholders | array\|null | merged from `trans('ts-ui::messages.autocomplete')` | Override translation strings (`empty`, `loading`, `default`). |
The component does **not** support `multiple`. Reach for `Form/Select/Styled` when multiple selection is required.
## Item Object Structure
| Key | Type | Required | Description |
|-------------|--------|----------|---------------------------------------------------------------------------------------------------------------|
| value | string | Yes | Visible text in the input and value bound to `wire:model`. Filter matches against this. |
| description | string | No | Subtitle line shown below `value` inside the dropdown row. Filter also matches against this. |
| image | string | No | URL displayed as a circular avatar on the left of the dropdown row. |
| disabled | bool | No | Dims the row and blocks selection. |
| metadata | mixed | No | Opaque passthrough. Never read or rendered by the component; carried to `selected` and to the `select` event. |
Keys outside this table are **dropped** during normalization. Sources that name
them differently are remapped with `select` (see "Field Mapping") instead of
being reshaped before reaching the component. To carry your own data alongside
an item, nest it under `metadata`:
```php
[
'value' => 'Alice',
'description' => 'admin',
'metadata' => ['id' => 42, 'role' => 'admin', 'team_id' => 7],
]
```
```json
[
{ "value": "Alice", "description": "admin", "metadata": { "id": 42, "role": "admin" } }
]
```
`metadata` is not filtered against, not rendered in the dropdown row, and not sent to `wire:model` — the model still receives `value`. It exists purely so the consumer can react to the picked item:
```blade
```
The bucket is deliberately namespaced rather than flattened onto the item: internal keys the component may add in future versions can never collide with consumer data.
## Field Mapping
When the items — local or fetched — name their fields differently, `select` remaps
them instead of forcing a reshape at the source:
```blade
```
Format: `value:key|description:key|image:key|metadata:key`. Any part left out falls
back to the key of the same name, and `disabled` is always read from `disabled`.
The remap happens during normalization, so filtering, the `select` event payload
and `wire:model` all keep using the canonical names.
The mapping can also be a global default in `config/tallstackui.php`:
```php
'autocomplete' => [
TallStackUi\Components\Form\Autocomplete\Component::class,
[
'strict' => false,
'select' => 'value:name|description:email|image:avatar',
],
],
```
The inline attribute always wins over the configured default.
## Slots
| Slot | Description |
|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| after | Rendered inside the dropdown when the filtered list is empty. Mirrors `Form/Select/Styled`'s `after`. Useful for "no results — create one?" flows. When omitted, the component falls back to the `placeholders.empty` string. |
## Validation Constraints
- `items` and `request` cannot be defined at the same time.
- When `request` is an array, `url` is required.
- When `request['method']` is set, it must be `get` or `post`.
- When `request['params']` is set, it must be a non-empty array.
## Strict Mode
By default, `wire:model` reflects whatever the user types — even values that don't appear in the list. With `:strict`, the binding only updates when a row is picked from the dropdown. If the user blurs or presses Esc with an unmatched query, the input reverts to the last selected value (or empties if nothing was ever picked). Use the `after` slot as the escape hatch for "I can't find this option, create one".
A value that is already bound when the component mounts, or that the server sets later, is checked against the list only with a local source. With `request` there is no list to check it against until the user types, so the value is kept as it came. Validate it on the server.
A value emptied on the server, such as a `reset()` after saving, empties the field and
drops the last selection with it, so closing the panel afterwards does not bring the old
value back.
A global default is available so an entire application can opt every Autocomplete into strict mode at once:
```php
'autocomplete' => [
\TallStackUi\Components\Form\Autocomplete\Component::class,
[
'strict' => true,
],
],
```
(in `config('tallstackui.components.autocomplete.strict')`.)
## Local vs Remote Source
```blade
```
For remote mode the component fires a debounced request (~250 ms) every time the user types, sending the current query as `search`. Every remote request carries the same `X-TallStack-Ui` header as Select Styled, plus `X-Requested-With` and, when present, the CSRF token. Loading state shows a spinner inside the dropdown panel; the dropdown only opens once typing has begun (or if `lazy` is satisfied).
## Lazy Threshold
Setting `lazy="N"` makes the component ignore any open/fetch attempt while the query has fewer than `N` characters. Useful with remote sources to avoid firing requests on every single keystroke:
```blade
```
Below the threshold the panel stays closed; once the user crosses it, the panel opens straight into the loading state and resolves to results / "no results".
## Keyboard
| Key | Behavior |
|-------|------------------------------------------------|
| ↓ / ↑ | Move the highlighted row. |
| Enter | Pick the highlighted row. |
| Esc | Close the dropdown without changing the model. |
| Tab | Close the dropdown and move focus naturally. |
The highlighted row is auto-scrolled into view inside the dropdown.
## Alpine.js Event Payloads
```blade
```
| Event | `$event.detail` | When |
|--------|---------------------------------------------------------------|------------------------------------------|
| select | `{ item: { value, description, image, disabled, metadata } }` | The user picked a row from the dropdown. |
| clear | `null` | The user pressed the clear `×` button. |
| open | `null` | The floating dropdown opened. |
| close | `null` | The floating dropdown closed. |
## Soft Customization
Soft customization allows you to override default Tailwind CSS classes used by this component at runtime, either through a service provider or scoped per-instance.
### Customization
```php
TallStackUi::customize()
->form('autocomplete')
->block('box.list.item.wrapper', 'your-tailwind-classes');
```
### Available Blocks
The Autocomplete delegates the input rendering to `Form/Input`, so any styling related to the input wrapper, label, hint, error state, or prefix slot is customized via `form.input` (and the `Form/Input` AI doc), not here. The blocks below are only the bits the Autocomplete adds on top.
| Block Name | Purpose |
|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| adornment.suffix | Wrapper around the user-provided `suffix` text inside the input's suffix slot. |
| icon.wrapper | Container for the loading/clear icons inside the input. |
| icon.clear | Clear `×` icon. |
| icon.loading | Spinner shown next to the input while remote requests are in flight. |
| floating.default | Inherits `Floating::customization()['wrapper']`. |
| floating.class | Overflow and stacking for the panel. Carries `w-full`, which is what opts the panel into the Floating's anchor width sync — drop it and the panel sizes to its content. |
| box.list.wrapper | The `
` container of the dropdown. |
| box.list.item.wrapper | Each option row. |
| box.list.item.base | Inner flex container (image + content). |
| box.list.item.value | Primary line (from `item.value`). |
| box.list.item.description | Secondary subtitle line (from `item.description`). |
| box.list.item.image | Avatar circle on the left of the row. |
| box.list.item.content | Wrapper around value + description. |
| box.list.item.highlighted | Keyboard-highlighted row. |
| box.list.item.selected | Currently selected row. |
| box.list.item.disabled | Disabled row. |
| box.list.empty | Default "no results" message when the `after` slot is not used. |
| box.list.loading.wrapper | Loading container inside the dropdown. |
| box.list.loading.class | Spinner icon classes inside the dropdown (matches Select Styled's loading animation). |