SelectMenu
A searchable dropdown select with icons, avatars, grouped items, descriptions, and custom rendering. Built on bits-ui Combobox with full keyboard navigation.
Playground
Experiment with different props in real-time.
Basic Usage
Pass items and use bind:value. Items are searchable by default.
<script lang="ts">
import { SelectMenu } from 'sv5ui';
let value = $state('');
const items = [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
{ value: 'orange', label: 'Orange' },
{ value: 'grape', label: 'Grape' },
{ value: 'mango', label: 'Mango' }
];
</script>
<SelectMenu {items} bind:value placeholder="Select a fruit..." />With Icons
Add Iconify icons to items.
<SelectMenu
placeholder="Select status..."
items={[
{ value: 'active', label: 'Active', icon: 'lucide:check-circle' },
{ value: 'paused', label: 'Paused', icon: 'lucide:pause-circle' },
{ value: 'closed', label: 'Closed', icon: 'lucide:x-circle' }
]}
/>With Avatar
Display avatars alongside items.
<SelectMenu
placeholder="Assign to..."
items={[
{ value: 'john', label: 'John Doe', avatar: { src: 'https://i.pravatar.cc/40?u=1', alt: 'John' } },
{ value: 'jane', label: 'Jane Smith', avatar: { src: 'https://i.pravatar.cc/40?u=2', alt: 'Jane' } },
{ value: 'bob', label: 'Bob Wilson', avatar: { src: 'https://i.pravatar.cc/40?u=3', alt: 'Bob' } }
]}
/>With Description
Add secondary text to items.
<SelectMenu
placeholder="Select a plan..."
items={[
{ value: 'free', label: 'Free', description: '5GB storage, basic features' },
{ value: 'pro', label: 'Pro', description: '50GB storage, advanced analytics' },
{ value: 'enterprise', label: 'Enterprise', description: 'Unlimited storage, priority support' }
]}
/>Grouped Items
Use type: 'label' for headings and type: 'separator' for dividers.
<SelectMenu
placeholder="Select a framework..."
items={[
{ type: 'label', label: 'Frontend' },
{ value: 'svelte', label: 'Svelte', icon: 'logos:svelte-icon' },
{ value: 'react', label: 'React', icon: 'logos:react' },
{ value: 'vue', label: 'Vue', icon: 'logos:vue' },
{ type: 'separator' },
{ type: 'label', label: 'Backend' },
{ value: 'node', label: 'Node.js', icon: 'logos:nodejs-icon' },
{ value: 'python', label: 'Python', icon: 'logos:python' },
{ value: 'go', label: 'Go', icon: 'logos:go' }
]}
/>Variants
4 visual styles for the trigger.
<SelectMenu variant="outline" placeholder="Outline" {items} />
<SelectMenu variant="soft" placeholder="Soft" {items} />
<SelectMenu variant="subtle" placeholder="Subtle" {items} />
<SelectMenu variant="ghost" placeholder="Ghost" {items} />Sizes
5 sizes.
<SelectMenu size="xs" placeholder="Extra Small" {items} />
<SelectMenu size="sm" placeholder="Small" {items} />
<SelectMenu size="md" placeholder="Medium" {items} />
<SelectMenu size="lg" placeholder="Large" {items} />
<SelectMenu size="xl" placeholder="Extra Large" {items} />Colors
Visible with highlight active.
<SelectMenu color="primary" highlight placeholder="Primary" {items} />
<SelectMenu color="success" highlight placeholder="Success" {items} />
<SelectMenu color="warning" highlight placeholder="Warning" {items} />
<SelectMenu color="error" highlight placeholder="Error" {items} />Search & Filtering
Items are searchable by default. Customize with filterFields or disable with ignoreFilter.
<!-- Default search (searches label and value) -->
<SelectMenu placeholder="Search items..." {items} />
<!-- Custom search fields -->
<SelectMenu
placeholder="Search by description..."
filterFields={['label', 'description']}
items={[
{ value: 'free', label: 'Free', description: '5GB storage' },
{ value: 'pro', label: 'Pro', description: '50GB storage' },
{ value: 'enterprise', label: 'Enterprise', description: 'Unlimited' }
]}
/>
<!-- Disable filtering (server-side) -->
<SelectMenu placeholder="Server-side..." ignoreFilter {items} />Loading
Show a spinner.
<SelectMenu loading placeholder="Loading..." {items} />Disabled
Disable the entire menu or individual items.
<SelectMenu disabled placeholder="Disabled" {items} />
<!-- Individual disabled items -->
<SelectMenu
placeholder="Some items disabled"
items={[
{ value: 'active', label: 'Active' },
{ value: 'disabled', label: 'Disabled option', disabled: true },
{ value: 'another', label: 'Another active' }
]}
/>Controlled
Use bind:value for two-way binding.
Selected: none
<script lang="ts">
import { SelectMenu, Button } from 'sv5ui';
let value = $state('');
</script>
<SelectMenu {items} bind:value placeholder="Pick one..." />
<p>Selected: {value || 'none'}</p>
<Button size="sm" variant="outline" label="Reset" onclick={() => value = ''} />UI Slots
Use the ui prop to override classes on internal elements.
| Slot | Description |
|---|---|
root | Root wrapper element |
base | Trigger button element |
leading | Leading section container |
leadingIcon | Leading icon element |
leadingAvatar | Leading avatar element |
trailing | Trailing section container |
trailingIcon | Trailing icon (chevron) |
value | Selected value text |
placeholder | Placeholder text |
content | Dropdown content container |
input | Search input element |
viewport | Scrollable items viewport |
empty | Empty state container |
groupLabel | Group label/heading |
separator | Visual separator between groups |
item | Individual item container |
itemIcon | Icon in item |
itemAvatar | Avatar in item |
itemLabel | Item label text |
itemDescription | Item description text |
itemIndicator | Selected indicator (checkmark) |
Snippets
Custom rendering for trigger and dropdown content.
| Snippet | Description |
|---|---|
leadingSlot | Custom content before selected value on trigger |
trailingSlot | Custom content after selected value on trigger |
item | Complete item rendering (receives item, index, selected) |
itemLeading | Item leading section (receives item, index, selected) |
itemLabel | Item label section (receives item, index, selected) |
itemTrailing | Item trailing/indicator section (receives item, index, selected) |
empty | Empty state (receives searchTerm) |
content | Complete dropdown content (receives open, searchTerm) |
Props
| Prop | Type | Default |
|---|---|---|
items | SelectMenuItemType[] | [] |
value | string | - |
open | boolean | false |
placeholder | string | - |
searchPlaceholder | string | 'Search...' |
variant | 'outline' | 'soft' | 'subtle' | 'ghost' | 'none' | 'outline' |
color | ColorType | 'primary' |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' |
highlight | boolean | false |
loading | boolean | false |
disabled | boolean | false |
icon | string | - |
leadingIcon | string | - |
trailingIcon | string | 'lucide:chevron-down' |
selectedIcon | string | 'lucide:check' |
avatar | AvatarProps | - |
filterFields | string[] | ['label', 'value'] |
ignoreFilter | boolean | false |
emptyText | string | 'No results found.' |
required | boolean | false |
name | string | - |
ref | HTMLElement | null | null |
class | string | - |
ui | Record<Slot, Class> | - |
Item Props
Each selectable item accepts these properties. Use type: 'label' for headings and type: 'separator' for dividers.
| Prop | Type | Default |
|---|---|---|
value | string | - |
label | string | - |
description | string | - |
icon | string | - |
avatar | AvatarProps | - |
disabled | boolean | false |