Passer au contenu principal

CSS-only Tailwind CSS combobox with live search filtering and accessible dropdown list. Keyboard navigation, color variants. WCAG AA accessible, works with Django, HTMX, Laravel, React, and any stack.

Combobox combine une saisie de texte avec une liste déroulante. Utilisez le composant input pour le champ : les variantes de taille, de couleur et d'état proviennent de là. Les utilitaires combobox-* gèrent uniquement la liste déroulante, les éléments et l'état ouvert.

CSS uniquement, aucun JavaScript requis. WCAG AA accessible et indépendant du framework – fonctionne avec Django, HTMX, Laravel, React et n'importe quelle pile.

Classe Taper Description
combobox-listBaseConteneur de liste déroulante pour les options de liste déroulante
combobox-itemModificateurOption individuelle dans la liste déroulante
combobox-openModificateurAffiche la liste déroulante lorsqu'elle est appliquée au wrapper
combobox-primaryCouleurPrimary theme color highlight for items
combobox-secondaryCouleurSecondary theme color highlight for items
combobox-accentCouleurAccent theme color highlight for items
combobox-infoCouleurInfo semantic color highlight
combobox-successCouleurSuccess semantic color highlight
combobox-warningCouleurWarning semantic color highlight
combobox-errorCouleurError semantic color highlight

Utilisation de base

Wrap an input and a combobox-list in a relative container. Add combobox-open to the wrapper to show the list — toggle it via JS or :focus-within.

  • React
  • Vue
  • Angular
  • Svelte
1
2
3
4
5
6
7
8
9
<div class="relative w-64 combobox-open">
  <input type="text" class="input w-full" placeholder="Search or select...">
  <ul class="combobox-list">
    <li class="combobox-item">React</li>
    <li class="combobox-item combobox-item-active">Vue</li>
    <li class="combobox-item">Angular</li>
    <li class="combobox-item">Svelte</li>
  </ul>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { useCombobox } from 'frutjam/react'

const frameworks = ['React', 'Vue', 'Angular', 'Svelte']

export default function ComboboxDemo() {
  const combo = useCombobox({ items: frameworks })
  return (
    <div {...combo.containerProps} className="relative w-64">
      <input {...combo.inputProps} className="input w-full" placeholder="Search or select..." />
      <ul className="combobox-list" role="listbox">
        {combo.filtered.map((item, i) => (
          <li key={item} {...combo.optionProps(item, i)} className="combobox-item">{item}</li>
        ))}
      </ul>
    </div>
  )
}

Open on Focus

Use focus-within:combobox-open on the wrapper to open the list when the input is focused — no JavaScript needed.

  • React
  • Vue
  • Angular
  • Svelte
html
1
2
3
4
5
6
7
8
9
<div class="relative w-64 focus-within:combobox-open">
  <input type="text" class="input w-full" placeholder="Focus to open...">
  <ul class="combobox-list">
    <li class="combobox-item">React</li>
    <li class="combobox-item">Vue</li>
    <li class="combobox-item">Angular</li>
    <li class="combobox-item">Svelte</li>
  </ul>
</div>

Sizes

Use input-sm, input-lg, etc. on the input field. The dropdown adapts naturally.

  • Banana
  • Mango
  • Papaya
  • Banana
  • Mango
  • Papaya
html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<div class="relative w-64 combobox-open">
  <input type="text" class="input input-sm w-full" placeholder="Small">
  <ul class="combobox-list">
    <li class="combobox-item">Banana</li>
    <li class="combobox-item combobox-item-active">Mango</li>
    <li class="combobox-item">Papaya</li>
  </ul>
</div>
<div class="relative w-64 combobox-open">
  <input type="text" class="input input-lg w-full" placeholder="Large">
  <ul class="combobox-list">
    <li class="combobox-item">Banana</li>
    <li class="combobox-item combobox-item-active">Mango</li>
    <li class="combobox-item">Papaya</li>
  </ul>
</div>

Colors

Use input-primary etc. on the input for the field color. Add the matching combobox-primary to the wrapper to tint the active item highlight.

  • React
  • Vue
  • Angular
  • React
  • Vue
  • Angular
html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<div class="relative w-64 combobox-open combobox-primary">
  <input type="text" class="input input-primary w-full" placeholder="Primary">
  <ul class="combobox-list">
    <li class="combobox-item">React</li>
    <li class="combobox-item combobox-item-active">Vue</li>
    <li class="combobox-item">Angular</li>
  </ul>
</div>
<div class="relative w-64 combobox-open combobox-error">
  <input type="text" class="input input-error w-full" placeholder="Error">
  <ul class="combobox-list">
    <li class="combobox-item">React</li>
    <li class="combobox-item combobox-item-active">Vue</li>
    <li class="combobox-item">Angular</li>
  </ul>
</div>

Disabled

html
1
2
3
<div class="relative w-64">
  <input type="text" class="input w-full" placeholder="Disabled" disabled>
</div>

With Button

Compose with join to attach a search icon or trigger button.

🔍
  • React
  • Vue
  • Angular
  • Svelte
html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<div class="relative w-64 combobox-open">
  <div class="join w-full">
    <span class="join-item btn btn-soft">🔍</span>
    <input type="text" class="input join-item" placeholder="Search...">
  </div>
  <ul class="combobox-list">
    <li class="combobox-item">React</li>
    <li class="combobox-item combobox-item-active">Vue</li>
    <li class="combobox-item">Angular</li>
    <li class="combobox-item">Svelte</li>
  </ul>
</div>

combobox-item works on any element — <li>, <a>, or <button>.

html
1
2
3
4
5
6
7
8
<div class="relative w-64 combobox-open">
  <input type="text" class="input w-full" placeholder="Search pages...">
  <ul class="combobox-list">
    <a href="#" class="combobox-item">Home</a>
    <a href="#" class="combobox-item combobox-item-active">About</a>
    <button class="combobox-item">Contact</button>
  </ul>
</div>

Aide JS et React

Use createCombobox from frutjam/js or useCombobox from frutjam/react to get filtering, keyboard navigation (↑ ↓ Enter Escape), and full aria-* wiring out of the box. Listen for the fj:select event to handle selection.

  • React
  • Vue
  • Angular
  • Svelte
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<div class="relative w-64" id="js-combobox">
  <input type="text" class="input w-full" placeholder="Search frameworks...">
  <ul class="combobox-list">
    <li class="combobox-option" data-value="react">React</li>
    <li class="combobox-option" data-value="vue">Vue</li>
    <li class="combobox-option" data-value="angular">Angular</li>
    <li class="combobox-option" data-value="svelte">Svelte</li>
  </ul>
</div>
<script type="module">
  import { createCombobox } from 'frutjam/js'
  const combo = createCombobox(document.getElementById('js-combobox'))
  document.getElementById('js-combobox').addEventListener('fj:select', (e) => {
    console.log('Selected:', e.detail.value)
  })
</script>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { useCombobox } from 'frutjam/react'

const frameworks = [
  { label: 'React', value: 'react' },
  { label: 'Vue', value: 'vue' },
  { label: 'Angular', value: 'angular' },
  { label: 'Svelte', value: 'svelte' },
]

export default function ComboboxDemo() {
  const combo = useCombobox({ items: frameworks })
  return (
    <div {...combo.containerProps} className="relative w-64">
      <input {...combo.inputProps} className="input w-full" placeholder="Search frameworks..." />
      <ul {...combo.listboxProps} className="combobox-list">
        {combo.filtered.map((item, i) => (
          <li key={item.value} {...combo.optionProps(item, i)}>{item.label}</li>
        ))}
      </ul>
    </div>
  )
}