Autocomplete
An input that suggests options as you type.
"use client";
import { useState } from "react";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
AutocompletePortal,
AutocompletePositioner,
} from "@/components/ui/autocomplete/autocomplete";
import { Badge } from "@/components/ui/badge/badge";
import styles from "./autocomplete-demo.module.css";
type Question = {
value: string;
question: string;
category: string;
};
const questions: Question[] = [
{
value: "reset-password",
question: "How do I reset my password?",
category: "Account",
},
{
value: "payment-methods",
question: "What payment methods do you accept?",
category: "Billing",
},
{
value: "cancel-subscription",
question: "How do I cancel my subscription?",
category: "Billing",
},
{ value: "export-data", question: "Can I export my data?", category: "Data" },
{
value: "api-access",
question: "How do I get API access?",
category: "Developer",
},
{
value: "contact-support",
question: "How do I contact support?",
category: "Support",
},
{
value: "refund-policy",
question: "What is your refund policy?",
category: "Billing",
},
{
value: "data-privacy",
question: "How is my data protected?",
category: "Privacy",
},
{
value: "team-members",
question: "How do I add team members?",
category: "Account",
},
{
value: "integrations",
question: "What integrations are available?",
category: "Features",
},
];
export default function AutocompleteDemo() {
const [value, setValue] = useState("");
return (
<div className={styles.container}>
<label className={styles.label} htmlFor="ac-input">
Search for help or ask a question
</label>
<Autocomplete
items={questions}
itemToStringValue={(item) => (item as Question).question}
onValueChange={setValue}
value={value}
>
<AutocompleteInput className={styles.input} id="ac-input" placeholder="Type your question or search FAQs..." />
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteEmpty>No matching questions found. Type your own question!</AutocompleteEmpty>
<AutocompleteList>
{(question: Question) => (
<AutocompleteItem key={question.value} value={question}>
<div className={styles.itemContainer}>
<div className={styles.itemInfo}>
<div className={styles.itemName}>{question.question}</div>
</div>
<Badge className={styles.badge} size="sm" variant="outline">
{question.category}
</Badge>
</div>
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
);
}
npx shadcn@latest add @roiui/autocompletenpx shadcn@latest add @roiui/autocomplete-tailwindanatomy
<Autocomplete>
<AutocompleteInput />
<AutocompleteTrigger />
<AutocompleteIcon />
<AutocompleteClear />
<AutocompleteValue />
<AutocompletePortal>
<AutocompleteBackdrop />
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteArrow />
<AutocompleteStatus />
<AutocompleteEmpty />
<AutocompleteList>
<AutocompleteRow>
<AutocompleteItem />
</AutocompleteRow>
<AutocompleteSeparator />
<AutocompleteGroup>
<AutocompleteGroupLabel />
<AutocompleteCollection />
</AutocompleteGroup>
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
"use client";
import { useState } from "react";
import {
Autocomplete,
AutocompleteClear,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
AutocompletePortal,
AutocompletePositioner,
} from "@/components/ui/autocomplete/autocomplete";
import styles from "./autocomplete-clearable.module.css";
const frameworks = [
{ value: "next", label: "Next.js" },
{ value: "remix", label: "Remix" },
{ value: "astro", label: "Astro" },
{ value: "nuxt", label: "Nuxt" },
{ value: "sveltekit", label: "SvelteKit" },
{ value: "gatsby", label: "Gatsby" },
];
export default function AutocompleteClearable() {
const [value, setValue] = useState("");
return (
<div className={styles.container}>
<label className={styles.label} htmlFor="clearable-input">
Select a framework
</label>
<Autocomplete
items={frameworks}
itemToStringValue={(item) => (item as (typeof frameworks)[0]).label}
onValueChange={setValue}
value={value}
>
<div className={styles.inputWrapper}>
<AutocompleteInput className={styles.input} id="clearable-input" placeholder="Search frameworks..." />
<AutocompleteClear />
</div>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup className={styles.popup}>
<AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
<AutocompleteList>
{(item: (typeof frameworks)[0]) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
);
}
"use client";
import { useState } from "react";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
AutocompletePortal,
AutocompletePositioner,
} from "@/components/ui/autocomplete/autocomplete";
import styles from "./autocomplete-auto-highlight.module.css";
const countries = [
{ value: "us", label: "United States" },
{ value: "uk", label: "United Kingdom" },
{ value: "ca", label: "Canada" },
{ value: "au", label: "Australia" },
{ value: "de", label: "Germany" },
{ value: "fr", label: "France" },
{ value: "jp", label: "Japan" },
{ value: "br", label: "Brazil" },
];
export default function AutocompleteAutoHighlight() {
const [value, setValue] = useState("");
return (
<div className={styles.container}>
<label className={styles.label} htmlFor="auto-highlight-input">
Select a country
</label>
<Autocomplete
autoHighlight
items={countries}
itemToStringValue={(item) => (item as (typeof countries)[0]).label}
onValueChange={setValue}
value={value}
>
<AutocompleteInput className={styles.input} id="auto-highlight-input" placeholder="Start typing..." />
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteEmpty>No countries found.</AutocompleteEmpty>
<AutocompleteList>
{(item: (typeof countries)[0]) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
);
}
"use client";
import { matchSorter } from "match-sorter";
import type React from "react";
import { useState } from "react";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
AutocompletePortal,
AutocompletePositioner,
AutocompleteValue,
} from "@/components/ui/autocomplete/autocomplete";
import styles from "./autocomplete-fuzzy.module.css";
interface Question {
value: string;
question: string;
category: string;
}
const questions: Question[] = [
{
value: "reset-password",
question: "How do I reset my password?",
category: "Account",
},
{
value: "payment-methods",
question: "What payment methods do you accept?",
category: "Billing",
},
{
value: "cancel-subscription",
question: "How do I cancel my subscription?",
category: "Billing",
},
{
value: "export-data",
question: "Can I export my data?",
category: "Data",
},
{
value: "api-access",
question: "How do I get API access?",
category: "Developer",
},
{
value: "contact-support",
question: "How do I contact support?",
category: "Support",
},
{
value: "refund-policy",
question: "What is your refund policy?",
category: "Billing",
},
{
value: "data-privacy",
question: "How is my data protected?",
category: "Privacy",
},
{
value: "team-members",
question: "How do I add team members?",
category: "Account",
},
{
value: "integrations",
question: "What integrations are available?",
category: "Features",
},
];
function highlightText(text: string, query: string): React.ReactNode {
const trimmed = query.trim().toLowerCase();
if (!trimmed) {
return text;
}
const textLower = text.toLowerCase();
const result: React.ReactNode[] = [];
let queryIndex = 0;
for (let i = 0; i < text.length; i++) {
if (queryIndex < trimmed.length && textLower[i] === trimmed[queryIndex]) {
result.push(
<mark className={styles.highlight} key={i}>
{text[i]}
</mark>
);
queryIndex++;
} else {
result.push(text[i]);
}
}
return result;
}
function fuzzyFilter(itemValue: unknown, query: string): boolean {
if (!query) {
return true;
}
const item = itemValue as Question;
const results = matchSorter([item], query, {
keys: [
"question",
"category",
{ key: "question", threshold: matchSorter.rankings.CONTAINS },
{ key: "category", threshold: matchSorter.rankings.WORD_STARTS_WITH },
],
});
return results.length > 0;
}
export default function AutocompleteFuzzy() {
const [value, setValue] = useState("");
return (
<div className={styles.container}>
<label className={styles.label} htmlFor="fuzzy-input">
Search for help
</label>
<Autocomplete
filter={fuzzyFilter}
items={questions}
itemToStringValue={(item) => (item as Question).question}
onValueChange={setValue}
value={value}
>
<AutocompleteInput className={styles.input} id="fuzzy-input" placeholder="Try 'billing' or 'password'..." />
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteEmpty className={styles.empty}>
No results found for "<AutocompleteValue />"
</AutocompleteEmpty>
<AutocompleteList>
{(item: Question) => (
<AutocompleteItem className={styles.item} key={item.value} value={item}>
<AutocompleteValue>
{(inputValue) => (
<div className={styles.itemContent}>
<div className={styles.itemQuestion}>{highlightText(item.question, inputValue)}</div>
<div className={styles.itemCategory}>{highlightText(item.category, inputValue)}</div>
</div>
)}
</AutocompleteValue>
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
);
}
"use client";
import { useState } from "react";
import {
Autocomplete,
AutocompleteEmpty,
AutocompleteGroup,
AutocompleteGroupLabel,
AutocompleteInput,
AutocompleteItem,
AutocompleteList,
AutocompletePopup,
AutocompletePortal,
AutocompletePositioner,
} from "@/components/ui/autocomplete/autocomplete";
import styles from "./autocomplete-grouped.module.css";
type Item = {
value: string;
label: string;
};
type Group = {
value: string;
items: Item[];
};
const groups: Group[] = [
{
value: "Fruits",
items: [
{ value: "apple", label: "Apple" },
{ value: "banana", label: "Banana" },
{ value: "orange", label: "Orange" },
],
},
{
value: "Vegetables",
items: [
{ value: "carrot", label: "Carrot" },
{ value: "broccoli", label: "Broccoli" },
{ value: "spinach", label: "Spinach" },
],
},
{
value: "Dairy",
items: [
{ value: "milk", label: "Milk" },
{ value: "cheese", label: "Cheese" },
{ value: "yogurt", label: "Yogurt" },
],
},
];
export default function AutocompleteGrouped() {
const [value, setValue] = useState("");
return (
<div className={styles.container}>
<label className={styles.label} htmlFor="grouped-input">
Select an item
</label>
<Autocomplete
items={groups}
itemToStringValue={(item) => (item as Item).label}
onValueChange={setValue}
value={value}
>
<AutocompleteInput className={styles.input} id="grouped-input" placeholder="Search items..." />
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteEmpty>No items found.</AutocompleteEmpty>
<AutocompleteList>
{(group: Group) => (
<AutocompleteGroup key={group.value}>
<AutocompleteGroupLabel>{group.value}</AutocompleteGroupLabel>
{group.items.map((item) => (
<AutocompleteItem key={item.value} value={item}>
{item.label}
</AutocompleteItem>
))}
</AutocompleteGroup>
)}
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
);
}