🔥 Oxide UI v0.1.0 — Dark mode only, copy-paste ready. Get started →
Skip to content

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 matching id/for
  • The appearance-none class hides the native chevron so you can render your own — the native behavior is preserved

Released under the MIT License.