Combobox

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJSApache-2.0
Installation mix mishka.ui.gen.headless combobox
Source View Source

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
heex
<.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
  • Vegetables
heex
<.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
heex
<.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
heex
<.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
  • Create “”
heex
<.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)

heex
<.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)

heex
<.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.

Partdata-partState / ARIAPurpose
Inputinputrole="combobox", aria-controls, aria-expanded, aria-autocomplete, aria-activedescendantThe text field the user types into to filter options. Takes input_class.
Triggertrigger-Optional button that opens/closes the popup without typing. Takes trigger_class.
Clearcleardata-hiddenOptional button that clears the query/selection. Takes clear_class.
Chips / Chipchips / chipdata-chip-valueMultiple-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.
Valuevalue-Hidden form input for a single selection; multiple submits through each chip's name[].
Popuppopuprole="listbox", data-open, data-closedThe listbox that shows the filtered options. Takes popup_class.
Group / Group labelgroup / group-labelrole="group", aria-labelledby, data-hiddenWraps 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.
Itemitemrole="option", data-value, data-highlighted, data-hidden, data-selected, data-disabled, aria-selected, aria-disabledA single selectable option. Takes item_class on the root and class on an option slot.
Indicatorindicator-The selected checkmark, shown when the item is data-selected. Takes indicator_class.
Emptyemptydata-hiddenShown when the query matches no options. Takes empty_class.
Createcreatedata-hiddenCreatable 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.