Select
<select>
For single-item selection from a list. Uses the native <select> element for accessibility and mobile compatibility.
Overview
Preview
Basic select
Preview
html
<div class="relative">
<select class="block w-full appearance-none rounded-md border border-zinc-700 bg-zinc-900 py-2 pl-3 pr-8 text-sm text-zinc-100 outline-none transition-all focus:border-[#D40C37] focus:ring-2 focus:ring-[#D40C37]/15 cursor-pointer">
<option value="" disabled selected>Select a framework...</option>
<option value="nuxt">Nuxt</option>
<option value="vue">Vue</option>
<option value="astro">Astro</option>
</select>
<!-- Custom chevron -->
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5">
<svg class="h-4 w-4 text-zinc-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"/>
</svg>
</div>
</div>With label
html
<div class="space-y-1.5">
<label for="framework" class="block text-[10px] font-semibold uppercase tracking-widest text-zinc-500">
Framework
</label>
<div class="relative">
<select id="framework" class="block w-full appearance-none rounded-md border border-zinc-700 bg-zinc-900 py-2 pl-3 pr-8 text-sm text-zinc-100 outline-none focus:border-[#D40C37] focus:ring-2 focus:ring-[#D40C37]/15">
...
</select>
...
</div>
</div>Disabled
html
<select disabled class="block w-full appearance-none rounded-md border border-zinc-800 bg-zinc-900/40 py-2 pl-3 pr-8 text-sm text-zinc-600 outline-none cursor-not-allowed">
<option>Auto-detected</option>
</select>Error state
html
<select aria-invalid="true" class="block w-full appearance-none rounded-md border border-red-700/60 bg-zinc-900 py-2 pl-3 pr-8 text-sm text-zinc-100 outline-none ring-2 ring-red-700/20">
...
</select>
<p class="mt-1 text-xs text-red-400">Please select a valid option.</p>Custom combobox (with Headless UI)
For searchable / autocomplete dropdowns, use Headless UI's Combobox:
vue
<script setup>
import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption } from '@headlessui/vue'
import { ref, computed } from 'vue'
const query = ref('')
const selected = ref(null)
const options = ['Nuxt', 'Vue', 'Astro', 'SvelteKit']
const filtered = computed(() =>
query.value === '' ? options : options.filter(o => o.toLowerCase().includes(query.value.toLowerCase()))
)
</script>
<template>
<Combobox v-model="selected">
<ComboboxInput class="block w-full rounded-md border border-zinc-700 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 outline-none focus:border-[#D40C37] focus:ring-2 focus:ring-[#D40C37]/15" @change="query = $event.target.value" />
<ComboboxOptions class="mt-1 rounded-lg border border-zinc-700/60 bg-zinc-900 py-1 shadow-xl">
<ComboboxOption v-for="opt in filtered" :key="opt" :value="opt" v-slot="{ active }">
<div :class="['px-3 py-2 text-sm cursor-pointer', active ? 'bg-zinc-800 text-zinc-100' : 'text-zinc-300']">
{{ opt }}
</div>
</ComboboxOption>
</ComboboxOptions>
</Combobox>
</template>Accessibility notes
- Always use a native
<select>for basic selection — it has full browser/assistive tech support for free - Pair with a
<label>using matchingid/for - The
appearance-noneclass hides the native chevron so you can render your own — the native behavior is preserved