Skip to main content
Glama
combobox-usage.md14.6 kB
# Combobox Component Usage ## Overview The combobox component combines a text input with a dropdown list, allowing users to filter and select from a list of options. It's ideal for large datasets where users need to search for specific items. The component uses JavaScript for filtering, keyboard navigation, and selection. ## JavaScript Requirements ### Required Scripts ```html <!-- Include Basecoat CSS and JavaScript --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/basecoat.cdn.min.css"> <script src="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/js/basecoat.min.js" defer></script> <script src="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/js/combobox.min.js" defer></script> ``` ## HTML Structure ### Basic Combobox ```html <div class="combobox"> <button type="button" class="btn-outline w-full justify-between" role="combobox" aria-expanded="false" aria-haspopup="listbox" aria-controls="framework-listbox" > <span data-value>Select framework...</span> <svg class="size-4 shrink-0 opacity-50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m7 15 5 5 5-5"></path> <path d="m7 9 5-5 5 5"></path> </svg> </button> <div role="listbox" id="framework-listbox" aria-label="Frameworks"> <header> <input type="text" placeholder="Search framework..." aria-label="Search frameworks"> </header> <div data-empty class="hidden">No framework found.</div> <div role="option" data-value="react">React</div> <div role="option" data-value="vue">Vue</div> <div role="option" data-value="angular">Angular</div> <div role="option" data-value="svelte">Svelte</div> <div role="option" data-value="solid">Solid</div> </div> </div> ``` ### Combobox with Icons ```html <div class="combobox"> <button type="button" class="btn-outline w-full justify-between" role="combobox" aria-expanded="false" aria-haspopup="listbox" > <span data-value>Select status...</span> <svg class="size-4 shrink-0 opacity-50"><!-- chevrons-up-down --></svg> </button> <div role="listbox" aria-label="Status options"> <header> <input type="text" placeholder="Search status..." aria-label="Search status"> </header> <div data-empty class="hidden">No status found.</div> <div role="option" data-value="backlog"> <svg class="size-4 text-muted-foreground"><!-- circle icon --></svg> Backlog </div> <div role="option" data-value="todo"> <svg class="size-4 text-blue-500"><!-- circle icon --></svg> Todo </div> <div role="option" data-value="in-progress"> <svg class="size-4 text-yellow-500"><!-- timer icon --></svg> In Progress </div> <div role="option" data-value="done"> <svg class="size-4 text-green-500"><!-- check-circle icon --></svg> Done </div> <div role="option" data-value="cancelled"> <svg class="size-4 text-red-500"><!-- x-circle icon --></svg> Cancelled </div> </div> </div> ``` ## Component Elements ### Trigger Button ```html <button type="button" class="btn-outline w-full justify-between" role="combobox" aria-expanded="false" aria-haspopup="listbox" aria-controls="listbox-id" > <span data-value>Placeholder text...</span> <svg class="size-4"><!-- chevrons-up-down icon --></svg> </button> ``` - `role="combobox"`: Indicates the element is a combobox - `aria-expanded`: Tracks the open/closed state - `aria-haspopup="listbox"`: Indicates a listbox popup - `aria-controls`: References the listbox ID - `data-value`: Displays the selected value ### Listbox Container ```html <div role="listbox" id="listbox-id" aria-label="Options"> <header> <input type="text" placeholder="Search..." aria-label="Search options"> </header> <div data-empty class="hidden">No results found.</div> <!-- Options --> </div> ``` ### Option Items ```html <div role="option" data-value="value" aria-selected="false"> Option Label </div> <!-- Selected option --> <div role="option" data-value="selected" aria-selected="true"> <svg class="size-4"><!-- check icon --></svg> Selected Option </div> <!-- Disabled option --> <div role="option" data-value="disabled" aria-disabled="true"> Disabled Option </div> ``` ## Implementation Examples ### Country Selector ```html <div class="combobox"> <button type="button" class="btn-outline w-[280px] justify-between" role="combobox" aria-expanded="false" aria-haspopup="listbox" > <span data-value>Select country...</span> <svg class="size-4 shrink-0 opacity-50"><!-- chevrons-up-down --></svg> </button> <div role="listbox" aria-label="Countries" class="max-h-[300px] overflow-auto"> <header> <input type="text" placeholder="Search country..." aria-label="Search countries"> </header> <div data-empty class="hidden">No country found.</div> <div role="group" aria-label="Popular"> <span class="px-2 py-1.5 text-xs font-medium text-muted-foreground">Popular</span> <div role="option" data-value="us">🇺🇸 United States</div> <div role="option" data-value="uk">🇬🇧 United Kingdom</div> <div role="option" data-value="ca">🇨🇦 Canada</div> </div> <hr role="separator"> <div role="group" aria-label="All countries"> <span class="px-2 py-1.5 text-xs font-medium text-muted-foreground">All Countries</span> <div role="option" data-value="af">🇦🇫 Afghanistan</div> <div role="option" data-value="al">🇦🇱 Albania</div> <div role="option" data-value="dz">🇩🇿 Algeria</div> <!-- More countries... --> </div> </div> </div> ``` ### Assignee Selector ```html <div class="combobox"> <button type="button" class="btn-outline w-[250px] justify-between" role="combobox" aria-expanded="false" aria-haspopup="listbox" > <span data-value class="flex items-center gap-2"> <svg class="size-4 text-muted-foreground"><!-- user icon --></svg> Assign to... </span> <svg class="size-4 shrink-0 opacity-50"><!-- chevrons-up-down --></svg> </button> <div role="listbox" aria-label="Team members"> <header> <input type="text" placeholder="Search team member..." aria-label="Search team members"> </header> <div data-empty class="hidden">No team member found.</div> <div role="option" data-value="john"> <img src="/avatars/john.png" alt="" class="size-6 rounded-full"> <div class="flex-1"> <p class="text-sm font-medium">John Doe</p> <p class="text-xs text-muted-foreground">john@example.com</p> </div> </div> <div role="option" data-value="jane"> <img src="/avatars/jane.png" alt="" class="size-6 rounded-full"> <div class="flex-1"> <p class="text-sm font-medium">Jane Smith</p> <p class="text-xs text-muted-foreground">jane@example.com</p> </div> </div> <div role="option" data-value="bob"> <img src="/avatars/bob.png" alt="" class="size-6 rounded-full"> <div class="flex-1"> <p class="text-sm font-medium">Bob Johnson</p> <p class="text-xs text-muted-foreground">bob@example.com</p> </div> </div> </div> </div> ``` ### Language Selector ```html <div class="combobox"> <button type="button" class="btn-outline justify-between" role="combobox" aria-expanded="false" aria-haspopup="listbox" > <span data-value>English</span> <svg class="size-4 shrink-0 opacity-50"><!-- chevrons-up-down --></svg> </button> <div role="listbox" aria-label="Languages"> <header> <input type="text" placeholder="Search language..." aria-label="Search languages"> </header> <div data-empty class="hidden">No language found.</div> <div role="option" data-value="en" aria-selected="true"> <svg class="size-4"><!-- check icon --></svg> English </div> <div role="option" data-value="es"> <svg class="size-4 invisible"><!-- placeholder --></svg> Spanish </div> <div role="option" data-value="fr"> <svg class="size-4 invisible"><!-- placeholder --></svg> French </div> <div role="option" data-value="de"> <svg class="size-4 invisible"><!-- placeholder --></svg> German </div> <div role="option" data-value="ja"> <svg class="size-4 invisible"><!-- placeholder --></svg> Japanese </div> <div role="option" data-value="zh"> <svg class="size-4 invisible"><!-- placeholder --></svg> Chinese </div> </div> </div> ``` ### Timezone Selector with Filtering ```html <div class="combobox"> <button type="button" class="btn-outline w-[300px] justify-between" role="combobox" aria-expanded="false" aria-haspopup="listbox" > <span data-value>Select timezone...</span> <svg class="size-4 shrink-0 opacity-50"><!-- chevrons-up-down --></svg> </button> <div role="listbox" aria-label="Timezones" class="max-h-[300px] overflow-auto"> <header> <input type="text" placeholder="Search timezone..." aria-label="Search timezones"> </header> <div data-empty class="hidden">No timezone found.</div> <div role="option" data-value="utc" data-keywords="UTC Coordinated Universal Time"> (UTC+00:00) Coordinated Universal Time </div> <div role="option" data-value="est" data-keywords="EST Eastern Standard New York"> (UTC-05:00) Eastern Time (US & Canada) </div> <div role="option" data-value="pst" data-keywords="PST Pacific Standard Los Angeles"> (UTC-08:00) Pacific Time (US & Canada) </div> <div role="option" data-value="gmt" data-keywords="GMT Greenwich Mean London"> (UTC+00:00) Greenwich Mean Time </div> <div role="option" data-value="cet" data-keywords="CET Central European Paris Berlin"> (UTC+01:00) Central European Time </div> <div role="option" data-value="jst" data-keywords="JST Japan Standard Tokyo"> (UTC+09:00) Japan Standard Time </div> </div> </div> ``` ## JavaScript Events ### Selection Event ```javascript const combobox = document.querySelector('.combobox'); combobox.addEventListener('change', function(e) { const selectedValue = e.detail.value; const selectedLabel = e.detail.label; console.log('Selected:', selectedValue, selectedLabel); }); ``` ### Filtering The component automatically filters options based on: - Option text content - `data-value` attribute - `data-keywords` attribute (for additional search terms) ```html <!-- Option searchable by "USA", "United States", or "America" --> <div role="option" data-value="us" data-keywords="USA America"> United States </div> ``` ### Programmatic Control ```javascript // Get selected value function getSelectedValue(comboboxElement) { const selected = comboboxElement.querySelector('[role="option"][aria-selected="true"]'); return selected ? selected.getAttribute('data-value') : null; } // Set selected value programmatically function setSelectedValue(comboboxElement, value) { const options = comboboxElement.querySelectorAll('[role="option"]'); options.forEach(option => { const isSelected = option.getAttribute('data-value') === value; option.setAttribute('aria-selected', isSelected); }); const selectedOption = comboboxElement.querySelector(`[data-value="${value}"]`); if (selectedOption) { const trigger = comboboxElement.querySelector('[role="combobox"] [data-value]'); trigger.textContent = selectedOption.textContent.trim(); } } ``` ## Accessibility Guidelines ### Keyboard Navigation - **Arrow Down**: Open menu, move to next option - **Arrow Up**: Move to previous option - **Enter**: Select focused option - **Escape**: Close menu - **Home**: Move to first option - **End**: Move to last option - **Type characters**: Filter options ### Screen Reader Support ```html <div class="combobox"> <label id="framework-label" class="label mb-2">Framework</label> <button type="button" role="combobox" aria-labelledby="framework-label" aria-expanded="false" aria-haspopup="listbox" aria-controls="framework-list" > <span data-value>Select...</span> </button> <div role="listbox" id="framework-list" aria-labelledby="framework-label"> <!-- options --> </div> </div> ``` ## Best Practices ### When to Use Combobox - Large lists that need filtering - User/assignee selection - Location/timezone selection - Tag/category selection - Any searchable dropdown ### When to Use Alternatives - Small lists (5-7 items): Use Select - Multiple selection: Use Combobox with multi-select or Checkbox group - Free-form input: Use Input with autocomplete ### Performance - For very large lists (100+ items), consider virtual scrolling - Debounce filter input for API-backed searches - Lazy load options when possible ### UX Guidelines - Always show placeholder text - Provide clear empty state messages - Show selected option clearly - Support keyboard navigation - Consider mobile touch targets ## Form Integration ### Within a Form ```html <form class="form space-y-4"> <div class="grid gap-2"> <label for="framework-combo" class="label">Framework</label> <div class="combobox"> <button type="button" id="framework-combo" class="btn-outline w-full justify-between" role="combobox" aria-expanded="false" > <span data-value>Select framework...</span> <svg class="size-4 opacity-50"><!-- icon --></svg> </button> <input type="hidden" name="framework" value=""> <div role="listbox" aria-label="Frameworks"> <!-- options --> </div> </div> </div> <button type="submit" class="btn">Submit</button> </form> ``` ### Hidden Input for Form Submission ```javascript combobox.addEventListener('change', function(e) { const hiddenInput = this.querySelector('input[type="hidden"]'); if (hiddenInput) { hiddenInput.value = e.detail.value; } }); ``` ## Jinja/Nunjucks Macro ### Using the Macro ```jinja {% from "combobox.njk" import combobox %} {% set options = [ { value: "react", label: "React" }, { value: "vue", label: "Vue" }, { value: "angular", label: "Angular" }, { value: "svelte", label: "Svelte" } ] %} {{ combobox( id="framework-select", placeholder="Select framework...", search_placeholder="Search framework...", empty_text="No framework found.", options=options ) }} ```

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GustavoGomezPG/basecoat-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server