Gooey Search

A command search whose results melt out of the input, with grouped results and full keyboard control.

Preview

Setup

Install with the shadcn CLI

$npx shadcn@latest add https://kinetikui.vercel.app/r/goo-search.json

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. 1

    Run in your terminal:

    $npm install clsx tailwind-merge framer-motion lucide-react
  2. 2

    Copy lib/utils.ts from the Code tab. Skip if you already have cn().

  3. 3

    Copy styles/tokens.css from the Code tab and import it in your global CSS after Tailwind: @import "./styles/tokens.css";

  4. 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. 5

    Import and render:

    Example
    import { 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

PropTypeDefaultNotes
itemsGooSearchItem[]—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.
linkResultsbooleanfalseRender results as links instead of buttons.
linkAsElementType"a"Component for internal links, e.g. Next's Link.
placeholderstring"Search…"Input placeholder.
initialQuerystring""Start with results already open.
maxResultsHeightnumber340The panel scrolls past this height (px).
compactbooleanfalseShorter input row for tight spaces.
labelstring"Search"Accessible name for the input.

Accessibility

  • The input is a combobox that controls a listbox; the highlighted result is exposed with aria-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