Comboboxes
Collect entries or chips in filterable fields.
Setup
Combobox styles are included in Manifest CSS or a standalone stylesheet, both referencing theme variables. The plugin is included in manifest.js with all core plugins, or it can be loaded on its own.
<!-- Manifest CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mnfst@latest/lib/manifest.min.css" />
<!-- Manifest JS -->
<script src="https://cdn.jsdelivr.net/npm/mnfst@latest/lib/manifest.min.js"></script>
Triggers
Add x-combobox to the element that holds the selection and opens the list. Three elements work as triggers, and the rest of this guide uses an input.
Input
Type to filter or to enter free text.
<input x-combobox="countries" placeholder="Country">
Textarea
Textareas are effectively the same as inputs in style and function. The .multiple modifier enabled both inputs and textareas to support multiple entries, which wrap and grow the field's height on overflow.
<textarea x-combobox.multiple.chips="skills" placeholder="Add skills"></textarea>
Button
Buttons behave like a select menu trigger, with no typing input. A single field shows the choice as the button's text. A multiple field shows chips.
<button x-combobox="priority">Select priority</button>
<menu popover id="priority">
<li>Low</li>
<li>Medium</li>
<li>High</li>
</menu>
Chips
Add .chips to show selections as removable chips, and .multiple to allow more than one. Type a value and press Enter, or a comma, to commit it as a chip. Press Backspace on an empty field to remove the last one.
<input x-combobox.multiple.chips placeholder="Add emails">
Separators
Set your own commit keys with the separators option, where each character commits the current entry. So ", " commits on a comma or a space. Pasting text that already contains a separator splits it into several chips at once.
<input x-combobox.multiple.chips="{ separators: ', ' }" placeholder="Add tags">
Starting Values
Seed the field with a starting set using the value attribute, written with the same separators.
<input x-combobox.multiple.chips value="ana@acme.com, sam@acme.com">
Suggestions List
Point a field at a list of options to filter as the user types. Name the list by its id in the directive value, the same way dropdowns reference their menu. The list is a <datalist> or <select> kept in the page as the source.
A single field lets the user pick one option or type their own. Re-opening it shows the whole list again, so the choice can be swapped.
<input x-combobox="countries" placeholder="Country">
<datalist id="countries">
<option>Australia</option>
<option>Brazil</option>
<option>Canada</option>
</datalist>
Add .multiple.chips to choose several. Chosen options drop out of the list as they are picked.
<input x-combobox.multiple.chips="skills" placeholder="Skills">
<datalist id="skills">
<option>Design</option>
<option>Writing</option>
<option>Research</option>
</datalist>
Dropdown Menu
For richer rows, author the list as a <menu popover> instead of a datalist, same as dropdown menus. Give each item a data-value for the value to store and a data-label for the chip text. Everything inside the item is free to hold extra content.
<input x-combobox.multiple.chips="people" placeholder="Assign people">
<menu popover id="people">
<li data-value="ana" data-label="Ana Ruiz">Ana Ruiz <span class="trailing">ana@acme.com</span></li>
<li data-value="sam" data-label="Sam Cole">Sam Cole <span class="trailing">sam@acme.com</span></li>
</menu>
Options
Beyond modes, settings are passed as an object in the directive value. The bare id form is shorthand for the source key, so x-combobox="people" and x-combobox="{ source: 'people' }" are the same.
| Option | Purpose |
|---|---|
source |
ID of the <datalist>, <select>, or <menu> holding the options |
max |
Most selections allowed; omit for uncapped selections |
filter |
How typed text matches options. See below |
separators |
Characters that commit the current entry as a chip |
min |
Characters to type before remote results are fetched |
debounce |
Milliseconds to wait after typing before fetching. Default 200 |
Filtering
Set how typed text matches options with the filter option.
| Value | Match |
|---|---|
includes |
Text appears anywhere in the option (default mode) |
startswith |
Option begins with the text |
none |
No filtering; always show every option |
pattern |
Match each option's own data-pattern expression against the input |
<input x-combobox="{ source: 'countries', filter: 'startswith' }">
Modifiers
New Values
A field accepts anything the user types by default. Add .create to show an explicit "Add …" row for the current text when it does not match an option.
<input x-combobox.multiple.chips.create="skills" placeholder="Skills">
To restrict the field to the list and reject anything else, add .strict instead.
Limit
Cap the number of selections with the max option. Once the limit is reached the field stops accepting input until a chip is removed.
<input x-combobox.multiple.chips="{ source: 'skills', max: 3 }" placeholder="Pick up to 3">
Locked Values
Mark a value as locked to keep it in the field but block its removal — its × is hidden and the chip ignores delete while still showing as selected. Flag an option with data-locked, or pass a locked list in the directive. Below, SSL is always included so its chip has no remove button, while the other features can be cleared.
<input x-combobox.multiple.chips="features" x-model="plan" placeholder="Add features">
<datalist id="features">
<option data-locked>SSL</option>
<option>Backups</option>
<option>Analytics</option>
</datalist>
Locking is reactive. Bind an option's flag with :data-locked="isRequired || null" — Alpine adds the attribute when the value is truthy and removes it on null — and the chip locks or unlocks live as state changes (× hidden, delete ignored, still selected). The directive's locked list works the same way: pass locked: <expression> in the config object. So an Owner chip can lock only while it's the last owner, then unlock when another is added.
Remote Suggestions
Add .async to fetch options as the user types instead of filtering a fixed list. On each keystroke the field sends a combobox-filter event carrying the typed value and a setOptions callback. Call setOptions with your results to fill the list.
<input x-combobox.async="{ min: 1 }" placeholder="Search users"
@combobox-filter="
fetch('/api/users?q=' + $event.detail.value)
.then(r => r.json())
.then(users => $event.detail.setOptions(users))
">
Each result is { value, label }, or a plain string when the two are the same. The field opens on focus, shows a brief loading state while it waits, and ignores out-of-date responses. Use min to set how many characters are needed before fetching, and debounce to set the pause after typing.
Form Participation
Add a name attribute to submit with a <form>. A single text field submits its value directly. A field with chips, or any multiple field, submits one entry per value under that name, which arrives as a list on the server.
<form>
<input x-combobox.multiple.chips="tags" name="tags" value="design, research">
<button type="submit">Save</button>
</form>
Dynamic Data
Bind a field to state with x-model and it stays in sync both ways. The field renders one chip per value on load and re-renders whenever the bound value changes — so the same combobox both composes a new selection and edits an existing record (switch which record is selected and the chips follow). Adding or removing chips writes straight back to the bound value.
The bound value can be an array or a comma-separated string; the field keeps whatever shape it started with. Any reactive source works, including local and cloud data through $x.
<div x-data="{ member: { roles: ['Design', 'Research'] } }">
<input x-combobox.multiple.chips="roles" x-model="member.roles" placeholder="Roles">
</div>
When the list carries separate values and labels — a dropdown menu with data-value/data-label — the chip shows the label while the bound value holds the token. Pick "Owner" and roles stores owner, keeping value mapping clean for editing.
Localization
The few built-in UI strings are overridable through the universal _ui block, namespaced under combobox, in any local data file your project loads. Override only the keys you want. Anything omitted stays in English, and switching locale updates them live.
{
"data": {
"content": {
"en": "/data/content.en.yaml",
"fr": "/data/content.fr.yaml"
}
}
}
The add string places the typed text with a {value} token, so translations keep their own word order and punctuation.
| Key | Default | Used for |
|---|---|---|
empty |
No matches | Shown when nothing matches |
add |
Add “{value}” | The .create row label |
loading |
Searching… | Shown while remote results load |
prompt |
Type to search | Remote field before enough is typed |
Keyboard
| Key | Action |
|---|---|
↑ ↓ |
Move through the list |
Enter |
Select the highlighted option, or commit typed text |
Backspace |
Remove the last chip when the field is empty |
| Separator | Commit the current text as a chip |
Escape |
Close the list |
Styles
Theme
Comboboxes use the following theme variables.
| Variable | Purpose |
|---|---|
--color-field-surface |
Field background, and the highlighted option |
--color-field-surface-hover |
Field hover background |
--color-field-inverse |
Field text |
--color-popover-surface |
Suggestion menu and chip background |
--color-content-stark |
Chip text and focus ring |
--color-content-neutral |
Chip remove icon and the "Add …" row |
--color-content-subtle |
Caret and empty-state text |
--color-negative-surface / --color-negative-inverse |
Invalid chip |
--radius |
Field, chip, and menu corners |
--spacing-field-height |
Field height |
--spacing-field-padding |
Field padding |
--icon-chevron-down |
Button trigger caret |
--transition |
Interactive transitions |
Customization
The markup is semantic, so the parts are plain selectors.
| Selector | Part |
|---|---|
.combobox |
The field shell |
.combobox-chip |
A chip, with its label in a span and remove control in a button |
menu[role=listbox] |
The suggestion menu |
[role=option] |
An option |
[role=option][aria-selected=true] |
A chosen option |
[role=option][aria-current=true] |
The highlighted option |
/* Pill-shaped chips */
.combobox-chip {
border-radius: 100px;
}
Article does not exist
There is no documentation at this path.