Combobox
A searchable dropdown component combining input and selection
Basic Usage
Simple combobox with searchable dropdown
Selected framework: None
Selected language: typescript
Code Svelte
1
2<script lang="ts">
3 import { ComboBox } from "@kareyes/aether";
4
5 const frameworks = [
6 { value: "sveltekit", label: "SvelteKit" },
7 { value: "next.js", label: "Next.js" },
8 { value: "nuxt.js", label: "Nuxt.js" },
9 { value: "remix", label: "Remix" },
10 { value: "astro", label: "Astro" },
11 { value: "gatsby", label: "Gatsby" },
12 { value: "angular", label: "Angular" },
13 { value: "vue", label: "Vue" },
14 { value: "react", label: "React" },
15 ];
16
17 const languages = [
18 { value: "javascript", label: "JavaScript" },
19 { value: "typescript", label: "TypeScript" },
20 { value: "python", label: "Python" },
21 { value: "java", label: "Java" },
22 { value: "csharp", label: "C#" },
23 { value: "go", label: "Go" },
24 { value: "rust", label: "Rust" },
25 { value: "ruby", label: "Ruby" },
26 { value: "php", label: "PHP" },
27 { value: "swift", label: "Swift" },
28 { value: "kotlin", label: "Kotlin" },
29 { value: "dart", label: "Dart" },
30 ];
31
32 let framework = $state("");
33 let language = $state("typescript");
34</script>
35
36 <div class="flex flex-wrap gap-4">
37 <ComboBox
38 items={frameworks}
39 bind:value={framework}
40 placeholder="Select framework..."
41 searchPlaceholder="Search frameworks..."
42 />
43
44 <ComboBox
45 items={languages}
46 bind:value={language}
47 placeholder="Select language..."
48 searchPlaceholder="Search languages..."
49 />
50 </div>Trigger Variants
Different button styles for the combobox trigger
Code Svelte
1
2<script lang="ts">
3 import { ComboBox } from "@kareyes/aether";
4
5 const frameworks = [
6 { value: "sveltekit", label: "SvelteKit" },
7 { value: "next.js", label: "Next.js" },
8 { value: "nuxt.js", label: "Nuxt.js" },
9 { value: "remix", label: "Remix" },
10 { value: "astro", label: "Astro" },
11 { value: "gatsby", label: "Gatsby" },
12 { value: "angular", label: "Angular" },
13 { value: "vue", label: "Vue" },
14 { value: "react", label: "React" },
15 ];
16</script>
17
18<div class="flex flex-wrap gap-4">
19 <ComboBox items={frameworks} triggerVariant="default" placeholder="Default" />
20 <ComboBox items={frameworks} triggerVariant="secondary" placeholder="Secondary" />
21 <ComboBox items={frameworks} triggerVariant="outline" placeholder="Outline" />
22 <ComboBox items={frameworks} triggerVariant="ghost" placeholder="Ghost" />
23</div>Sizes
Three size options: small, default, and large
Code Svelte
1
2<script lang="ts">
3 import { ComboBox } from "@kareyes/aether";
4
5 const frameworks = [
6 { value: "sveltekit", label: "SvelteKit" },
7 { value: "next.js", label: "Next.js" },
8 ];
9</script>
10
11<div class="flex flex-wrap items-center gap-4">
12 <ComboBox items={frameworks} triggerSize="sm" placeholder="Small" />
13 <ComboBox items={frameworks} triggerSize="default" placeholder="Default" />
14 <ComboBox items={frameworks} triggerSize="lg" placeholder="Large" />
15</div>Width Options
Customize the width of the trigger and content
Default width (200px)
Medium width (300px)
Full width with auto-size content
Code Svelte
1
2<script lang="ts">
3 import { ComboBox } from "@kareyes/aether";
4
5 const languages = [
6 { value: "javascript", label: "JavaScript" },
7 { value: "typescript", label: "TypeScript" },
8 { value: "python", label: "Python" },
9 { value: "java", label: "Java" },
10 { value: "csharp", label: "C#" },
11 { value: "go", label: "Go" },
12 { value: "rust", label: "Rust" },
13 { value: "ruby", label: "Ruby" },
14 { value: "php", label: "PHP" },
15 { value: "swift", label: "Swift" },
16 { value: "kotlin", label: "Kotlin" },
17 { value: "dart", label: "Dart" },
18 ];
19
20 const countries = [
21 { value: "us", label: "United States" },
22 { value: "uk", label: "United Kingdom" },
23 { value: "ca", label: "Canada" },
24 { value: "au", label: "Australia" },
25 { value: "de", label: "Germany" },
26 { value: "fr", label: "France" },
27 { value: "jp", label: "Japan" },
28 { value: "cn", label: "China" },
29 { value: "in", label: "India" },
30 { value: "br", label: "Brazil" },
31 ];
32
33 const timezones = [
34 { value: "pst", label: "Pacific Standard Time (PST)" },
35 { value: "mst", label: "Mountain Standard Time (MST)" },
36 { value: "cst", label: "Central Standard Time (CST)" },
37 { value: "est", label: "Eastern Standard Time (EST)" },
38 { value: "utc", label: "Coordinated Universal Time (UTC)" },
39 { value: "gmt", label: "Greenwich Mean Time (GMT)" },
40 { value: "ist", label: "Indian Standard Time (IST)" },
41 { value: "jst", label: "Japan Standard Time (JST)" },
42 ];
43
44
45 let language = $state("typescript");
46 let country = $state("");
47 let timezone = $state("utc");
48</script>
49
50 <div class="space-y-4">
51 <div>
52 <p class="text-sm text-muted-foreground mb-2">Default width (200px)</p>
53 <ComboBox
54 items={countries}
55 triggerClass="w-[200px]"
56 contentClass="w-[200px]"
57 placeholder="Select country..."
58 />
59 </div>
60 <div>
61 <p class="text-sm text-muted-foreground mb-2">Medium width (300px)</p>
62 <ComboBox
63 items={timezones}
64 triggerClass="w-[300px]"
65 contentClass="w-[300px]"
66 placeholder="Select timezone..."
67 />
68 </div>
69 <div>
70 <p class="text-sm text-muted-foreground mb-2">Full width with auto-size content</p>
71 <ComboBox
72 items={frameworks}
73 triggerClass="w-full"
74 contentClass="w-[var(--bits-popover-trigger-width)]"
75 placeholder="Select framework..."
76 />
77 </div>
78 </div>Form Example
Using combobox in a form with labels
Form Values:
Framework: Not selected
Language: typescript
Country: Not selected
Timezone: utc
Code Svelte
1
2<script lang="ts">
3 import { ComboBox, Label } from "@kareyes/aether";
4
5 const frameworks = [
6 { value: "sveltekit", label: "SvelteKit" },
7 { value: "next.js", label: "Next.js" },
8 { value: "nuxt.js", label: "Nuxt.js" },
9 { value: "remix", label: "Remix" },
10 { value: "astro", label: "Astro" },
11 { value: "gatsby", label: "Gatsby" },
12 { value: "angular", label: "Angular" },
13 { value: "vue", label: "Vue" },
14 { value: "react", label: "React" },
15 ];
16
17 const languages = [
18 { value: "javascript", label: "JavaScript" },
19 { value: "typescript", label: "TypeScript" },
20 { value: "python", label: "Python" },
21 { value: "java", label: "Java" },
22 { value: "csharp", label: "C#" },
23 { value: "go", label: "Go" },
24 { value: "rust", label: "Rust" },
25 { value: "ruby", label: "Ruby" },
26 { value: "php", label: "PHP" },
27 { value: "swift", label: "Swift" },
28 { value: "kotlin", label: "Kotlin" },
29 { value: "dart", label: "Dart" },
30 ];
31
32 const countries = [
33 { value: "us", label: "United States" },
34 { value: "uk", label: "United Kingdom" },
35 { value: "ca", label: "Canada" },
36 { value: "au", label: "Australia" },
37 { value: "de", label: "Germany" },
38 { value: "fr", label: "France" },
39 { value: "jp", label: "Japan" },
40 { value: "cn", label: "China" },
41 { value: "in", label: "India" },
42 { value: "br", label: "Brazil" },
43 ];
44
45 const timezones = [
46 { value: "pst", label: "Pacific Standard Time (PST)" },
47 { value: "mst", label: "Mountain Standard Time (MST)" },
48 { value: "cst", label: "Central Standard Time (CST)" },
49 { value: "est", label: "Eastern Standard Time (EST)" },
50 { value: "utc", label: "Coordinated Universal Time (UTC)" },
51 { value: "gmt", label: "Greenwich Mean Time (GMT)" },
52 { value: "ist", label: "Indian Standard Time (IST)" },
53 { value: "jst", label: "Japan Standard Time (JST)" },
54 ];
55
56 // State
57 let framework = $state("");
58 let language = $state("typescript");
59 let country = $state("");
60 let timezone = $state("utc");
61</script>
62
63 <div class="max-w-md space-y-4">
64 <div class="space-y-2">
65 <Label class="text-sm font-medium">Framework</Label>
66 <ComboBox
67 items={frameworks}
68 bind:value={framework}
69 placeholder="Select a framework..."
70 searchPlaceholder="Search frameworks..."
71 triggerClass="w-full"
72 contentClass="w-[var(--bits-popover-trigger-width)]"
73 />
74 </div>
75 <div class="space-y-2">
76 <Label class="text-sm font-medium">Primary Language</Label>
77 <ComboBox
78 items={languages}
79 bind:value={language}
80 placeholder="Select a language..."
81 searchPlaceholder="Search languages..."
82 triggerClass="w-full"
83 contentClass="w-[var(--bits-popover-trigger-width)]"
84 />
85 </div>
86 <div class="space-y-2">
87 <Label class="text-sm font-medium">Country</Label>
88 <ComboBox
89 items={countries}
90 bind:value={country}
91 placeholder="Select a country..."
92 searchPlaceholder="Search countries..."
93 triggerClass="w-full"
94 contentClass="w-[var(--bits-popover-trigger-width)]"
95 />
96 </div>
97 <div class="space-y-2">
98 <Label class="text-sm font-medium">Timezone</Label>
99 <ComboBox
100 items={timezones}
101 bind:value={timezone}
102 placeholder="Select a timezone..."
103 searchPlaceholder="Search timezones..."
104 triggerClass="w-full"
105 contentClass="w-[var(--bits-popover-trigger-width)]"
106 />
107 </div>
108 </div>
109 <div class="mt-6 p-4 bg-muted rounded-lg max-w-md">
110 <h3 class="font-medium mb-2">Form Values:</h3>
111 <div class="text-sm space-y-1">
112 <div>Framework: <span class="font-medium">{framework || "Not selected"}</span></div>
113 <div>Language: <span class="font-medium">{language || "Not selected"}</span></div>
114 <div>Country: <span class="font-medium">{country || "Not selected"}</span></div>
115 <div>Timezone: <span class="font-medium">{timezone || "Not selected"}</span></div>
116 </div>
117 </div>Custom Trigger
Use the trigger snippet to provide custom trigger elements.
Code Svelte
1
2<script lang="ts">
3 import { ComboBox, Button, PopoverPrimitives as Popover } from "@kareyes/aether";
4 import { ChevronsUpDown } from "@kareyes/aether/icons";
5
6 const frameworks = [
7 { value: "sveltekit", label: "SvelteKit" },
8 { value: "next.js", label: "Next.js" },
9 { value: "nuxt.js", label: "Nuxt.js" },
10 { value: "remix", label: "Remix" },
11 { value: "astro", label: "Astro" },
12 { value: "gatsby", label: "Gatsby" },
13 { value: "angular", label: "Angular" },
14 { value: "vue", label: "Vue" },
15 { value: "react", label: "React" },
16 ];
17
18 const languages = [
19 { value: "javascript", label: "JavaScript" },
20 { value: "typescript", label: "TypeScript" },
21 { value: "python", label: "Python" },
22 { value: "java", label: "Java" },
23 { value: "csharp", label: "C#" },
24 { value: "go", label: "Go" },
25 { value: "rust", label: "Rust" },
26 { value: "ruby", label: "Ruby" },
27 { value: "php", label: "PHP" },
28 { value: "swift", label: "Swift" },
29 { value: "kotlin", label: "Kotlin" },
30 { value: "dart", label: "Dart" },
31 ];
32 let framework = $state("");
33 let language = $state("typescript");
34</script>
35
36 <div class="flex flex-wrap gap-4">
37 <!-- Custom styled trigger -->
38 <ComboBox items={frameworks} bind:value={framework}>
39 {#snippet trigger({ selectedLabel, open })}
40 <Popover.Trigger>
41 {#snippet child({ props })}
42 <Button
43 {...props}
44 variant="outline"
45 class="w-[280px] justify-between border-2 border-primary/20 hover:border-primary/40"
46 role="combobox"
47 aria-expanded={open}
48 >
49 <span class="flex items-center gap-2">
50 {#if selectedLabel}
51 <span class="w-2 h-2 rounded-full bg-green-500"></span>
52 {/if}
53 {selectedLabel || "Choose your framework..."}
54 </span>
55 <ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
56 </Button>
57 {/snippet}
58 </Popover.Trigger>
59 {/snippet}
60 </ComboBox>
61
62 <!-- Minimal text trigger -->
63 <ComboBox items={languages} bind:value={language}>
64 {#snippet trigger({ selectedLabel, open })}
65 <Popover.Trigger>
66 {#snippet child({ props })}
67 <button
68 {...props}
69 class="text-sm font-medium text-primary hover:underline cursor-pointer flex items-center gap-1"
70 >
71 {selectedLabel || "Select language"} ▼
72 </button>
73 {/snippet}
74 </Popover.Trigger>
75 {/snippet}
76 </ComboBox>
77 </div>
78With Disabled Items
Some options can be disabled to prevent selection
Code Svelte
1
2<script lang="ts">
3 import { ComboBox } from "@kareyes/aether";
4</script>
5
6<ComboBox
7 items={[
8 { value: "option1", label: "Available Option 1" },
9 { value: "option2", label: "Disabled Option 2", disabled: true },
10 { value: "option3", label: "Available Option 3" },
11 { value: "option4", label: "Disabled Option 4", disabled: true },
12 { value: "option5", label: "Available Option 5" },
13 ]}
14 placeholder="Select option..."
15/>Features
- 🔍 Searchable - Built-in search/filter functionality
- 🎨 Customizable - Multiple variants, sizes, and styling options
- ♿ Accessible - Proper ARIA attributes and keyboard navigation
- 🎯 Simple API - Easy to use with declarative items array
- 🔗 Bindable - Two-way binding support with
bind:value - 🎭 Custom Trigger - Support for custom trigger elements via snippet
- 🚫 Disabled Items - Support for disabled options
- 📦 TypeScript - Full type safety
Basic Example
<script lang="ts">
import { Combobox } from "@kareyes/aether";
const frameworks = [
{ value: "sveltekit", label: "SvelteKit" },
{ value: "next.js", label: "Next.js" },
{ value: "nuxt.js", label: "Nuxt.js" },
{ value: "remix", label: "Remix" },
{ value: "astro", label: "Astro" },
];
let selectedFramework = $state("");
</script>
<Combobox
items={frameworks}
bind:value={selectedFramework}
placeholder="Select framework..."
searchPlaceholder="Search frameworks..."
/>
With Initial Value
<Combobox
items={frameworks}
value="sveltekit"
placeholder="Select framework..."
/>
Custom Placeholder and Messages
<Combobox
items={languages}
placeholder="Pick a language..."
searchPlaceholder="Type to search..."
emptyMessage="No language found."
/>
With Value Change Handler
<script lang="ts">
function handleValueChange(value: string) {
console.log("Selected:", value);
}
</script>
<Combobox
items={frameworks}
onValueChange={handleValueChange}
placeholder="Select framework..."
/>
Different Sizes
<!-- Small -->
<Combobox
items={frameworks}
triggerSize="sm"
placeholder="Small combobox"
/>
<!-- Default -->
<Combobox
items={frameworks}
triggerSize="default"
placeholder="Default combobox"
/>
<!-- Large -->
<Combobox
items={frameworks}
triggerSize="lg"
placeholder="Large combobox"
/>
Different Variants
<!-- Default -->
<Combobox
items={frameworks}
triggerVariant="default"
placeholder="Default variant"
/>
<!-- Secondary -->
<Combobox
items={frameworks}
triggerVariant="secondary"
placeholder="Secondary variant"
/>
<!-- Outline -->
<Combobox
items={frameworks}
triggerVariant="outline"
placeholder="Outline variant"
/>
<!-- Ghost -->
<Combobox
items={frameworks}
triggerVariant="ghost"
placeholder="Ghost variant"
/>
Custom Width
<!-- Fixed width -->
<Combobox
items={countries}
triggerClass="w-[300px]"
contentClass="w-[300px]"
placeholder="Select country..."
/>
<!-- Full width with auto-size content -->
<Combobox
items={frameworks}
triggerClass="w-full"
contentClass="w-[var(--bits-popover-trigger-width)]"
placeholder="Select framework..."
/>
With Disabled Items
<Combobox
items={[
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2", disabled: true },
{ value: "option3", label: "Option 3" },
{ value: "option4", label: "Option 4", disabled: true },
{ value: "option5", label: "Option 5" },
]}
placeholder="Select option..."
/>
Content Alignment
<!-- Align start -->
<Combobox
items={frameworks}
align="start"
placeholder="Align start"
/>
<!-- Align center -->
<Combobox
items={frameworks}
align="center"
placeholder="Align center"
/>
<!-- Align end -->
<Combobox
items={frameworks}
align="end"
placeholder="Align end"
/>
Custom Trigger
<script lang="ts">
import { Combobox, PopoverPrimitives, Button } from "@kareyes/aether";
import ChevronsUpDown from "@lucide/svelte/icons/chevrons-up-down";
let selectedFramework = $state("");
</script>
<Combobox items={frameworks} bind:value={selectedFramework}>
{#snippet trigger({ selectedLabel, open })}
<PopoverPrimitives.Trigger>
{#snippet child({ props })}
<Button
{...props}
variant="outline"
class="w-[280px] justify-between border-2 border-primary/20"
role="combobox"
aria-expanded={open}
>
<span class="flex items-center gap-2">
{#if selectedLabel}
<span class="w-2 h-2 rounded-full bg-green-500"></span>
{/if}
{selectedLabel || "Choose your framework..."}
</span>
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
</Button>
{/snippet}
</PopoverPrimitives.Trigger>
{/snippet}
</Combobox>
In Forms
<script lang="ts">
let framework = $state("");
let language = $state("");
let country = $state("");
</script>
<form class="space-y-4">
<div class="space-y-2">
<label class="text-sm font-medium">Framework</label>
<Combobox
items={frameworks}
bind:value={framework}
placeholder="Select a framework..."
triggerClass="w-full"
contentClass="w-[var(--bits-popover-trigger-width)]"
/>
</div>
<div class="space-y-2">
<label class="text-sm font-medium">Language</label>
<Combobox
items={languages}
bind:value={language}
placeholder="Select a language..."
triggerClass="w-full"
contentClass="w-[var(--bits-popover-trigger-width)]"
/>
</div>
<div class="space-y-2">
<label class="text-sm font-medium">Country</label>
<Combobox
items={countries}
bind:value={country}
placeholder="Select a country..."
triggerClass="w-full"
contentClass="w-[var(--bits-popover-trigger-width)]"
/>
</div>
</form>
Props
Items
| Prop | Type | Default | Description |
|---|---|---|---|
items |
ComboboxItem[] |
[] |
Array of items to display in the combobox |
ComboboxItem Type:
type ComboboxItem = {
value: string; // Unique value for the item
label: string; // Display text for the item
disabled?: boolean; // Whether the item is disabled
};
Value
| Prop | Type | Default | Description |
|---|---|---|---|
value |
string |
"" |
Selected value (bindable) |
onValueChange |
(value: string) => void |
undefined |
Callback when selection changes |
Trigger Props
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder |
string |
"Select an item..." |
Placeholder text when nothing selected |
triggerVariant |
ButtonVariant |
"outline" |
Button variant for trigger |
triggerSize |
ButtonSize |
"default" |
Button size for trigger |
triggerClass |
string |
undefined |
Additional CSS classes for trigger |
ButtonVariant: "default" | "secondary" | "outline" | "ghost" | "destructive"
ButtonSize: "sm" | "default" | "lg"
Search Props
| Prop | Type | Default | Description |
|---|---|---|---|
searchPlaceholder |
string |
"Search..." |
Placeholder for search input |
emptyMessage |
string |
"No item found." |
Message when no results found |
Popover Props
| Prop | Type | Default | Description |
|---|---|---|---|
contentClass |
string |
undefined |
Additional CSS classes for content |
align |
"start" | "center" | "end" |
"start" |
Horizontal alignment of content |
side |
"top" | "right" | "bottom" | "left" |
"bottom" |
Side where content appears |
Advanced Props
| Prop | Type | Default | Description |
|---|---|---|---|
trigger |
Snippet<[{ selectedLabel: string | undefined; open: boolean }]> |
undefined |
Custom trigger snippet |
open |
boolean |
false |
Controlled open state (bindable) |
Examples
Reactive Updates
<script lang="ts">
let selectedValue = $state("sveltekit");
$effect(() => {
console.log("Current selection:", selectedValue);
});
</script>
<Combobox
items={frameworks}
bind:value={selectedValue}
placeholder="Select framework..."
/>
<p>You selected: {selectedValue || "nothing"}</p>
Controlled Open State
<script lang="ts">
let isOpen = $state(false);
let selectedValue = $state("");
</script>
<button onclick={() => isOpen = !isOpen}>
Toggle Combobox
</button>
<Combobox
items={frameworks}
bind:value={selectedValue}
bind:open={isOpen}
placeholder="Select framework..."
/>
Dynamic Items
<script lang="ts">
let items = $state([
{ value: "item1", label: "Item 1" },
{ value: "item2", label: "Item 2" },
]);
function addItem() {
const newId = items.length + 1;
items = [...items, { value: `item${newId}`, label: `Item ${newId}` }];
}
</script>
<button onclick={addItem}>Add Item</button>
<Combobox
items={items}
placeholder="Select item..."
/>
Accessibility
The Combobox component follows WAI-ARIA combobox patterns:
- ✅ Proper
role="combobox"attribute - ✅
aria-expandedstate - ✅ Keyboard navigation (Arrow keys, Enter, Escape)
- ✅ Focus management
- ✅ Screen reader support
- ✅ Disabled state support
Keyboard Shortcuts
| Key | Action |
|---|---|
Space / Enter |
Open/close combobox |
ArrowDown |
Navigate to next item |
ArrowUp |
Navigate to previous item |
Enter |
Select highlighted item |
Escape |
Close combobox |
Tab |
Close and move to next focusable element |
| Type to search | Filter items by typing |
Styling
Custom Trigger Width
<Combobox
items={items}
triggerClass="w-[280px]"
contentClass="w-[280px]"
/>
Match Content Width to Trigger
<Combobox
items={items}
triggerClass="w-full max-w-sm"
contentClass="w-[var(--bits-popover-trigger-width)]"
/>
Custom Styling
<Combobox
items={items}
triggerClass="border-2 border-primary hover:border-primary/80"
contentClass="bg-card"
/>
Best Practices
- Provide Clear Labels - Use descriptive labels for form fields
- Appropriate Placeholder - Make placeholder text helpful and concise
- Limit Items - For very large lists, consider pagination or server-side search
- Responsive Width - Use appropriate width for the context
- Error States - Combine with form validation for better UX
- Loading States - Show loading indicator for async data
- Empty State - Provide helpful message when no items match