Taggable Selector
A searchable multi-select dropdown that displays selected options as tags inside the trigger, and can emit a create event when the user types a value that isn't in the list. Built on top of Selector and Tag.
Live Demo
Basic
Creatable
Type a value that isn't in the list, then either click the Create "…" button or press Enter while nothing matches. The @create handler decides what to do with the new value.
Usage
Basic (select only)
Pass :allow-create="false" when the list is fixed and users should not add new options.
<script setup>
const selected = ref([]);
const options = [
{ id: 1, text: "Option A", variant: "accent" },
{ id: 2, text: "Option B", variant: "neutral" },
];
</script>
<template>
<orio-taggable-selector
v-model="selected"
:options="options"
:allow-create="false"
placeholder="Select options"
/>
</template>Creatable with a custom label key
field and option-name let you use any option shape — they default to "id" and "text".
<script setup>
const allSkills = ref([
{ id: "vue", name: "Vue" },
{ id: "nuxt", name: "Nuxt" },
]);
const selected = ref([]);
const search = ref("");
function createSkill(name) {
const skill = { id: name.toLowerCase(), name };
allSkills.value.push(skill);
selected.value.push(skill);
}
</script>
<template>
<orio-taggable-selector
v-model="selected"
v-model:search="search"
:options="allSkills"
field="id"
option-name="name"
label="Skills"
@create="createSkill"
/>
</template>How It Works
TaggableSelector wraps Selector with multiple forced on. The trigger renders each selected option as an <orio-tag> plus a search <input>. As you type, the option list is fuzzy-filtered (via useFuzzySearch, keyed on optionName). Selecting an option checks it in the dropdown and adds a chip to the trigger.
The create flow is opt-out. When allow-create is true (the default) and the search text is non-empty and does not exactly match an existing option, a sticky "Create …" button appears at the bottom of the dropdown. The component emits @create(searchText) when:
- the Create button is clicked, or
- the user presses Enter while no options match the search.
If matches are visible, Enter selects the highlighted option instead of creating. When the search matches nothing, the dropdown shows a "No matches found" empty state above the Create button. On create, the search input is cleared.
@create does not mutate options or the selection — the parent owns persistence. Push the new option into your list (and into v-model, if it should be selected) inside the handler, as the examples above do.
Keyboard
The dropdown reuses Selector's roving-list navigation, so it is keyboard-operable just like a plain multi-select:
- ↓ / ↑ — move the highlighted option (opening the list seeds the highlight, so the first press has an anchor).
- Enter — select the highlighted option; or, when nothing matches, create (see above).
- Esc — close the dropdown.
Typing keys stay with the search input — only arrows and Esc drive the list while the input is focused.
Props
Inherits all Selector props except multiple (always true).
| Prop | Type | Default | Description |
|---|---|---|---|
options | SelectableOption[] | — | Options to select from (strings or objects). |
field | string | 'id' | Key used as the unique identifier on object options. |
optionName | string | 'text' | Key used for the visible label and fuzzy search on object options. |
placeholder | string | i18n default | Placeholder shown in the search input when nothing is selected. |
tagVariant | 'neutral' | 'accent' | — | Variant applied to every chip (there is no per-option variant). |
allowCreate | boolean | true | Show the "Create …" button and allow Enter-to-create. false disables it. |
label | string | — | Label text (passed through to ControlElement). |
size | 'xs'…'xl' | inherited | Control size (passed through to ControlElement). |
Models
| Model | Type | Default | Description |
|---|---|---|---|
v-model | SelectableOption[] | [] | Two-way binding for the selected options array. |
v-model:search | string | '' | Two-way binding for the search input. Cleared on create. |
Events
| Event | Payload | Description |
|---|---|---|
create | string | The user asked to create a new option (Create button, or Enter with no matches). The search text. |
i18n
| Key | English |
|---|---|
taggableSelector.placeholder | Select options |
taggableSelector.create | Create "{search}" |
taggableSelector.noMatches | No matches found |
Styling
Selected tags wrap inside the trigger with flex-wrap:
--color-accent-soft /* Accent tag background */
--color-accent-border /* Accent tag border */
--color-surface /* Neutral tag background */
--color-border /* Neutral tag border */