Combobox

A combobox is a combination of a text input and a dropdown list. It allows users to select an option from the list or enter a custom value.

WAI-ARIA: Combobox Pattern

Features

Autocomplete

If enabled, the input will automatically complete the value with the first option that matches the user’s input.

Keyboard navigation

  • Enter on an option selects it.
  • Arrow Up or Arrow Down navigates between options.
  • Home or End navigates to the first or last option.

Example

Sources

+page.svelte

<script lang="ts">
  import Combobox from './Combobox.svelte'

  const searchOptions = [
    'Black',
    'Blue',
    'Brown',
    'Green',
    'Orange',
    'Purple',
    'Red',
    'White',
    'Yellow',
  ]
</script>

<Combobox
  options={searchOptions}
  label="Favourite color"
  placeholder="Enter the name of your favourite color..."
  autocomplete
/>

Combobox.svelte

<script lang="ts">
  import { createCombobox } from 'louisette'

  export let label: string
  export let placeholder: string = ''
  export let options: string[] = []
  export let autocomplete: boolean = false

  const {
    comboboxAttrs,
    inputAttrs,
    buttonAttrs,
    listboxAttrs,
    optionAttrs,
    opened,
    activeDescendant,
  } = createCombobox({ autocomplete })
</script>

<div {...$comboboxAttrs} class="relative min-w-[1rem]">
  <div class="flex gap-1">
    <input
      type="text"
      {...$inputAttrs}
      {placeholder}
      aria-label={label}
      class="w-full rounded bg-white px-4 py-2 shadow dark:bg-neutral-700 dark:text-neutral-100"
    />
    <button
      class="ml-auto flex place-items-center rounded bg-white p-2 shadow dark:bg-neutral-700 dark:text-neutral-100"
      {...$buttonAttrs}
    >
      <span class:rotate-180={$opened}>
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
          class="h-4 w-4"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M19.5 8.25l-7.5 7.5-7.5-7.5"
          />
        </svg>
      </span>
    </button>
  </div>

  <div
    {...$listboxAttrs}
    class:hidden={!$opened}
    class="absolute z-10 mt-1 w-full space-y-2 rounded-b bg-white p-2 text-sm shadow-lg dark:bg-neutral-700 dark:text-neutral-100"
  >
    {#each options as option}
      <div
        {...$optionAttrs(option)}
        class="flex cursor-pointer items-center justify-between rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-600"
        class:is-active-descendant={$activeDescendant === option}
      >
        {option}
      </div>
    {/each}
  </div>
</div>

<style lang="postcss">
  .is-active-descendant {
    @apply bg-neutral-200 dark:bg-neutral-600;
  }
</style>