Next.js
SvelteKit
Nuxt.js
Remix
Astro
<div class="select">
<button type="button" class="btn-outline justify-between font-normal w-[200px]" id="select-527827-trigger" popovertarget="select-527827" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-527827-listbox">
<span class="truncate"></span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down text-muted-foreground opacity-50 shrink-0">
<path d="m7 15 5 5 5-5" />
<path d="m7 9 5-5 5 5" />
</svg>
</button>
<div popover id="select-527827" class="popover">
<header>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
<input type="text" value="" placeholder="Search framework..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="select-527827-content" aria-labelledby="select-527827-trigger" />
</header>
<div role="listbox" id="select-527827-listbox" aria-orientation="vertical" aria-labelledby="select-527827-trigger" data-empty="No framework found.">
<div role="option" data-value="Next.js">Next.js</div>
<div role="option" data-value="SvelteKit">SvelteKit</div>
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
<div role="option" data-value="Remix">Remix</div>
<div role="option" data-value="Astro">Astro</div>
</div>
</div>
<input type="hidden" name="select-527827-value" value="" />
</div>
Usage
HTML + JavaScript
Step 1: Include the JavaScript files
Include the select.js
file. It is recommended to also include the CSS anchor positioning polyfill to support older browsers. Add this to the <head>
of your page:
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@beta/dist/js/select.min.js" defer></script>
<script type="module">
if (!("anchorName" in document.documentElement.style)) {
import("https://cdn.jsdelivr.net/npm/basecoat-css@beta/dist/js/css-anchor-positioning.min.js");
}
</script>
Step 2: Add your combobox HTML
Combobox uses the same markup and JavaScript as the Select component, the only difference being the search box at the top of the listbox:
<div class="select">
<button type="button" class="btn-outline justify-between font-normal w-[200px]" id="select-527827-trigger" popovertarget="select-527827" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-527827-listbox">
<span class="truncate"></span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down text-muted-foreground opacity-50 shrink-0">
<path d="m7 15 5 5 5-5" />
<path d="m7 9 5-5 5 5" />
</svg>
</button>
<div popover id="select-527827" class="popover">
<header>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
<input type="text" value="" placeholder="Search framework..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="select-527827-content" aria-labelledby="select-527827-trigger" />
</header>
<div role="listbox" id="select-527827-listbox" aria-orientation="vertical" aria-labelledby="select-527827-trigger" data-empty="No framework found.">
<div role="option" data-value="Next.js">Next.js</div>
<div role="option" data-value="SvelteKit">SvelteKit</div>
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
<div role="option" data-value="Remix">Remix</div>
<div role="option" data-value="Astro">Astro</div>
</div>
</div>
<input type="hidden" name="select-527827-value" value="" />
</div>
HTML structure
<div class="select">
- Wraps around the entire component.
<button type="button" popovertarget="{ POPOVER_ID }">
-
The trigger to open the popover, can also have the following attributes:
id="{BUTTON_ID}"
: linked to by thearia-labelledby
attribute of the listbox.aria-haspopup="listbox"
: indicates that the button opens a listbox.aria-controls="{ LISTBOX_ID }"
: points to the listbox's id.aria-expanded="false"
: tracks the popover's state.aria-activedescendant="{ OPTION_ID }"
: points to the active option's id.
<div popover class="popover" id="{ POPOVER_ID }">
- As with the Popover component, you can set up the side and alignment of the popover using the
data-side
anddata-align
attributes.<header>
- Contains the search input to filter the options in the listbox.
<div role="listbox">
- The listbox containing the options. Should have the following attributes:
id="{ LISTBOX_ID }"
: refered to by thearia-controls
attribute of the trigger.aria-labelledby="{ BUTTON_ID }"
: linked to by the button'sid
attribute.
<div role="option" data-value="{ VALUE }">
- Option that can be selected.Should have a unique id if you use the
aria-activedescendant
attribute on the trigger. <hr role="separator">
Optional- Separator between groups/options.
<div role="group">
Optional- Group of options, can have a
aria-labelledby
attribute to link to a heading. <span role="heading">
- Group heading, must have an
id
attribute if you use thearia-labelledby
attribute on the group.
<input type="hidden" name="{ NAME }" value="{ VALUE }">
Optional- The hidden input that holds the value of the field (if needed).
Jinja and Nunjucks
You can use the select()
Nunjucks or Jinja macro for this component. If you use one of the macros, make sure to set is_combobox
to True
(or true
for Nunjucks).
{% call select(
listbox_attrs={"data-empty": "No framework found."},
is_combobox=true
) %}
<div role="option" data-value="nextjs">Next.js</div>
<div role="option" data-value="sveltekit">SvelteKit</div>
<div role="option" data-value="nuxtjs">Nuxt.js</div>
<div role="option" data-value="remix">Remix</div>
<div role="option" data-value="astro">Astro</div>
{% endcall %}
Examples
Scrollable
Create timezone
<div class="select">
<button type="button" class="btn-outline justify-between font-normal w-[200px]" id="select-230118-trigger" popovertarget="select-230118" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-230118-listbox">
<span class="truncate"></span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down text-muted-foreground opacity-50 shrink-0">
<path d="m7 15 5 5 5-5" />
<path d="m7 9 5-5 5 5" />
</svg>
</button>
<div popover id="select-230118" class="popover">
<header>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
<input type="text" value="" placeholder="Search timezone..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="select-230118-content" aria-labelledby="select-230118-trigger" />
</header>
<div role="listbox" id="select-230118-listbox" aria-orientation="vertical" aria-labelledby="select-230118-trigger" data-empty="No timezone found.">
<div class="max-h-64 overflow-y-auto scrollbar">
<div role="group" aria-labelledby="demo-combobox-timezones-group-0">
<span id="demo-combobox-timezones-group-0" role="heading">Americas</span>
<div role="option" data-value="America/New_York">(GMT-5) New York</div>
<div role="option" data-value="America/Los_Angeles">(GMT-8) Los Angeles</div>
<div role="option" data-value="America/Chicago">(GMT-6) Chicago</div>
<div role="option" data-value="America/Toronto">(GMT-5) Toronto</div>
<div role="option" data-value="America/Vancouver">(GMT-8) Vancouver</div>
<div role="option" data-value="America/Sao_Paulo">(GMT-3) São Paulo</div>
</div>
<div role="group" aria-labelledby="demo-combobox-timezones-group-1">
<span id="demo-combobox-timezones-group-1" role="heading">Europe</span>
<div role="option" data-value="Europe/London">(GMT+0) London</div>
<div role="option" data-value="Europe/Paris">(GMT+1) Paris</div>
<div role="option" data-value="Europe/Berlin">(GMT+1) Berlin</div>
<div role="option" data-value="Europe/Rome">(GMT+1) Rome</div>
<div role="option" data-value="Europe/Madrid">(GMT+1) Madrid</div>
<div role="option" data-value="Europe/Amsterdam">(GMT+1) Amsterdam</div>
</div>
<div role="group" aria-labelledby="demo-combobox-timezones-group-2">
<span id="demo-combobox-timezones-group-2" role="heading">Asia/Pacific</span>
<div role="option" data-value="Asia/Tokyo">(GMT+9) Tokyo</div>
<div role="option" data-value="Asia/Shanghai">(GMT+8) Shanghai</div>
<div role="option" data-value="Asia/Singapore">(GMT+8) Singapore</div>
<div role="option" data-value="Asia/Dubai">(GMT+4) Dubai</div>
<div role="option" data-value="Australia/Sydney">(GMT+11) Sydney</div>
<div role="option" data-value="Asia/Seoul">(GMT+9) Seoul</div>
</div>
</div>
<hr role="separator" />
<div role="option">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M8 12h8" />
<path d="M12 8v8" />
</svg>
Create timezone
</div>
</div>
</div>
<input type="hidden" name="select-230118-value" value="" />
</div>
Top side
Next.js
SvelteKit
Nuxt.js
Remix
Astro
<div class="select">
<button type="button" class="btn-outline justify-between font-normal w-[200px]" id="select-886006-trigger" popovertarget="select-886006" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-886006-listbox">
<span class="truncate"></span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down text-muted-foreground opacity-50 shrink-0">
<path d="m7 15 5 5 5-5" />
<path d="m7 9 5-5 5 5" />
</svg>
</button>
<div popover id="select-886006" class="popover">
<header>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
<input type="text" value="" placeholder="Search framework..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="select-886006-content" aria-labelledby="select-886006-trigger" />
</header>
<div role="listbox" id="select-886006-listbox" aria-orientation="vertical" aria-labelledby="select-886006-trigger" data-empty="No framework found.">
<div role="option" data-value="Next.js">Next.js</div>
<div role="option" data-value="SvelteKit">SvelteKit</div>
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
<div role="option" data-value="Remix">Remix</div>
<div role="option" data-value="Astro">Astro</div>
</div>
</div>
<input type="hidden" name="select-886006-value" value="" />
</div>