Combobox
v0.0.10
mix mishka.ui.gen.headless combobox
Overview of the Phoenix and LiveView Headless Combobox Component
The Mishka Chelekom Headless Combobox ships zero opinionated styling - only the semantic markup, full WAI-ARIA wiring, and paired-presence state (data-open/data-closed). Typing filters the options (non-matching options get data-hidden), ArrowDown opens the popup and moves a roving highlight over the navigable options, Enter or a click selects, and Escape closes. A selected option carries data-selected and aria-selected, styleable through the indicator part. Every part - input, trigger, clear, chips, popup, group, item, indicator, empty and create - is addressable through a *_class attribute, so you style it entirely with your own Tailwind classes while the accessibility and behavior keep working underneath.
In a grouped list, group_class styles each group, group_label_class its label and group_list_class the list of options inside it, so group_list_class="pl-4" indents the options under their label. An option slot adds its own class to one item.
Base UI Combobox example - single-select with clear & trigger (Phoenix headless)
Base UI Combobox example - single-select with clear & trigger (Phoenix headless)
- Apple
- Banana
- Orange
- Pineapple
- Grape
- Mango
- Strawberry
- Blueberry
- Raspberry
- Blackberry
- Cherry
- Peach
- Pear
- Plum
- Kiwi
- Watermelon
- Cantaloupe
- Honeydew
- Papaya
- Guava
- Lychee
- Pomegranate
- Apricot
- Grapefruit
- Passionfruit
- No fruits found.
<.combobox
id="baseui-combobox-hero"
clear
trigger
placeholder="e.g. Apple"
class="relative"
control_class="relative h-8 w-56 border border-neutral-950 bg-white dark:bg-neutral-950 focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-neutral-950 dark:focus-within:outline-white dark:border-white"
input_class="h-full w-full border-0 bg-white pl-2 pr-[calc(0.5rem+2rem*2)] dark:bg-neutral-950 text-sm font-normal text-neutral-950 outline-none placeholder:text-neutral-500 dark:placeholder:text-neutral-400 dark:text-white"
clear_class="absolute right-6 bottom-0 flex h-full w-6 items-center justify-center border-0 bg-transparent p-0 text-neutral-950 dark:text-white data-[hidden]:hidden"
trigger_class="absolute right-0 bottom-0 flex h-full w-6 items-center justify-center border-0 bg-transparent p-0 text-neutral-950 dark:text-white"
popup_class="mt-1 w-56 max-w-[var(--available-width)] origin-[var(--transform-origin)] border border-neutral-950 bg-white text-neutral-950 shadow-[0.25rem_0.25rem_0_rgb(0_0_0_/_12%)] max-h-[22.5rem] overflow-y-auto overscroll-contain py-1 scroll-py-1 dark:border-white dark:bg-neutral-950 dark:text-white dark:shadow-none data-[closed]:hidden"
item_class="group/item grid cursor-default grid-cols-[1rem_1fr] items-center gap-2 p-2 text-sm leading-4 outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-white data-[highlighted]:before:absolute data-[highlighted]:before:inset-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:bg-neutral-950 dark:data-[highlighted]:text-neutral-950 dark:data-[highlighted]:before:bg-white data-[hidden]:hidden"
indicator_class="col-start-1 invisible group-data-[selected]/item:visible"
empty_class="py-4 pr-4 pl-2 text-sm leading-4 text-neutral-500 dark:text-neutral-400 data-[hidden]:hidden"
>
<:trigger_icon>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="display:block">
<path d="M12 6H4l4 4.5z" />
</svg>
</:trigger_icon>
<:clear_icon>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-linecap="square"
stroke-linejoin="round"
style="display:block"
>
<path d="m4.5 4.5 7 7m-7 0 7-7" />
</svg>
</:clear_icon>
<:item_indicator>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
style="display:block"
>
<path d="m2.5 8.5 4 4 7-9" />
</svg>
</:item_indicator>
<:option
:for={
f <-
~w(Apple Banana Orange Pineapple Grape Mango Strawberry Blueberry Raspberry Blackberry Cherry Peach Pear Plum Kiwi Watermelon Cantaloupe Honeydew Papaya Guava Lychee Pomegranate Apricot Grapefruit Passionfruit)
}
value={String.downcase(f)}
>
<span class="col-start-2">{f}</span>
</:option>
<:empty>No fruits found.</:empty>
</.combobox>Base UI Combobox example - grouped options (Phoenix headless)
Base UI Combobox example - grouped options (Phoenix headless)
-
Fruits
- Apple
- Banana
- Mango
- Kiwi
- Grape
- Orange
- Strawberry
- Watermelon
-
Vegetables
- Broccoli
- Carrot
- Cauliflower
- Cucumber
- Kale
- Bell pepper
- Spinach
- Zucchini
- No produce found.
<.combobox
id="baseui-combobox-grouped"
clear
trigger
placeholder="e.g. Mango"
class="relative"
control_class="relative h-8 w-64 border border-neutral-950 bg-white dark:bg-neutral-950 focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-neutral-950 dark:focus-within:outline-white dark:border-white"
input_class="h-full w-full border-0 bg-white pl-2 pr-[calc(0.5rem+2rem*2)] dark:bg-neutral-950 text-sm font-normal text-neutral-950 outline-none placeholder:text-neutral-500 dark:placeholder:text-neutral-400 dark:text-white"
clear_class="absolute right-6 bottom-0 flex h-full w-6 items-center justify-center border-0 bg-transparent p-0 text-neutral-950 dark:text-white data-[hidden]:hidden"
trigger_class="absolute right-0 bottom-0 flex h-full w-6 items-center justify-center border-0 bg-transparent p-0 text-neutral-950 dark:text-white"
popup_class="mt-1 w-64 max-w-[var(--available-width)] origin-[var(--transform-origin)] border border-neutral-950 bg-white text-neutral-950 shadow-[0.25rem_0.25rem_0_rgb(0_0_0_/_12%)] max-h-[22.5rem] overflow-auto overscroll-contain py-1 scroll-py-1 dark:border-white dark:bg-neutral-950 dark:text-white dark:shadow-none data-[closed]:hidden"
group_class="block pb-2 last:pb-0"
group_label_class="block py-2 pr-2 pl-8 text-sm leading-4 text-neutral-500 select-none dark:text-neutral-400"
item_class="group/item grid cursor-default grid-cols-[1rem_1fr] items-center gap-2 p-2 text-sm leading-4 outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-white data-[highlighted]:before:absolute data-[highlighted]:before:inset-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:bg-neutral-950 dark:data-[highlighted]:text-neutral-950 dark:data-[highlighted]:before:bg-white data-[hidden]:hidden"
indicator_class="col-start-1 flex items-center justify-center invisible group-data-[selected]/item:visible"
empty_class="py-4 pr-4 pl-2 text-sm leading-4 text-neutral-500 dark:text-neutral-400 data-[hidden]:hidden"
>
<:trigger_icon>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="display:block">
<path d="M12 6H4l4 4.5z" />
</svg>
</:trigger_icon>
<:clear_icon>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-linecap="square"
stroke-linejoin="round"
style="display:block"
>
<path d="m4.5 4.5 7 7m-7 0 7-7" />
</svg>
</:clear_icon>
<:item_indicator>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
style="display:block"
>
<path d="m2.5 8.5 4 4 7-9" />
</svg>
</:item_indicator>
<:option
:for={f <- ~w(Apple Banana Mango Kiwi Grape Orange Strawberry Watermelon)}
value={"fruit-" <> String.downcase(f)}
group="Fruits"
>
<span class="col-start-2">{f}</span>
</:option>
<:option
:for={
v <- [
"Broccoli",
"Carrot",
"Cauliflower",
"Cucumber",
"Kale",
"Bell pepper",
"Spinach",
"Zucchini"
]
}
value={"veg-" <> String.downcase(v)}
group="Vegetables"
>
<span class="col-start-2">{v}</span>
</:option>
<:empty>No produce found.</:empty>
</.combobox>Base UI Combobox example - multiple selection with chips (Phoenix headless)
Base UI Combobox example - multiple selection with chips (Phoenix headless)
- JavaScript
- TypeScript
- Python
- Java
- C++
- C#
- PHP
- Ruby
- Go
- Rust
- Swift
- No languages found.
<.combobox
id="baseui-combobox-multiple"
multiple
placeholder="e.g. TypeScript"
control_class="flex min-h-8 w-64 cursor-text flex-wrap items-center gap-0.5 border border-neutral-950 bg-white dark:bg-neutral-950 px-2 py-1 focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-neutral-950 dark:focus-within:outline-white has-[button]:px-1 dark:border-white min-[32rem]:w-[22rem]"
chips_class="flex w-full flex-wrap items-center gap-1"
chip_class="group flex min-h-[calc(1.5rem-2px)] cursor-default items-center gap-1 overflow-hidden bg-neutral-100 py-0 pr-[0.2rem] pl-[0.4rem] text-sm leading-none text-neutral-950 outline-none focus-within:bg-neutral-950 focus-within:text-white dark:bg-neutral-800 dark:text-white dark:focus-within:bg-white dark:focus-within:text-neutral-950"
chip_remove_class="flex size-4 items-center justify-center border-0 bg-transparent p-0 text-inherit hover:bg-neutral-200 group-focus-within:hover:bg-neutral-700 dark:hover:bg-neutral-700 dark:group-focus-within:hover:bg-neutral-200"
input_class="h-[calc(1.5rem-2px)] min-w-12 flex-1 border-0 bg-white p-0 text-sm dark:bg-neutral-950 font-normal text-neutral-950 outline-none placeholder:text-neutral-500 dark:placeholder:text-neutral-400 dark:text-white"
popup_class="mt-1 w-64 max-h-[24.5rem] max-w-[var(--available-width)] origin-[var(--transform-origin)] overflow-y-auto overscroll-contain border border-neutral-950 bg-white py-1 text-neutral-950 shadow-[0.25rem_0.25rem_0_rgb(0_0_0_/_12%)] dark:border-white dark:bg-neutral-950 dark:text-white dark:shadow-none data-[closed]:hidden"
item_class="group/item grid cursor-default grid-cols-[1rem_1fr] items-center gap-2 p-2 text-sm leading-4 outline-none select-none data-[selected]:relative data-[selected]:z-0 data-[selected]:text-neutral-950 data-[selected]:before:absolute data-[selected]:before:inset-0 data-[selected]:before:z-[-1] data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-white data-[highlighted]:before:absolute data-[highlighted]:before:inset-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:bg-neutral-950 dark:data-[selected]:text-white dark:data-[highlighted]:text-neutral-950 dark:data-[highlighted]:before:bg-white data-[hidden]:hidden"
indicator_class="col-start-1 invisible group-data-[selected]/item:visible"
empty_class="py-2 pr-4 pl-2 text-sm leading-4 text-neutral-500 dark:text-neutral-400 data-[hidden]:hidden"
>
<:chip_remove_icon>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-linecap="square"
stroke-linejoin="round"
style="display:block"
>
<path d="m4.5 4.5 7 7m-7 0 7-7" />
</svg>
</:chip_remove_icon>
<:item_indicator>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
style="display:block"
>
<path d="m2.5 8.5 4 4 7-9" />
</svg>
</:item_indicator>
<:option
:for={
lang <- [
"JavaScript",
"TypeScript",
"Python",
"Java",
"C++",
"C#",
"PHP",
"Ruby",
"Go",
"Rust",
"Swift"
]
}
value={lang}
>
<span class="col-start-2">{lang}</span>
</:option>
<:empty>No languages found.</:empty>
</.combobox>Base UI Combobox example - input inside popup (Phoenix headless)
Base UI Combobox example - input inside popup (Phoenix headless)
- Australia
- Canada
- France
- Germany
- India
- Italy
- Japan
- Mexico
- Spain
- United Kingdom
- United States
- Vietnam
- No countries found.
<.combobox
id="baseui-combobox-input-inside-popup"
placeholder="e.g. United Kingdom"
control_class="relative flex h-8 min-w-40 items-center border border-neutral-950 bg-white px-2 text-sm dark:bg-neutral-950 dark:border-white focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-neutral-950 dark:focus-within:outline-white"
input_class="h-full w-full border-0 bg-white text-sm font-normal text-neutral-950 outline-none placeholder:text-neutral-500 dark:bg-neutral-950 dark:placeholder:text-neutral-400 dark:text-white"
popup_class="mt-1 max-h-[24.5rem] max-w-[var(--available-width)] origin-[var(--transform-origin)] overflow-auto overscroll-contain border border-neutral-950 bg-white py-1 text-neutral-950 shadow-[0.25rem_0.25rem_0_rgb(0_0_0_/_12%)] dark:border-white dark:bg-neutral-950 dark:text-white dark:shadow-none data-[closed]:hidden"
item_class="group/item grid min-w-80 cursor-default grid-cols-[1rem_1fr] items-center gap-2 p-2 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-white data-[highlighted]:before:absolute data-[highlighted]:before:inset-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:bg-neutral-950 dark:data-[highlighted]:text-neutral-950 dark:data-[highlighted]:before:bg-white data-[hidden]:hidden"
indicator_class="col-start-1 invisible group-data-[selected]/item:visible"
empty_class="py-4 pr-4 pl-2 text-sm leading-4 text-neutral-500 dark:text-neutral-400 data-[hidden]:hidden"
>
<:item_indicator>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
style="display:block"
>
<path d="m2.5 8.5 4 4 7-9" />
</svg>
</:item_indicator>
<:option
:for={
{code, label} <- [
{"au", "Australia"},
{"ca", "Canada"},
{"fr", "France"},
{"de", "Germany"},
{"in", "India"},
{"it", "Italy"},
{"jp", "Japan"},
{"mx", "Mexico"},
{"es", "Spain"},
{"gb", "United Kingdom"},
{"us", "United States"},
{"vn", "Vietnam"}
]
}
value={code}
>
<span class="col-start-2">{label}</span>
</:option>
<:empty>No countries found.</:empty>
</.combobox>Base UI Combobox example - creatable multi-select (Phoenix headless)
Base UI Combobox example - creatable multi-select (Phoenix headless)
- bug
- documentation
- enhancement
- help wanted
- good first issue
- No labels found.
- Create “”
<.combobox
id="baseui-combobox-creatable"
multiple
creatable
placeholder="e.g. bug"
control_class="flex min-h-8 w-64 cursor-text flex-wrap items-center gap-0.5 border border-neutral-950 bg-white dark:bg-neutral-950 px-2 py-1 focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-neutral-950 dark:focus-within:outline-white has-[button]:px-1 dark:border-white min-[32rem]:w-[22rem]"
chips_class="flex w-full flex-wrap items-center gap-1"
chip_class="group flex min-h-[calc(1.5rem-2px)] cursor-default items-center gap-1 overflow-hidden bg-neutral-100 py-0 pr-[0.2rem] pl-[0.4rem] text-sm leading-none text-neutral-950 outline-none focus-within:bg-neutral-950 focus-within:text-white dark:bg-neutral-800 dark:text-white dark:focus-within:bg-white dark:focus-within:text-neutral-950"
chip_remove_class="flex size-4 items-center justify-center border-0 bg-transparent p-0 text-inherit hover:bg-neutral-200 group-focus-within:hover:bg-neutral-700 dark:hover:bg-neutral-700 dark:group-focus-within:hover:bg-neutral-200"
input_class="h-[calc(1.5rem-2px)] min-w-12 flex-1 border-0 bg-white p-0 text-sm dark:bg-neutral-950 font-normal text-neutral-950 outline-none placeholder:text-neutral-500 dark:placeholder:text-neutral-400 dark:text-white"
popup_class="mt-1 w-64 max-h-[24.5rem] max-w-[var(--available-width)] origin-[var(--transform-origin)] overflow-y-auto overscroll-contain border border-neutral-950 bg-white py-1 text-neutral-950 shadow-[0.25rem_0.25rem_0_rgb(0_0_0_/_12%)] dark:border-white dark:bg-neutral-950 dark:text-white dark:shadow-none data-[closed]:hidden"
item_class="group/item grid cursor-default grid-cols-[1rem_1fr] items-center gap-2 p-2 text-sm leading-4 outline-none select-none data-[selected]:relative data-[selected]:z-0 data-[selected]:text-neutral-950 data-[selected]:before:absolute data-[selected]:before:inset-0 data-[selected]:before:z-[-1] data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-white data-[highlighted]:before:absolute data-[highlighted]:before:inset-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:bg-neutral-950 dark:data-[selected]:text-white dark:data-[highlighted]:text-neutral-950 dark:data-[highlighted]:before:bg-white data-[hidden]:hidden"
indicator_class="col-start-1 invisible group-data-[selected]/item:visible"
create_class="grid cursor-default grid-cols-[1rem_1fr] items-center gap-2 p-2 text-sm leading-4 outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-white data-[highlighted]:before:absolute data-[highlighted]:before:inset-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:bg-neutral-950 dark:data-[highlighted]:text-neutral-950 dark:data-[highlighted]:before:bg-white data-[hidden]:hidden"
create_label_class="col-start-2"
empty_class="py-2 pr-4 pl-2 text-sm leading-4 text-neutral-500 dark:text-neutral-400 data-[hidden]:hidden"
>
<:chip_remove_icon>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-linecap="square"
stroke-linejoin="round"
style="display:block"
>
<path d="m4.5 4.5 7 7m-7 0 7-7" />
</svg>
</:chip_remove_icon>
<:item_indicator>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
style="display:block"
>
<path d="m2.5 8.5 4 4 7-9" />
</svg>
</:item_indicator>
<:create_icon>
<span class="col-start-1">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-linecap="square"
stroke-linejoin="round"
style="display:block"
>
<path d="M1.5 8h13M8 14.5v-13" />
</svg>
</span>
</:create_icon>
<:option
:for={lbl <- ["bug", "documentation", "enhancement", "help wanted", "good first issue"]}
value={lbl}
>
<span class="col-start-2">{lbl}</span>
</:option>
<:empty>No labels found.</:empty>
</.combobox>Base UI Combobox example - async single-select (Phoenix headless)
Base UI Combobox example - async single-select (Phoenix headless)
- Leslie Alexander[email protected]@leslieProduct Manager
- Kathryn Murphy[email protected]@kathrynMarketing Lead
- Courtney Henry[email protected]@courtneyDesign Systems
- Michael Foster[email protected]@michaelEngineering Manager
- Lindsay Walton[email protected]@lindsayProduct Designer
- Try a different search term.
<.combobox
id="baseui-combobox-async-single"
clear
trigger
placeholder="e.g. Michael"
class="relative"
control_class="relative h-8 w-64 border border-neutral-950 bg-white dark:bg-neutral-950 focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-neutral-950 dark:focus-within:outline-white dark:border-white md:w-80"
input_class="h-full w-full border-0 bg-white pl-2 pr-[calc(0.5rem+2rem*2)] dark:bg-neutral-950 text-sm font-normal text-neutral-950 outline-none placeholder:text-neutral-500 dark:placeholder:text-neutral-400 dark:text-white"
clear_class="absolute right-6 bottom-0 flex h-full w-6 items-center justify-center border-0 bg-transparent p-0 text-neutral-950 dark:text-white data-[hidden]:hidden"
trigger_class="absolute right-0 bottom-0 flex h-full w-6 items-center justify-center border-0 bg-transparent p-0 text-neutral-950 dark:text-white"
popup_class="mt-1 w-64 md:w-80 max-w-[var(--available-width)] origin-[var(--transform-origin)] border border-neutral-950 bg-white text-neutral-950 shadow-[0.25rem_0.25rem_0_rgb(0_0_0_/_12%)] max-h-[22.5rem] overflow-y-auto overscroll-contain py-1 scroll-pt-1 scroll-pb-1 dark:border-white dark:bg-neutral-950 dark:text-white dark:shadow-none data-[closed]:hidden"
item_class="group/item grid cursor-default grid-cols-[1rem_1fr] items-start gap-2 px-2 py-2 text-sm leading-[1.2rem] outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-neutral-950 data-[highlighted]:before:absolute data-[highlighted]:before:inset-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:bg-neutral-100 dark:data-[highlighted]:text-white dark:data-[highlighted]:before:bg-neutral-800 data-[hidden]:hidden"
indicator_class="col-start-1 mt-1 invisible group-data-[selected]/item:visible"
>
<:trigger_icon>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="display:block">
<path d="M12 6H4l4 4.5z" />
</svg>
</:trigger_icon>
<:clear_icon>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-linecap="square"
stroke-linejoin="round"
style="display:block"
>
<path d="m4.5 4.5 7 7m-7 0 7-7" />
</svg>
</:clear_icon>
<:item_indicator>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
style="display:block"
>
<path d="m2.5 8.5 4 4 7-9" />
</svg>
</:item_indicator>
<:option
:for={
u <- [
%{
id: "leslie-alexander",
name: "Leslie Alexander",
username: "leslie",
email: "[email protected]",
title: "Product Manager"
},
%{
id: "kathryn-murphy",
name: "Kathryn Murphy",
username: "kathryn",
email: "[email protected]",
title: "Marketing Lead"
},
%{
id: "courtney-henry",
name: "Courtney Henry",
username: "courtney",
email: "[email protected]",
title: "Design Systems"
},
%{
id: "michael-foster",
name: "Michael Foster",
username: "michael",
email: "[email protected]",
title: "Engineering Manager"
},
%{
id: "lindsay-walton",
name: "Lindsay Walton",
username: "lindsay",
email: "[email protected]",
title: "Product Designer"
}
]
}
value={u.id}
>
<span class="col-start-2 flex flex-col gap-1">
<span class="text-sm leading-5 font-bold">{u.name}</span>
<span class="text-xs">{u.email}</span>
<span class="flex flex-wrap gap-2 text-xs text-neutral-500 dark:text-neutral-400">
<span>@{u.username}</span>
<span>{u.title}</span>
</span>
</span>
</:option>
<:empty>Try a different search term.</:empty>
</.combobox>Base UI Combobox example - async multi-select (Phoenix headless)
Base UI Combobox example - async multi-select (Phoenix headless)
- Leslie Alexander[email protected]@leslieProduct Manager
- Kathryn Murphy[email protected]@kathrynMarketing Lead
- Courtney Henry[email protected]@courtneyDesign Systems
- Michael Foster[email protected]@michaelEngineering Manager
- Lindsay Walton[email protected]@lindsayProduct Designer
- Try a different search term.
<.combobox
id="baseui-combobox-async-multiple"
multiple
placeholder="e.g. Michael"
control_class="flex min-h-8 w-64 cursor-text flex-wrap items-center gap-0.5 border border-neutral-950 bg-white dark:bg-neutral-950 px-2 py-1 focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-neutral-950 dark:focus-within:outline-white has-[button]:px-1 dark:border-white min-[32rem]:w-[22rem]"
chips_class="flex w-full flex-wrap items-center gap-1"
chip_class="group flex min-h-[calc(1.5rem-2px)] cursor-default items-center gap-1 overflow-hidden bg-neutral-100 py-0 pr-[0.2rem] pl-[0.4rem] text-sm leading-none text-neutral-950 outline-none focus-within:bg-neutral-950 focus-within:text-white dark:bg-neutral-800 dark:text-white dark:focus-within:bg-white dark:focus-within:text-neutral-950"
chip_remove_class="flex size-4 items-center justify-center border-0 bg-transparent p-0 text-inherit hover:bg-neutral-200 group-focus-within:hover:bg-neutral-700 dark:hover:bg-neutral-700 dark:group-focus-within:hover:bg-neutral-200"
input_class="h-[calc(1.5rem-2px)] min-w-12 flex-1 border-0 bg-white p-0 text-sm dark:bg-neutral-950 font-normal text-neutral-950 outline-none placeholder:text-neutral-500 dark:placeholder:text-neutral-400 dark:text-white"
popup_class="mt-1 w-64 max-w-[var(--available-width)] origin-[var(--transform-origin)] border border-neutral-950 bg-white text-neutral-950 shadow-[0.25rem_0.25rem_0_rgb(0_0_0_/_12%)] max-h-[24.5rem] overflow-y-auto overscroll-contain py-1 scroll-pt-1 scroll-pb-1 dark:border-white dark:bg-neutral-950 dark:text-white dark:shadow-none data-[closed]:hidden"
item_class="group/item grid cursor-default grid-cols-[1rem_1fr] items-start gap-2 px-2 py-2 text-sm leading-[1.2rem] outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-neutral-950 data-[highlighted]:before:absolute data-[highlighted]:before:inset-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:bg-neutral-100 dark:data-[highlighted]:text-white dark:data-[highlighted]:before:bg-neutral-800 data-[hidden]:hidden"
indicator_class="col-start-1 mt-1 invisible group-data-[selected]/item:visible"
>
<:chip_remove_icon>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-linecap="square"
stroke-linejoin="round"
style="display:block"
>
<path d="m4.5 4.5 7 7m-7 0 7-7" />
</svg>
</:chip_remove_icon>
<:item_indicator>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
style="display:block"
>
<path d="m2.5 8.5 4 4 7-9" />
</svg>
</:item_indicator>
<:option
:for={
u <- [
%{
id: "leslie-alexander",
name: "Leslie Alexander",
username: "leslie",
email: "[email protected]",
title: "Product Manager"
},
%{
id: "kathryn-murphy",
name: "Kathryn Murphy",
username: "kathryn",
email: "[email protected]",
title: "Marketing Lead"
},
%{
id: "courtney-henry",
name: "Courtney Henry",
username: "courtney",
email: "[email protected]",
title: "Design Systems"
},
%{
id: "michael-foster",
name: "Michael Foster",
username: "michael",
email: "[email protected]",
title: "Engineering Manager"
},
%{
id: "lindsay-walton",
name: "Lindsay Walton",
username: "lindsay",
email: "[email protected]",
title: "Product Designer"
}
]
}
value={u.id}
>
<span class="col-start-2 flex flex-col gap-1">
<span class="text-sm leading-5 font-bold">{u.name}</span>
<span class="text-xs">{u.email}</span>
<span class="flex flex-wrap gap-2 text-xs text-neutral-500 dark:text-neutral-400">
<span>@{u.username}</span>
<span>{u.title}</span>
</span>
</span>
</:option>
<:empty>Try a different search term.</:empty>
</.combobox>Parts & data attributes
Each part carries a stable data-part and a structural chelekom-combobox__* class you can target, plus state attributes for filtering, highlighting, and selection.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Input | input | role="combobox", aria-controls, aria-expanded, aria-autocomplete, aria-activedescendant | The text field the user types into to filter options. Takes input_class. |
| Trigger | trigger | - | Optional button that opens/closes the popup without typing. Takes trigger_class. |
| Clear | clear | data-hidden | Optional button that clears the query/selection. Takes clear_class. |
| Chips / Chip | chips / chip | data-chip-value | Multiple-mode: the container of selected chips, and each removable chip (with a nested chip-remove button). Take chips_class, chip_class and chip_remove_class. |
| Value | value | - | Hidden form input for a single selection; multiple submits through each chip's name[]. |
| Popup | popup | role="listbox", data-open, data-closed | The listbox that shows the filtered options. Takes popup_class. |
| Group / Group label | group / group-label | role="group", aria-labelledby, data-hidden | Wraps consecutive options that share the same group. Take group_class and group_label_class. |
| Group list | (no data-part) | role="presentation" | The <ul> that holds a group's options. Takes group_list_class. |
| Item | item | role="option", data-value, data-highlighted, data-hidden, data-selected, data-disabled, aria-selected, aria-disabled | A single selectable option. Takes item_class on the root and class on an option slot. |
| Indicator | indicator | - | The selected checkmark, shown when the item is data-selected. Takes indicator_class. |
| Empty | empty | data-hidden | Shown when the query matches no options. Takes empty_class. |
| Create | create | data-hidden | Creatable mode: offers to create the typed query as a new option. Takes create_class. |
Accessibility
Follows the WAI-ARIA Combobox pattern: Down/Up open the popup and navigate the options, skipping disabled and filtered-out ones; Enter selects the highlighted option (or creates it, when creatable is set); Escape closes the popup; and typing filters the list live. The input keeps aria-activedescendant pointed at the highlighted option so screen readers announce it as focus moves.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | Unique id (anchors the popup and option ids) |
name
|
string
| — | — | No | Form name (multiple submits as `name[]`) |
value
|
any
| — | — | No | Selected value (a string, or a list when `multiple`) |
placeholder
|
string
| — | — | No | Input placeholder text |
multiple
|
boolean
|
false
| — | No | Allow selecting several options (renders chips) |
auto_highlight
|
boolean
|
false
| — | No | Highlight the first match as you type |
filter
|
string
|
contains
|
containsstarts_with
| No | Match mode for the typed query |
clear
|
boolean
|
false
| — | No | Render a clear button |
trigger
|
boolean
|
false
| — | No | Render a button that opens/closes the popup |
creatable
|
boolean
|
false
| — | No | Offer to create the typed query as a new option |
on_create
|
string
| — | — | No | LiveView event pushed to create ({value}) |
on_change
|
string
| — | — | No | LiveView event pushed on selection ({value}) |
class
|
any
| — | — | No | — |
control_class
|
any
| — | — | No | Class for the `control` part |
input_class
|
any
| — | — | No | Class for the `input` part |
clear_class
|
any
| — | — | No | Class for the `clear` button part |
trigger_class
|
any
| — | — | No | Class for the `trigger` button part |
chips_class
|
any
| — | — | No | Class for the `chips` part |
chip_class
|
any
| — | — | No | Class for each `chip` part |
chip_remove_class
|
any
| — | — | No | Class for each `chip-remove` part |
popup_class
|
any
| — | — | No | Class for the `popup` (listbox) part |
group_class
|
any
| — | — | No | Class for each `group` part |
group_label_class
|
any
| — | — | No | Class for each `group-label` part |
item_class
|
any
| — | — | No | Class for each `item` part |
indicator_class
|
any
| — | — | No | Class for each `indicator` part |
empty_class
|
any
| — | — | No | Class for the `empty` part |
create_class
|
any
| — | — | No | Class for the `create` part |
create_label_class
|
any
| — | — | No | Class for the `create-label` text wrapper (only used when a `create_icon` is given) |
group_list_class
|
any
| — | — | No | — |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
option
| Yes | — |
empty
| No | Shown when the query matches no options |
clear_icon
| No | Icon rendered inside the `clear` button (defaults to ×) |
trigger_icon
| No | Icon rendered inside the `trigger` button (defaults to ▾) |
chip_remove_icon
| No | Icon rendered inside each `chip-remove` button (defaults to ×) |
create_icon
| No | Icon rendered before the `create` label |
item_indicator
| No | Icon (e.g. a checkmark) rendered inside each option's `indicator` part |
Attributes of the option slot
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
value
|
string
| — | — | Yes | — |
disabled
|
boolean
| — | — | No | — |
group
|
string
| — | — | No | Optional group label (consecutive same-group options are grouped) |
class
|
any
| — | — | No | Per-option class merged onto the `item` part |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
combobox
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless combobox