Simple Search
The clean ⌘K search this site uses, with a rotating placeholder, results that blur in and a filter menu.
Preview
Click the search button, type, or use the filter.
Setup
Install with the shadcn CLI
One command adds every file, the npm packages and any Kinetik components it builds on. Needs a components.json; the CLI offers to create one if your project has none.
Or install manually
- 1
Run in your terminal:
$npm install clsx tailwind-merge framer-motion lucide-react tw-animate-css - 2
Copy
lib/utils.tsfrom the Code tab. Skip if you already havecn(). - 3
Copy
styles/tokens.cssfrom the Code tab and import it in your global CSS after Tailwind:@import "./styles/tokens.css"; - 4
Create a
components/ui/simple-search/folder and copy every file from the Code tab into it. Each file does one job, so you can read or change a piece without the rest. - 5
Import and render:
Exampleimport { SimpleSearch } from "@/components/ui/simple-search" <SimpleSearch items={[{ title: "Button", href: "/docs/button", group: "Components" }]} onSelect={(item) => router.push(item.href!)} />
Usage
This is the same search as the one in this site's navbar. Press ⌘K / Ctrl K to try it on real pages.
Pass it a flat list of items. Each item belongs to a group, which is used for the
result headings and the filter menu. While the field is empty, the placeholder cycles
through your item titles.
import { useRouter } from "next/navigation"
import { SimpleSearch, type SimpleSearchItem } from "@/components/ui/simple-search"
const items: SimpleSearchItem[] = [
{ title: "Home", href: "/", group: "Pages" },
{ title: "Button", href: "/docs/button", group: "Components", badge: "New" },
{ title: "Theming", href: "/docs/theming", group: "Guides", description: "Colours and dark mode" },
]
export function Search() {
const router = useRouter()
return <SimpleSearch items={items} onSelect={(item) => item.href && router.push(item.href)} />
}
To open it from your own button, control it with open and onOpenChange and set
showTrigger={false}.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
items | SimpleSearchItem[] | — | Matched by title, group, description and keywords. |
onSelect | (item) => void | — | Runs on Enter or click, after the dialog closes. |
hotkey | string | false | "k" | Letter that toggles the dialog with ⌘ / Ctrl. |
open | boolean | — | Controlled open state. Pair with onOpenChange. |
onOpenChange | (open) => void | — | Runs whenever the dialog opens or closes. |
showTrigger | boolean | true | Render the built-in search button. |
showFilter | boolean | true | Show the group filter in the footer. |
onFilterChange | (group, direction) => void | — | Hook for analytics or sounds when the filter changes. |
label | string | "Search" | Accessible name for the dialog and input. |
inputProps | InputHTMLAttributes | — | Extra props for the input, e.g. data attributes. |
| SimpleSearchItem | Type | Notes |
|---|---|---|
title | string | Shown and searched. |
group | string | Result heading and filter option. |
href | string? | Not used by the component; handy in onSelect. |
description | string? | Second line, also searched. |
badge | string? | Small pill after the title, e.g. New. |
keywords | string? | Extra hidden words to match. |
Accessibility
- The input is a
comboboxthat controls alistbox; the highlighted result is exposed witharia-activedescendant - ↑ ↓ move through results across groups, Enter opens, Escape closes and returns focus to the trigger
- The filter menu is its own
listboxwith arrow keys; Escape closes just the menu - Animations are skipped when the visitor prefers reduced motion