Gooey Search
A command search whose results melt out of the input, with grouped results and full keyboard control.
Preview
Type to see the results melt out of the input.
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 - 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/goo-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 { GooSearch } from "@/components/ui/goo-search" <GooSearch items={[{ title: "Button", href: "/docs/button", category: "Components" }]} onOpen={(item) => console.log(item.href)} />
Usage
The input and the results panel are drawn as two blobs behind an SVG goo filter. As the panel grows, the gap between them springs open so the results look like they pull away from the input.
import { GooSearch } from "@/components/ui/goo-search"
const items = [
{ title: "Button", href: "/docs/components/button", category: "Components" },
{ title: "Theming", href: "/docs/theming", category: "Guides" },
]
export function Search() {
return (
<GooSearch
items={items}
onOpen={(item) => console.log(item.href)}
placeholder="Search components…"
/>
)
}
To render results as real links, set linkResults and pass your router's link
component, e.g. linkAs={Link} from next/link.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
items | GooSearchItem[] | — | Searched by title, category and keywords. Results are grouped by category. |
onOpen | (item) => void | — | Runs on Enter, the open hint, or a click when results are buttons. |
onClose | () => void | — | Adds an “esc to close” hint and runs on Escape. |
linkResults | boolean | false | Render results as links instead of buttons. |
linkAs | ElementType | "a" | Component for internal links, e.g. Next's Link. |
placeholder | string | "Search…" | Input placeholder. |
initialQuery | string | "" | Start with results already open. |
maxResultsHeight | number | 340 | The panel scrolls past this height (px). |
compact | boolean | false | Shorter input row for tight spaces. |
label | string | "Search" | Accessible name for the input. |
Accessibility
- The input is a
comboboxthat controls alistbox; the highlighted result is exposed witharia-activedescendant - Arrow keys move through results across groups, Enter opens the active one
- Every keyboard hint is also a button, so the component works without a keyboard