Combobox
Autocomplete input and command palette with a list of suggestions.
shadcn/ui
This component uses shadcn/uiDocs
Loading...
'use client'
import { Check } from 'lucide-react'
import * as React from 'react'
import {
ComboboxTrigger,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
Popover,
PopoverContent,
PopoverTrigger,
} from 'ui'
import { cn } from '@/lib/utils'
const frameworks = [
{
value: 'next.js',
label: 'Next.js',
},
{
value: 'sveltekit',
label: 'SvelteKit',
},
{
value: 'nuxt.js',
label: 'Nuxt.js',
},
{
value: 'remix',
label: 'Remix',
},
{
value: 'astro',
label: 'Astro',
},
]
export function ComboboxDemo() {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState('')
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<ComboboxTrigger
aria-expanded={open}
data-state={open ? 'open' : 'closed'}
className={cn('w-[200px]', !value && 'text-foreground-lighter')}
>
{value
? frameworks.find((framework) => framework.value === value)?.label
: 'Select framework...'}
</ComboboxTrigger>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search framework..." />
<CommandList>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks.map((framework) => (
<CommandItem
key={framework.value}
value={framework.value}
onSelect={(currentValue) => {
setValue(currentValue === value ? '' : currentValue)
setOpen(false)
}}
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === framework.value ? 'opacity-100' : 'opacity-0'
)}
/>
{framework.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}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
'use client'
import { Check } from 'lucide-react'
import * as React from 'react'
import {
ComboboxTrigger,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
Popover,
PopoverContent,
PopoverTrigger,
} from 'ui'
import { cn } from '@/lib/utils'
const frameworks = [
{
value: 'next.js',
label: 'Next.js',
},
{
value: 'sveltekit',
label: 'SvelteKit',
},
{
value: 'nuxt.js',
label: 'Nuxt.js',
},
{
value: 'remix',
label: 'Remix',
},
{
value: 'astro',
label: 'Astro',
},
]
export function ComboboxDemo() {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState('')
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<ComboboxTrigger
aria-expanded={open}
data-state={open ? 'open' : 'closed'}
className={cn('w-[200px]', !value && 'text-foreground-lighter')}
>
{value
? frameworks.find((framework) => framework.value === value)?.label
: 'Select framework...'}
</ComboboxTrigger>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search framework..." />
<CommandList>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks.map((framework) => (
<CommandItem
key={framework.value}
value={framework.value}
onSelect={(currentValue) => {
setValue(currentValue === value ? '' : currentValue)
setOpen(false)
}}
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === framework.value ? 'opacity-100' : 'opacity-0'
)}
/>
{framework.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}Examples
Combobox
Loading...
'use client'
import { Check } from 'lucide-react'
import * as React from 'react'
import {
ComboboxTrigger,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
Popover,
PopoverContent,
PopoverTrigger,
} from 'ui'
import { cn } from '@/lib/utils'
const frameworks = [
{
value: 'next.js',
label: 'Next.js',
},
{
value: 'sveltekit',
label: 'SvelteKit',
},
{
value: 'nuxt.js',
label: 'Nuxt.js',
},
{
value: 'remix',
label: 'Remix',
},
{
value: 'astro',
label: 'Astro',
},
]
export function ComboboxDemo() {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState('')
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<ComboboxTrigger
aria-expanded={open}
data-state={open ? 'open' : 'closed'}
className={cn('w-[200px]', !value && 'text-foreground-lighter')}
>
{value
? frameworks.find((framework) => framework.value === value)?.label
: 'Select framework...'}
</ComboboxTrigger>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search framework..." />
<CommandList>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks.map((framework) => (
<CommandItem
key={framework.value}
value={framework.value}
onSelect={(currentValue) => {
setValue(currentValue === value ? '' : currentValue)
setOpen(false)
}}
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === framework.value ? 'opacity-100' : 'opacity-0'
)}
/>
{framework.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}Popover
Loading...
Dropdown menu
Loading...
Responsive
You can create a responsive combobox by using the <Popover /> on desktop and the <Drawer /> components on mobile.
Loading...
Form
Loading...
Create from list
When users can create an option while choosing one, give the action a clear verb label and place it at the bottom of the list, separated from the existing options. Keep the current selection in place while they create the new option.
Loading...
'use client'
import { Check, Plus } from 'lucide-react'
import { useState } from 'react'
import {
ComboboxTrigger,
Command,
CommandGroup,
CommandItem,
CommandList,
CommandSeparator,
Popover,
PopoverContent,
PopoverTrigger,
} from 'ui'
const buckets = ['Images', 'Exports']
export function ComboboxCreateOption() {
const [selectedBucket, setSelectedBucket] = useState('')
const [isListOpen, setIsListOpen] = useState(false)
return (
<Popover open={isListOpen} onOpenChange={setIsListOpen}>
<PopoverTrigger asChild>
<ComboboxTrigger
aria-expanded={isListOpen}
data-state={isListOpen ? 'open' : 'closed'}
className="w-[240px]"
>
{selectedBucket || 'Select a bucket'}
</ComboboxTrigger>
</PopoverTrigger>
<PopoverContent className="w-[240px] p-0" align="start">
<Command>
<CommandList>
<CommandGroup>
{buckets.map((bucket) => (
<CommandItem
key={bucket}
value={bucket}
className="cursor-pointer justify-between"
onSelect={() => {
setSelectedBucket(bucket)
setIsListOpen(false)
}}
>
{bucket}
{selectedBucket === bucket && <Check size={14} />}
</CommandItem>
))}
</CommandGroup>
<CommandSeparator />
<CommandGroup>
<CommandItem
className="cursor-pointer"
onSelect={() => {
setIsListOpen(false)
// Open the creation flow here without changing the selected bucket.
}}
>
<Plus size={14} className="mr-2" />
New bucket
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}