Docs
Combobox
Combobox
Autocomplete input and command palette with a list of suggestions.
Loading...
<script lang="ts">
import { Check, CaretSort } from "radix-icons-svelte";
import * as Command from "$lib/components/ui/command";
import * as Popover from "$lib/components/ui/popover";
import { Button } from "$lib/components/ui/button";
import { cn } from "$lib/utils";
import { tick } from "svelte";
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 open = false;
let value = "";
$: selectedValue =
frameworks.find((f) => f.value === value)?.label ?? "Select a framework...";
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<Popover.Root bind:open let:ids>
<Popover.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="outline"
role="combobox"
aria-expanded={open}
class="w-[200px] justify-between"
>
{selectedValue}
<CaretSort class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Command.Root>
<Command.Input placeholder="Search framework..." class="h-9" />
<Command.Empty>No framework found.</Command.Empty>
<Command.Group>
{#each frameworks as framework}
<Command.Item
value={framework.value}
onSelect={(currentValue) => {
value = currentValue;
closeAndFocusTrigger(ids.trigger);
}}
>
<Check
class={cn(
"mr-2 h-4 w-4",
value !== framework.value && "text-transparent"
)}
/>
{framework.label}
</Command.Item>
{/each}
</Command.Group>
</Command.Root>
</Popover.Content>
</Popover.Root>
<script lang="ts">
import { Check, ChevronsUpDown } from "lucide-svelte";
import * as Command from "$lib/components/ui/command";
import * as Popover from "$lib/components/ui/popover";
import { Button } from "$lib/components/ui/button";
import { cn } from "$lib/utils";
import { tick } from "svelte";
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 open = false;
let value = "";
$: selectedValue =
frameworks.find((f) => f.value === value)?.label ?? "Select a framework...";
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<Popover.Root bind:open let:ids>
<Popover.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="outline"
role="combobox"
aria-expanded={open}
class="w-[200px] justify-between"
>
{selectedValue}
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Command.Root>
<Command.Input placeholder="Search framework..." />
<Command.Empty>No framework found.</Command.Empty>
<Command.Group>
{#each frameworks as framework}
<Command.Item
value={framework.value}
onSelect={(currentValue) => {
value = currentValue;
closeAndFocusTrigger(ids.trigger);
}}
>
<Check
class={cn(
"mr-2 h-4 w-4",
value !== framework.value && "text-transparent"
)}
/>
{framework.label}
</Command.Item>
{/each}
</Command.Group>
</Command.Root>
</Popover.Content>
</Popover.Root>
Installation
The Combobox is built using a composition of the <Popover />
and the <Command />
components.
See installation instructions for the Popover and the Command components.
Usage
<script lang="ts">
import { Check, ChevronsUpDown } from "lucide-svelte";
import * as Command from "@/registry/default/ui/command";
import * as Popover from "@/registry/default/ui/popover";
import { Button } from "@/registry/default/ui/button";
import { cn } from "$lib/utils";
import { tick } from "svelte";
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 open = false;
let value = "";
$: selectedValue =
frameworks.find((f) => f.value === value)?.label ??
"Select a framework...";
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<Popover.Root bind:open let:ids>
<Popover.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="outline"
role="combobox"
aria-expanded={open}
class="w-[200px] justify-between"
>
{selectedValue}
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Command.Root>
<Command.Input placeholder="Search framework..." />
<Command.Empty>No framework found.</Command.Empty>
<Command.Group>
{#each frameworks as framework}
<Command.Item
value={framework.value}
onSelect={(currentValue) => {
value = currentValue;
closeAndFocusTrigger(ids.trigger);
}}
>
<Check
class={cn(
"mr-2 h-4 w-4",
value !== framework.value && "text-transparent"
)}
/>
{framework.label}
</Command.Item>
{/each}
</Command.Group>
</Command.Root>
</Popover.Content>
</Popover.Root>
Examples
Combobox
Loading...
<script lang="ts">
import { Check, CaretSort } from "radix-icons-svelte";
import * as Command from "$lib/components/ui/command";
import * as Popover from "$lib/components/ui/popover";
import { Button } from "$lib/components/ui/button";
import { cn } from "$lib/utils";
import { tick } from "svelte";
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 open = false;
let value = "";
$: selectedValue =
frameworks.find((f) => f.value === value)?.label ?? "Select a framework...";
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<Popover.Root bind:open let:ids>
<Popover.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="outline"
role="combobox"
aria-expanded={open}
class="w-[200px] justify-between"
>
{selectedValue}
<CaretSort class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Command.Root>
<Command.Input placeholder="Search framework..." class="h-9" />
<Command.Empty>No framework found.</Command.Empty>
<Command.Group>
{#each frameworks as framework}
<Command.Item
value={framework.value}
onSelect={(currentValue) => {
value = currentValue;
closeAndFocusTrigger(ids.trigger);
}}
>
<Check
class={cn(
"mr-2 h-4 w-4",
value !== framework.value && "text-transparent"
)}
/>
{framework.label}
</Command.Item>
{/each}
</Command.Group>
</Command.Root>
</Popover.Content>
</Popover.Root>
<script lang="ts">
import { Check, ChevronsUpDown } from "lucide-svelte";
import * as Command from "$lib/components/ui/command";
import * as Popover from "$lib/components/ui/popover";
import { Button } from "$lib/components/ui/button";
import { cn } from "$lib/utils";
import { tick } from "svelte";
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 open = false;
let value = "";
$: selectedValue =
frameworks.find((f) => f.value === value)?.label ?? "Select a framework...";
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<Popover.Root bind:open let:ids>
<Popover.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="outline"
role="combobox"
aria-expanded={open}
class="w-[200px] justify-between"
>
{selectedValue}
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Command.Root>
<Command.Input placeholder="Search framework..." />
<Command.Empty>No framework found.</Command.Empty>
<Command.Group>
{#each frameworks as framework}
<Command.Item
value={framework.value}
onSelect={(currentValue) => {
value = currentValue;
closeAndFocusTrigger(ids.trigger);
}}
>
<Check
class={cn(
"mr-2 h-4 w-4",
value !== framework.value && "text-transparent"
)}
/>
{framework.label}
</Command.Item>
{/each}
</Command.Group>
</Command.Root>
</Popover.Content>
</Popover.Root>
Popover
Loading...
<script lang="ts">
import * as Command from "$lib/components/ui/command";
import * as Popover from "$lib/components/ui/popover";
import { Button } from "$lib/components/ui/button";
import { tick } from "svelte";
type Status = {
value: string;
label: string;
};
const statuses: Status[] = [
{
value: "backlog",
label: "Backlog"
},
{
value: "todo",
label: "Todo"
},
{
value: "in progress",
label: "In Progress"
},
{
value: "done",
label: "Done"
},
{
value: "canceled",
label: "Canceled"
}
];
let open = false;
let value = "";
$: selectedStatus = statuses.find((s) => s.value === value) ?? null;
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<div class="flex items-center space-x-4">
<p class="text-sm text-muted-foreground">Status</p>
<Popover.Root bind:open let:ids positioning={{ placement: "right-start" }}>
<Popover.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="outline"
class="w-[150px] justify-start"
>
{selectedStatus ? selectedStatus.label : "+ Set status"}
</Button>
</Popover.Trigger>
<Popover.Content class="p-0">
<Command.Root>
<Command.Input placeholder="Change status..." />
<Command.List>
<Command.Empty>No results found.</Command.Empty>
<Command.Group>
{#each statuses as status}
<Command.Item
value={status.value}
onSelect={(currentValue) => {
value = currentValue;
closeAndFocusTrigger(ids.trigger);
}}
>
{status.label}
</Command.Item>
{/each}
</Command.Group>
</Command.List>
</Command.Root>
</Popover.Content>
</Popover.Root>
</div>
<script lang="ts">
import {
ArrowUpCircle,
CheckCircle2,
Circle,
HelpCircle,
XCircle
} from "lucide-svelte";
import * as Command from "$lib/components/ui/command";
import * as Popover from "$lib/components/ui/popover";
import { Button } from "$lib/components/ui/button";
import { cn } from "$lib/utils";
import { tick, type ComponentType } from "svelte";
type Status = {
value: string;
label: string;
icon: ComponentType;
};
const statuses: Status[] = [
{
value: "backlog",
label: "Backlog",
icon: HelpCircle
},
{
value: "todo",
label: "Todo",
icon: Circle
},
{
value: "in progress",
label: "In Progress",
icon: ArrowUpCircle
},
{
value: "done",
label: "Done",
icon: CheckCircle2
},
{
value: "canceled",
label: "Canceled",
icon: XCircle
}
];
let open = false;
let value = "";
$: selectedStatus = statuses.find((s) => s.value === value) ?? null;
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<div class="flex items-center space-x-4">
<p class="text-sm text-muted-foreground">Status</p>
<Popover.Root bind:open let:ids positioning={{ placement: "right-start" }}>
<Popover.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="outline"
size="sm"
class="w-[150px] justify-start"
>
{#if selectedStatus}
<svelte:component
this={selectedStatus.icon}
class="mr-2 h-4 w-4 shrink-0"
/>
{selectedStatus.label}
{:else}
+ Set status
{/if}
</Button>
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Command.Root>
<Command.Input placeholder="Change status..." />
<Command.List>
<Command.Empty>No results found.</Command.Empty>
<Command.Group>
{#each statuses as status}
<Command.Item
value={status.value}
onSelect={(currentValue) => {
value = currentValue;
closeAndFocusTrigger(ids.trigger);
}}
>
<svelte:component
this={status.icon}
class={cn(
"mr-2 h-4 w-4",
status.value !== selectedStatus?.value &&
"text-foreground/40"
)}
/>
<span>
{status.label}
</span>
</Command.Item>
{/each}
</Command.Group>
</Command.List>
</Command.Root>
</Popover.Content>
</Popover.Root>
</div>
Dropdown menu
Loading...
<script lang="ts">
import { DotsHorizontal } from "radix-icons-svelte";
import * as Command from "$lib/components/ui/command";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { Button } from "$lib/components/ui/button";
import { tick } from "svelte";
const labels = [
"feature",
"bug",
"enhancement",
"documentation",
"design",
"question",
"maintenance"
];
let open = false;
let selectedLabel = "feature";
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<div
class="flex w-full flex-col items-start justify-between rounded-md border px-4 py-3 sm:flex-row sm:items-center"
>
<p class="text-sm font-medium leading-none">
<span
class="mr-2 rounded-lg bg-primary px-2 py-1 text-xs text-primary-foreground"
>
{selectedLabel}
</span>
<span class="text-muted-foreground">Create a new project</span>
</p>
<DropdownMenu.Root
bind:open
positioning={{ placement: "bottom-end" }}
let:ids
>
<DropdownMenu.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="ghost"
size="sm"
aria-label="Open menu"
>
<DotsHorizontal />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-[200px]">
<DropdownMenu.Group>
<DropdownMenu.Label>Actions</DropdownMenu.Label>
<DropdownMenu.Item>Assign to...</DropdownMenu.Item>
<DropdownMenu.Item>Set due date...</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>Apply label</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent class="p-0">
<Command.Root value={selectedLabel}>
<Command.Input
autofocus
placeholder="Filter label..."
class="h-9"
/>
<Command.List>
<Command.Empty>No label found.</Command.Empty>
<Command.Group>
{#each labels as label}
<Command.Item
value={label}
onSelect={(value) => {
selectedLabel = value;
closeAndFocusTrigger(ids.trigger);
}}
>
{label}
</Command.Item>
{/each}
</Command.Group>
</Command.List>
</Command.Root>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Separator />
<DropdownMenu.Item class="text-red-600">
Delete
<DropdownMenu.Shortcut>⌘⌫</DropdownMenu.Shortcut>
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
<script lang="ts">
import { Calendar, MoreHorizontal, Tags, Trash, User } from "lucide-svelte";
import * as Command from "$lib/components/ui/command";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { Button } from "$lib/components/ui/button";
import { tick } from "svelte";
const labels = [
"feature",
"bug",
"enhancement",
"documentation",
"design",
"question",
"maintenance"
];
let open = false;
let selectedLabel = "feature";
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<div
class="flex w-full flex-col items-start justify-between rounded-md border px-4 py-3 sm:flex-row sm:items-center"
>
<p class="text-sm font-medium leading-none">
<span
class="mr-2 rounded-lg bg-primary px-2 py-1 text-xs text-primary-foreground"
>
{selectedLabel}
</span>
<span class="text-muted-foreground">Create a new project</span>
</p>
<DropdownMenu.Root
bind:open
positioning={{ placement: "bottom-end" }}
let:ids
>
<DropdownMenu.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="ghost"
size="sm"
aria-label="Open menu"
>
<MoreHorizontal />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-[200px]">
<DropdownMenu.Group>
<DropdownMenu.Label>Actions</DropdownMenu.Label>
<DropdownMenu.Item>
<User class="mr-2 h-4 w-4" />
Assign to...
</DropdownMenu.Item>
<DropdownMenu.Item>
<Calendar class="mr-2 h-4 w-4" />
Set due date...
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>
<Tags class="mr-2 h-4 w-4" />
Apply label
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent class="p-0">
<Command.Root value={selectedLabel}>
<Command.Input autofocus placeholder="Filter label..." />
<Command.List>
<Command.Empty>No label found.</Command.Empty>
<Command.Group>
{#each labels as label}
<Command.Item
value={label}
onSelect={(value) => {
selectedLabel = value;
closeAndFocusTrigger(ids.trigger);
}}
>
{label}
</Command.Item>
{/each}
</Command.Group>
</Command.List>
</Command.Root>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Separator />
<DropdownMenu.Item class="text-red-600">
<Trash class="mr-2 h-4 w-4" />
Delete
<DropdownMenu.Shortcut>⌘⌫</DropdownMenu.Shortcut>
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
Form
Since the Combobox is built using the <Popover />
and the <Command />
components, we need to use the <Form.Control />
component. <Form.Control />
enables us to apply the right aria-*
attributes to non-standard form elements, and adds a hidden input to ensure the form is submitted with the correct value.
Note: You must on version 0.3.1
or higher of formsnap
for this to work correctly.
Loading...
<script lang="ts" context="module">
import { z } from "zod";
const languages = [
{ label: "English", value: "en" },
{ label: "French", value: "fr" },
{ label: "German", value: "de" },
{ label: "Spanish", value: "es" },
{ label: "Portuguese", value: "pt" },
{ label: "Russian", value: "ru" },
{ label: "Japanese", value: "ja" },
{ label: "Korean", value: "ko" },
{ label: "Chinese", value: "zh" }
] as const;
type Language = (typeof languages)[number]["value"];
export const formSchema = z.object({
language: z.enum(
languages.map((f) => f.value) as [Language, ...Language[]],
{
errorMap: () => ({ message: "Please select a valid language." })
}
)
});
export type FormSchema = typeof formSchema;
</script>
<script lang="ts">
import { page } from "$app/stores";
import * as Form from "$lib/components/ui/form";
import { Button } from "$lib/components/ui/button";
import * as Popover from "$lib/components/ui/popover";
import * as Command from "$lib/components/ui/command";
import type { SuperValidated } from "sveltekit-superforms";
import { cn } from "@/utils";
import { tick } from "svelte";
import { Check, CaretSort } from "radix-icons-svelte";
export let form: SuperValidated<FormSchema> = $page.data.combobox;
let open = false;
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<Form.Root
{form}
schema={formSchema}
let:config
method="POST"
action="?/combobox"
class="space-y-6"
>
<Form.Field {config} name="language" let:setValue let:value>
<Form.Item class="flex flex-col">
<Form.Label>Language</Form.Label>
<Popover.Root bind:open let:ids>
<Popover.Trigger asChild let:builder>
<Form.Control id={ids.trigger} let:attrs>
<Button
{...attrs}
builders={[builder]}
variant="outline"
role="combobox"
class={cn(
"w-[200px] justify-between",
!value && "text-muted-foreground"
)}
>
{languages.find((f) => f.value === value)?.label ??
"Select language"}
<CaretSort class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</Form.Control>
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Command.Root>
<Command.Input
autofocus
placeholder="Search language..."
class="h-9"
/>
<Command.Empty>No language found.</Command.Empty>
<Command.Group>
{#each languages as language}
<Command.Item
value={language.value}
onSelect={() => {
setValue(language.value);
closeAndFocusTrigger(ids.trigger);
}}
>
{language.label}
<Check
class={cn(
"ml-auto h-4 w-4",
language.value !== value && "text-transparent"
)}
/>
</Command.Item>
{/each}
</Command.Group>
</Command.Root>
</Popover.Content>
</Popover.Root>
<Form.Description>
This is the language that will be used in the dashboard.
</Form.Description>
<Form.Validation />
</Form.Item>
</Form.Field>
<Form.Button>Submit</Form.Button>
</Form.Root>
<script lang="ts" context="module">
import { z } from "zod";
const languages = [
{ label: "English", value: "en" },
{ label: "French", value: "fr" },
{ label: "German", value: "de" },
{ label: "Spanish", value: "es" },
{ label: "Portuguese", value: "pt" },
{ label: "Russian", value: "ru" },
{ label: "Japanese", value: "ja" },
{ label: "Korean", value: "ko" },
{ label: "Chinese", value: "zh" }
] as const;
type Language = (typeof languages)[number]["value"];
export const formSchema = z.object({
language: z.enum(
languages.map((f) => f.value) as [Language, ...Language[]],
{
errorMap: () => ({ message: "Please select a valid language." })
}
)
});
export type FormSchema = typeof formSchema;
</script>
<script lang="ts">
import { page } from "$app/stores";
import * as Form from "$lib/components/ui/form";
import { Button } from "$lib/components/ui/button";
import * as Popover from "$lib/components/ui/popover";
import * as Command from "$lib/components/ui/command";
import type { SuperValidated } from "sveltekit-superforms";
import { cn } from "@/utils";
import { tick } from "svelte";
import { Check, ChevronsUpDown } from "lucide-svelte";
export let form: SuperValidated<FormSchema> = $page.data.combobox;
let open = false;
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger(triggerId: string) {
open = false;
tick().then(() => {
document.getElementById(triggerId)?.focus();
});
}
</script>
<Form.Root
{form}
schema={formSchema}
let:config
method="POST"
action="?/combobox"
class="space-y-6"
>
<Form.Field {config} name="language" let:setValue let:value>
<Form.Item class="flex flex-col">
<Form.Label>Language</Form.Label>
<Popover.Root bind:open let:ids>
<Popover.Trigger asChild let:builder>
<Form.Control id={ids.trigger} let:attrs>
<Button
builders={[builder]}
{...attrs}
variant="outline"
role="combobox"
type="button"
class={cn(
"w-[200px] justify-between",
!value && "text-muted-foreground"
)}
>
{languages.find((f) => f.value === value)?.label ??
"Select language"}
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</Form.Control>
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Command.Root>
<Command.Input autofocus placeholder="Search language..." />
<Command.Empty>No language found.</Command.Empty>
<Command.Group>
{#each languages as language}
<Command.Item
value={language.value}
onSelect={() => {
console.log("on select firing");
setValue(language.value);
closeAndFocusTrigger(ids.trigger);
}}
>
<Check
class={cn(
"mr-2 h-4 w-4",
language.value !== value && "text-transparent"
)}
/>
{language.label}
</Command.Item>
{/each}
</Command.Group>
</Command.Root>
</Popover.Content>
</Popover.Root>
<Form.Description>
This is the language that will be used in the dashboard.
</Form.Description>
<Form.Validation />
</Form.Item>
</Form.Field>
<Form.Button>Submit</Form.Button>
</Form.Root>
On This Page