GitHub

Combobox

Autocomplete component for selecting items from a list.

combobox-demo.tsx
import * as React from "react"
 
import {
	Combobox,
	ComboboxContent,
	ComboboxEmpty,
	ComboboxInput,
	ComboboxItem,
	ComboboxList,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"
 
export function ComboboxDemo() {
	return (
		<Combobox items={fruits}>
			<div className="relative flex flex-col gap-2">
				<Label htmlFor="select-fruit">Select a fruit</Label>
				<ComboboxInput
					className="w-80"
					id="select-fruit"
					placeholder="e.g. Apple"
				/>
			</div>
 
			<ComboboxContent>
				<ComboboxEmpty>No fruits found.</ComboboxEmpty>
				<ComboboxList>
					{(item: string) => (
						<ComboboxItem key={item} value={item}>
							{item}
						</ComboboxItem>
					)}
				</ComboboxList>
			</ComboboxContent>
		</Combobox>
	)
}
 
const fruits = [
	"Apple",
	"Banana",
	"Orange",
	"Pineapple",
	"Grape",
	"Mango",
	"Strawberry",
	"Blueberry",
	"Raspberry",
	"Blackberry",
	"Cherry",
	"Peach",
	"Pear",
	"Plum",
	"Kiwi",
	"Watermelon",
	"Cantaloupe",
	"Honeydew",
	"Papaya",
	"Guava",
	"Lychee",
	"Pomegranate",
	"Apricot",
	"Grapefruit",
	"Passionfruit",
]

Installation

npx shadcn@latest add @9ui/combobox

Usage

Imports
import {
	Combobox,
	ComboboxChip,
	ComboboxChips,
	ComboboxContent,
	ComboboxEmpty,
	ComboboxInput,
	ComboboxItem,
	ComboboxList,
	ComboboxTrigger,
	ComboboxValue,
} from "@/components/ui/combobox"
Anatomy
<Combobox>
	<ComboboxInput />
	<ComboboxTrigger />
	<ComboboxValue />
	<ComboboxChips>
		<ComboboxChip />
	</ComboboxChips>
	<ComboboxContent>
		<ComboboxEmpty />
		<ComboboxList>
			<ComboboxItem />
		</ComboboxList>
	</ComboboxContent>
</Combobox>

Examples

Input inside popup

combobox-input-inside-popup.tsx
import * as React from "react"
 
import {
	Combobox,
	ComboboxContent,
	ComboboxEmpty,
	ComboboxInput,
	ComboboxItem,
	ComboboxList,
	ComboboxTrigger,
	ComboboxValue,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"
 
export function ComboboxInputInsidePopupDemo() {
	return (
		<Combobox items={countries} defaultValue={countries[0]}>
			<div className="relative flex flex-col gap-2">
				<Label htmlFor="select-country">Select country</Label>
				<ComboboxTrigger id="select-country" className="w-80">
					<ComboboxValue />
				</ComboboxTrigger>
			</div>
			<ComboboxContent className="max-h-[20rem] [--input-container-height:4rem]">
				<ComboboxInput
					className="focus-visible:ring-0"
					inputContainerClassName="mb-2"
					placeholder="e.g. United Kingdom"
					showClear={false}
				/>
				<ComboboxEmpty>No countries found.</ComboboxEmpty>
				<ComboboxList className="max-h-[min(calc(20rem-var(--input-container-height)),calc(var(--available-height)-var(--input-container-height)))] scroll-py-2 overflow-y-auto overscroll-contain">
					{(country: Country) => (
						<ComboboxItem key={country.code} value={country}>
							{country.label ?? country.value}
						</ComboboxItem>
					)}
				</ComboboxList>
			</ComboboxContent>
		</Combobox>
	)
}
 
interface Country {
	code: string
	value: string | null
	continent: string
	label: string
}
 
const countries: Country[] = [
	{ code: "", value: null, continent: "", label: "Select country" },
	{ code: "af", value: "afghanistan", label: "Afghanistan", continent: "Asia" },
	{ code: "al", value: "albania", label: "Albania", continent: "Europe" },
	{ code: "dz", value: "algeria", label: "Algeria", continent: "Africa" },
	{ code: "ad", value: "andorra", label: "Andorra", continent: "Europe" },
	{ code: "ao", value: "angola", label: "Angola", continent: "Africa" },
	{
		code: "ar",
		value: "argentina",
		label: "Argentina",
		continent: "South America",
	},
	{ code: "am", value: "armenia", label: "Armenia", continent: "Asia" },
	{ code: "au", value: "australia", label: "Australia", continent: "Oceania" },
	{ code: "at", value: "austria", label: "Austria", continent: "Europe" },
	{ code: "az", value: "azerbaijan", label: "Azerbaijan", continent: "Asia" },
	{
		code: "bs",
		value: "bahamas",
		label: "Bahamas",
		continent: "North America",
	},
	{ code: "bh", value: "bahrain", label: "Bahrain", continent: "Asia" },
	{ code: "bd", value: "bangladesh", label: "Bangladesh", continent: "Asia" },
	{
		code: "bb",
		value: "barbados",
		label: "Barbados",
		continent: "North America",
	},
	{ code: "by", value: "belarus", label: "Belarus", continent: "Europe" },
	{ code: "be", value: "belgium", label: "Belgium", continent: "Europe" },
	{ code: "bz", value: "belize", label: "Belize", continent: "North America" },
	{ code: "bj", value: "benin", label: "Benin", continent: "Africa" },
	{ code: "bt", value: "bhutan", label: "Bhutan", continent: "Asia" },
	{
		code: "bo",
		value: "bolivia",
		label: "Bolivia",
		continent: "South America",
	},
	{
		code: "ba",
		value: "bosnia-and-herzegovina",
		label: "Bosnia and Herzegovina",
		continent: "Europe",
	},
	{ code: "bw", value: "botswana", label: "Botswana", continent: "Africa" },
	{ code: "br", value: "brazil", label: "Brazil", continent: "South America" },
	{ code: "bn", value: "brunei", label: "Brunei", continent: "Asia" },
	{ code: "bg", value: "bulgaria", label: "Bulgaria", continent: "Europe" },
	{
		code: "bf",
		value: "burkina-faso",
		label: "Burkina Faso",
		continent: "Africa",
	},
	{ code: "bi", value: "burundi", label: "Burundi", continent: "Africa" },
	{ code: "kh", value: "cambodia", label: "Cambodia", continent: "Asia" },
	{ code: "cm", value: "cameroon", label: "Cameroon", continent: "Africa" },
	{ code: "ca", value: "canada", label: "Canada", continent: "North America" },
	{ code: "cv", value: "cape-verde", label: "Cape Verde", continent: "Africa" },
	{
		code: "cf",
		value: "central-african-republic",
		label: "Central African Republic",
		continent: "Africa",
	},
	{ code: "td", value: "chad", label: "Chad", continent: "Africa" },
	{ code: "cl", value: "chile", label: "Chile", continent: "South America" },
	{ code: "cn", value: "china", label: "China", continent: "Asia" },
	{
		code: "co",
		value: "colombia",
		label: "Colombia",
		continent: "South America",
	},
	{ code: "km", value: "comoros", label: "Comoros", continent: "Africa" },
	{ code: "cg", value: "congo", label: "Congo", continent: "Africa" },
	{
		code: "cr",
		value: "costa-rica",
		label: "Costa Rica",
		continent: "North America",
	},
	{ code: "hr", value: "croatia", label: "Croatia", continent: "Europe" },
	{ code: "cu", value: "cuba", label: "Cuba", continent: "North America" },
	{ code: "cy", value: "cyprus", label: "Cyprus", continent: "Asia" },
	{
		code: "cz",
		value: "czech-republic",
		label: "Czech Republic",
		continent: "Europe",
	},
	{ code: "dk", value: "denmark", label: "Denmark", continent: "Europe" },
	{ code: "dj", value: "djibouti", label: "Djibouti", continent: "Africa" },
	{
		code: "dm",
		value: "dominica",
		label: "Dominica",
		continent: "North America",
	},
	{
		code: "do",
		value: "dominican-republic",
		label: "Dominican Republic",
		continent: "North America",
	},
	{
		code: "ec",
		value: "ecuador",
		label: "Ecuador",
		continent: "South America",
	},
	{ code: "eg", value: "egypt", label: "Egypt", continent: "Africa" },
	{
		code: "sv",
		value: "el-salvador",
		label: "El Salvador",
		continent: "North America",
	},
	{
		code: "gq",
		value: "equatorial-guinea",
		label: "Equatorial Guinea",
		continent: "Africa",
	},
	{ code: "er", value: "eritrea", label: "Eritrea", continent: "Africa" },
	{ code: "ee", value: "estonia", label: "Estonia", continent: "Europe" },
	{ code: "et", value: "ethiopia", label: "Ethiopia", continent: "Africa" },
	{ code: "fj", value: "fiji", label: "Fiji", continent: "Oceania" },
	{ code: "fi", value: "finland", label: "Finland", continent: "Europe" },
	{ code: "fr", value: "france", label: "France", continent: "Europe" },
	{ code: "ga", value: "gabon", label: "Gabon", continent: "Africa" },
	{ code: "gm", value: "gambia", label: "Gambia", continent: "Africa" },
	{ code: "ge", value: "georgia", label: "Georgia", continent: "Asia" },
	{ code: "de", value: "germany", label: "Germany", continent: "Europe" },
	{ code: "gh", value: "ghana", label: "Ghana", continent: "Africa" },
	{ code: "gr", value: "greece", label: "Greece", continent: "Europe" },
	{
		code: "gd",
		value: "grenada",
		label: "Grenada",
		continent: "North America",
	},
	{
		code: "gt",
		value: "guatemala",
		label: "Guatemala",
		continent: "North America",
	},
	{ code: "gn", value: "guinea", label: "Guinea", continent: "Africa" },
	{
		code: "gw",
		value: "guinea-bissau",
		label: "Guinea-Bissau",
		continent: "Africa",
	},
	{ code: "gy", value: "guyana", label: "Guyana", continent: "South America" },
	{ code: "ht", value: "haiti", label: "Haiti", continent: "North America" },
	{
		code: "hn",
		value: "honduras",
		label: "Honduras",
		continent: "North America",
	},
	{ code: "hu", value: "hungary", label: "Hungary", continent: "Europe" },
	{ code: "is", value: "iceland", label: "Iceland", continent: "Europe" },
	{ code: "in", value: "india", label: "India", continent: "Asia" },
	{ code: "id", value: "indonesia", label: "Indonesia", continent: "Asia" },
	{ code: "ir", value: "iran", label: "Iran", continent: "Asia" },
	{ code: "iq", value: "iraq", label: "Iraq", continent: "Asia" },
	{ code: "ie", value: "ireland", label: "Ireland", continent: "Europe" },
	{ code: "il", value: "israel", label: "Israel", continent: "Asia" },
	{ code: "it", value: "italy", label: "Italy", continent: "Europe" },
	{
		code: "jm",
		value: "jamaica",
		label: "Jamaica",
		continent: "North America",
	},
	{ code: "jp", value: "japan", label: "Japan", continent: "Asia" },
	{ code: "jo", value: "jordan", label: "Jordan", continent: "Asia" },
	{ code: "kz", value: "kazakhstan", label: "Kazakhstan", continent: "Asia" },
	{ code: "ke", value: "kenya", label: "Kenya", continent: "Africa" },
	{ code: "kw", value: "kuwait", label: "Kuwait", continent: "Asia" },
	{ code: "kg", value: "kyrgyzstan", label: "Kyrgyzstan", continent: "Asia" },
	{ code: "la", value: "laos", label: "Laos", continent: "Asia" },
	{ code: "lv", value: "latvia", label: "Latvia", continent: "Europe" },
	{ code: "lb", value: "lebanon", label: "Lebanon", continent: "Asia" },
	{ code: "ls", value: "lesotho", label: "Lesotho", continent: "Africa" },
	{ code: "lr", value: "liberia", label: "Liberia", continent: "Africa" },
	{ code: "ly", value: "libya", label: "Libya", continent: "Africa" },
	{
		code: "li",
		value: "liechtenstein",
		label: "Liechtenstein",
		continent: "Europe",
	},
	{ code: "lt", value: "lithuania", label: "Lithuania", continent: "Europe" },
	{ code: "lu", value: "luxembourg", label: "Luxembourg", continent: "Europe" },
	{ code: "mg", value: "madagascar", label: "Madagascar", continent: "Africa" },
	{ code: "mw", value: "malawi", label: "Malawi", continent: "Africa" },
	{ code: "my", value: "malaysia", label: "Malaysia", continent: "Asia" },
	{ code: "mv", value: "maldives", label: "Maldives", continent: "Asia" },
	{ code: "ml", value: "mali", label: "Mali", continent: "Africa" },
	{ code: "mt", value: "malta", label: "Malta", continent: "Europe" },
	{
		code: "mh",
		value: "marshall-islands",
		label: "Marshall Islands",
		continent: "Oceania",
	},
	{ code: "mr", value: "mauritania", label: "Mauritania", continent: "Africa" },
	{ code: "mu", value: "mauritius", label: "Mauritius", continent: "Africa" },
	{ code: "mx", value: "mexico", label: "Mexico", continent: "North America" },
	{
		code: "fm",
		value: "micronesia",
		label: "Micronesia",
		continent: "Oceania",
	},
	{ code: "md", value: "moldova", label: "Moldova", continent: "Europe" },
	{ code: "mc", value: "monaco", label: "Monaco", continent: "Europe" },
	{ code: "mn", value: "mongolia", label: "Mongolia", continent: "Asia" },
	{ code: "me", value: "montenegro", label: "Montenegro", continent: "Europe" },
	{ code: "ma", value: "morocco", label: "Morocco", continent: "Africa" },
	{ code: "mz", value: "mozambique", label: "Mozambique", continent: "Africa" },
	{ code: "mm", value: "myanmar", label: "Myanmar", continent: "Asia" },
	{ code: "na", value: "namibia", label: "Namibia", continent: "Africa" },
	{ code: "nr", value: "nauru", label: "Nauru", continent: "Oceania" },
	{ code: "np", value: "nepal", label: "Nepal", continent: "Asia" },
	{
		code: "nl",
		value: "netherlands",
		label: "Netherlands",
		continent: "Europe",
	},
	{
		code: "nz",
		value: "new-zealand",
		label: "New Zealand",
		continent: "Oceania",
	},
	{
		code: "ni",
		value: "nicaragua",
		label: "Nicaragua",
		continent: "North America",
	},
	{ code: "ne", value: "niger", label: "Niger", continent: "Africa" },
	{ code: "ng", value: "nigeria", label: "Nigeria", continent: "Africa" },
	{ code: "kp", value: "north-korea", label: "North Korea", continent: "Asia" },
	{
		code: "mk",
		value: "north-macedonia",
		label: "North Macedonia",
		continent: "Europe",
	},
	{ code: "no", value: "norway", label: "Norway", continent: "Europe" },
	{ code: "om", value: "oman", label: "Oman", continent: "Asia" },
	{ code: "pk", value: "pakistan", label: "Pakistan", continent: "Asia" },
	{ code: "pw", value: "palau", label: "Palau", continent: "Oceania" },
	{ code: "ps", value: "palestine", label: "Palestine", continent: "Asia" },
	{ code: "pa", value: "panama", label: "Panama", continent: "North America" },
	{
		code: "pg",
		value: "papua-new-guinea",
		label: "Papua New Guinea",
		continent: "Oceania",
	},
	{
		code: "py",
		value: "paraguay",
		label: "Paraguay",
		continent: "South America",
	},
	{ code: "pe", value: "peru", label: "Peru", continent: "South America" },
	{ code: "ph", value: "philippines", label: "Philippines", continent: "Asia" },
	{ code: "pl", value: "poland", label: "Poland", continent: "Europe" },
	{ code: "pt", value: "portugal", label: "Portugal", continent: "Europe" },
	{ code: "qa", value: "qatar", label: "Qatar", continent: "Asia" },
	{ code: "ro", value: "romania", label: "Romania", continent: "Europe" },
	{ code: "ru", value: "russia", label: "Russia", continent: "Europe" },
	{ code: "rw", value: "rwanda", label: "Rwanda", continent: "Africa" },
	{ code: "ws", value: "samoa", label: "Samoa", continent: "Oceania" },
	{ code: "sm", value: "san-marino", label: "San Marino", continent: "Europe" },
	{
		code: "sa",
		value: "saudi-arabia",
		label: "Saudi Arabia",
		continent: "Asia",
	},
	{ code: "sn", value: "senegal", label: "Senegal", continent: "Africa" },
	{ code: "rs", value: "serbia", label: "Serbia", continent: "Europe" },
	{ code: "sc", value: "seychelles", label: "Seychelles", continent: "Africa" },
	{
		code: "sl",
		value: "sierra-leone",
		label: "Sierra Leone",
		continent: "Africa",
	},
	{ code: "sg", value: "singapore", label: "Singapore", continent: "Asia" },
	{ code: "sk", value: "slovakia", label: "Slovakia", continent: "Europe" },
	{ code: "si", value: "slovenia", label: "Slovenia", continent: "Europe" },
	{
		code: "sb",
		value: "solomon-islands",
		label: "Solomon Islands",
		continent: "Oceania",
	},
	{ code: "so", value: "somalia", label: "Somalia", continent: "Africa" },
	{
		code: "za",
		value: "south-africa",
		label: "South Africa",
		continent: "Africa",
	},
	{ code: "kr", value: "south-korea", label: "South Korea", continent: "Asia" },
	{
		code: "ss",
		value: "south-sudan",
		label: "South Sudan",
		continent: "Africa",
	},
	{ code: "es", value: "spain", label: "Spain", continent: "Europe" },
	{ code: "lk", value: "sri-lanka", label: "Sri Lanka", continent: "Asia" },
	{ code: "sd", value: "sudan", label: "Sudan", continent: "Africa" },
	{
		code: "sr",
		value: "suriname",
		label: "Suriname",
		continent: "South America",
	},
	{ code: "se", value: "sweden", label: "Sweden", continent: "Europe" },
	{
		code: "ch",
		value: "switzerland",
		label: "Switzerland",
		continent: "Europe",
	},
	{ code: "sy", value: "syria", label: "Syria", continent: "Asia" },
	{ code: "tw", value: "taiwan", label: "Taiwan", continent: "Asia" },
	{ code: "tj", value: "tajikistan", label: "Tajikistan", continent: "Asia" },
	{ code: "tz", value: "tanzania", label: "Tanzania", continent: "Africa" },
	{ code: "th", value: "thailand", label: "Thailand", continent: "Asia" },
	{ code: "tl", value: "timor-leste", label: "Timor-Leste", continent: "Asia" },
	{ code: "tg", value: "togo", label: "Togo", continent: "Africa" },
	{ code: "to", value: "tonga", label: "Tonga", continent: "Oceania" },
	{
		code: "tt",
		value: "trinidad-and-tobago",
		label: "Trinidad and Tobago",
		continent: "North America",
	},
	{ code: "tn", value: "tunisia", label: "Tunisia", continent: "Africa" },
	{ code: "tr", value: "turkey", label: "Turkey", continent: "Asia" },
	{
		code: "tm",
		value: "turkmenistan",
		label: "Turkmenistan",
		continent: "Asia",
	},
	{ code: "tv", value: "tuvalu", label: "Tuvalu", continent: "Oceania" },
	{ code: "ug", value: "uganda", label: "Uganda", continent: "Africa" },
	{ code: "ua", value: "ukraine", label: "Ukraine", continent: "Europe" },
	{
		code: "ae",
		value: "united-arab-emirates",
		label: "United Arab Emirates",
		continent: "Asia",
	},
	{
		code: "gb",
		value: "united-kingdom",
		label: "United Kingdom",
		continent: "Europe",
	},
	{
		code: "us",
		value: "united-states",
		label: "United States",
		continent: "North America",
	},
	{
		code: "uy",
		value: "uruguay",
		label: "Uruguay",
		continent: "South America",
	},
	{ code: "uz", value: "uzbekistan", label: "Uzbekistan", continent: "Asia" },
	{ code: "vu", value: "vanuatu", label: "Vanuatu", continent: "Oceania" },
	{
		code: "va",
		value: "vatican-city",
		label: "Vatican City",
		continent: "Europe",
	},
	{
		code: "ve",
		value: "venezuela",
		label: "Venezuela",
		continent: "South America",
	},
	{ code: "vn", value: "vietnam", label: "Vietnam", continent: "Asia" },
	{ code: "ye", value: "yemen", label: "Yemen", continent: "Asia" },
	{ code: "zm", value: "zambia", label: "Zambia", continent: "Africa" },
	{ code: "zw", value: "zimbabwe", label: "Zimbabwe", continent: "Africa" },
]

Multiple

combobox-multiple.tsx
import * as React from "react"
 
import {
	Combobox,
	ComboboxChip,
	ComboboxChips,
	ComboboxContent,
	ComboboxEmpty,
	ComboboxInput,
	ComboboxItem,
	ComboboxList,
	ComboboxValue,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"
 
export function ComboboxMultipleDemo() {
	const containerRef = React.useRef<HTMLDivElement | null>(null)
 
	return (
		<Combobox items={langs} multiple>
			<div className="flex w-80 flex-col gap-2">
				<Label htmlFor="select-language">Select a language</Label>
				<ComboboxChips ref={containerRef}>
					<ComboboxValue>
						{(value: ProgrammingLanguage[]) => (
							<React.Fragment>
								{value.length > 0 && (
									<div className="flex flex-wrap gap-1 p-1">
										{value.map((language) => (
											<ComboboxChip
												key={language.id}
												aria-label={language.value}
											>
												{language.value}
											</ComboboxChip>
										))}
									</div>
								)}
								<ComboboxInput
									id="select-language"
									placeholder="e.g. TypeScript"
									showClear={false}
									multiple
								/>
							</React.Fragment>
						)}
					</ComboboxValue>
				</ComboboxChips>
			</div>
 
			<ComboboxContent anchor={containerRef}>
				<ComboboxEmpty>No languages found.</ComboboxEmpty>
				<ComboboxList>
					{(language: ProgrammingLanguage) => (
						<ComboboxItem key={language.id} value={language}>
							{language.value}
						</ComboboxItem>
					)}
				</ComboboxList>
			</ComboboxContent>
		</Combobox>
	)
}
 
interface ProgrammingLanguage {
	id: string
	value: string
}
 
const langs: ProgrammingLanguage[] = [
	{ id: "js", value: "JavaScript" },
	{ id: "ts", value: "TypeScript" },
	{ id: "py", value: "Python" },
	{ id: "java", value: "Java" },
	{ id: "cpp", value: "C++" },
	{ id: "cs", value: "C#" },
	{ id: "php", value: "PHP" },
	{ id: "ruby", value: "Ruby" },
	{ id: "go", value: "Go" },
	{ id: "rust", value: "Rust" },
	{ id: "swift", value: "Swift" },
]

Creatable

combobox-creatable.tsx
import * as React from "react"
import { Plus } from "lucide-react"
 
import { Button } from "@/components/ui/button"
import {
	Combobox,
	ComboboxChip,
	ComboboxChips,
	ComboboxContent,
	ComboboxEmpty,
	ComboboxInput,
	ComboboxItem,
	ComboboxList,
	ComboboxValue,
} from "@/components/ui/combobox"
import {
	Dialog,
	DialogContent,
	DialogDescription,
	DialogFooter,
	DialogTitle,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
 
export function CreatableComboboxDemo() {
	const id = React.useId()
 
	const [labels, setLabels] = React.useState<LabelItem[]>(initialLabels)
	const [selected, setSelected] = React.useState<LabelItem[]>([])
	const [query, setQuery] = React.useState("")
	const [openDialog, setOpenDialog] = React.useState(false)
 
	const containerRef = React.useRef<HTMLDivElement | null>(null)
	const createInputRef = React.useRef<HTMLInputElement | null>(null)
	const pendingQueryRef = React.useRef("")
 
	function handleCreate() {
		const input = createInputRef.current
		const value = input ? input.value.trim() : ""
		if (!value) {
			return
		}
 
		const normalized = value.toLocaleLowerCase()
		const baseId = normalized.replace(/\s+/g, "-")
		const existing = labels.find(
			(l) => l.value.trim().toLocaleLowerCase() === normalized
		)
 
		if (existing) {
			setSelected((prev) =>
				prev.some((i) => i.id === existing.id) ? prev : [...prev, existing]
			)
			setOpenDialog(false)
			setQuery("")
			return
		}
 
		// Ensure we don't collide with an existing id (e.g., value "docs" vs. existing id "docs")
		const existingIds = new Set(labels.map((l) => l.id))
		let uniqueId = baseId
		if (existingIds.has(uniqueId)) {
			let i = 2
			while (existingIds.has(`${baseId}-${i}`)) {
				i += 1
			}
			uniqueId = `${baseId}-${i}`
		}
 
		const newItem: LabelItem = { id: uniqueId, value }
 
		if (!selected.find((item) => item.id === newItem.id)) {
			setLabels((prev) => [...prev, newItem])
			setSelected((prev) => [...prev, newItem])
		}
 
		setOpenDialog(false)
		setQuery("")
	}
 
	function handleCreateSubmit(event: React.FormEvent<HTMLFormElement>) {
		event.preventDefault()
		handleCreate()
	}
 
	const trimmed = query.trim()
	const lowered = trimmed.toLocaleLowerCase()
	const exactExists = labels.some(
		(l) => l.value.trim().toLocaleLowerCase() === lowered
	)
	// Show the creatable item alongside matches if there's no exact match
	const itemsForView: Array<LabelItem> =
		trimmed !== "" && !exactExists
			? [
					...labels,
					{
						creatable: trimmed,
						id: `create:${lowered}`,
						value: `Create "${trimmed}"`,
					},
				]
			: labels
 
	return (
		<React.Fragment>
			<Combobox
				items={itemsForView}
				multiple
				onValueChange={(value) => {
					const next = value as LabelItem[]
					const last = next[next.length - 1]
					if (last && last.creatable) {
						pendingQueryRef.current = last.creatable
						setOpenDialog(true)
						return
					}
					const clean = next.filter((i: LabelItem) => !i.creatable)
					setSelected(clean)
					setQuery("")
				}}
				value={selected}
				inputValue={query}
				onInputValueChange={setQuery}
				onOpenChange={(open, details) => {
					if ("key" in details.event && details.event.key === "Enter") {
						// When pressing Enter:
						// - If the typed value exactly matches an existing item, add that item to the selected chips
						// - Otherwise, create a new item
						if (trimmed === "") {
							return
						}
 
						const existing = labels.find(
							(l) => l.value.trim().toLocaleLowerCase() === lowered
						)
 
						if (existing) {
							setSelected((prev) =>
								prev.some((i) => i.id === existing.id)
									? prev
									: [...prev, existing]
							)
							setQuery("")
							return
						}
 
						pendingQueryRef.current = trimmed
						setOpenDialog(true)
					}
				}}
			>
				<div className="flex w-80 flex-col gap-2">
					<Label htmlFor={id}>Labels</Label>
					<ComboboxChips ref={containerRef}>
						<ComboboxValue>
							{(value: LabelItem[]) => (
								<React.Fragment>
									{value.length > 0 && (
										<div className="flex flex-wrap gap-1 p-1">
											{value.map((label) => (
												<ComboboxChip key={label.id} aria-label={label.value}>
													{label.value}
												</ComboboxChip>
											))}
										</div>
									)}
									<ComboboxInput
										id={id}
										placeholder="e.g. bug"
										showClear={false}
										multiple
									/>
								</React.Fragment>
							)}
						</ComboboxValue>
					</ComboboxChips>
				</div>
 
				<ComboboxContent anchor={containerRef}>
					<ComboboxEmpty>No labels found.</ComboboxEmpty>
					<ComboboxList>
						{(item: LabelItem) =>
							item.creatable ? (
								<ComboboxItem key={item.id} value={item}>
									<Plus className="size-4" />
									<span>Create &quot;{item.creatable}&quot;</span>
								</ComboboxItem>
							) : (
								<ComboboxItem key={item.id} value={item}>
									{item.value}
								</ComboboxItem>
							)
						}
					</ComboboxList>
				</ComboboxContent>
			</Combobox>
 
			<Dialog open={openDialog} onOpenChange={setOpenDialog}>
				<DialogContent initialFocus={createInputRef}>
					<DialogTitle>Create new label</DialogTitle>
					<DialogDescription>Add a new label to select.</DialogDescription>
					<form onSubmit={handleCreateSubmit} className="space-y-4">
						<Input
							ref={createInputRef}
							placeholder="Label name"
							defaultValue={pendingQueryRef.current}
						/>
						<DialogFooter>
							<Button
								type="button"
								variant="outline"
								onClick={() => setOpenDialog(false)}
							>
								Cancel
							</Button>
							<Button type="submit">Create</Button>
						</DialogFooter>
					</form>
				</DialogContent>
			</Dialog>
		</React.Fragment>
	)
}
 
interface LabelItem {
	creatable?: string
	id: string
	value: string
}
 
const initialLabels: LabelItem[] = [
	{ id: "bug", value: "bug" },
	{ id: "docs", value: "documentation" },
	{ id: "enhancement", value: "enhancement" },
	{ id: "help-wanted", value: "help wanted" },
	{ id: "good-first-issue", value: "good first issue" },
]