Componentform1.14 KB gzipZero dependencies
Search Bar
Dashed search field with shortcut keycap and clear action.
Preview
Live Preview
⌘K
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add search-barOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { SearchBar } from "@/components/ui/search-bar";
<SearchBar placeholder="Search tickets..." shortcut="⌘K" />Usage guidance
Use Search Bar when a ticket stub surface needs Dashed search field with shortcut keycap and clear action. Keep props minimal and prefer composition with sibling stubs.
Avoid Search Bar for long form flows or dense data grids where a dedicated layout block fits better.
Source code
search-bar.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface SearchBarProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue"> {
value?: string;
defaultValue?: string;
onValueChange?: (val: string) => void;
onSearch?: (val: string) => void;
shortcut?: string;
className?: string;
}
export function SearchBar({
value: controlledValue,
defaultValue = "",
onValueChange,
onSearch,
shortcut = "⌘K",
placeholder = "Search stubs, events...",
className,
...props
}: SearchBarProps) {
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
const isControlled = controlledValue !== undefined;
const currentVal = isControlled ? controlledValue : uncontrolledValue;
const inputRef = React.useRef<HTMLInputElement>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const nextVal = e.target.value;
if (!isControlled) {
setUncontrolledValue(nextVal);
}
onValueChange?.(nextVal);
};
const handleClear = () => {
if (!isControlled) {
setUncontrolledValue("");
}
onValueChange?.("");
inputRef.current?.focus();
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
onSearch?.(currentVal);
} else if (e.key === "Escape") {
handleClear();
}
};
return (
<div
className={cn(
"relative flex h-10 w-full max-w-sm items-center rounded-md border-2 border-dashed border-border bg-card px-3 transition-colors",
"focus-within:border-foreground focus-within:ring-2 focus-within:ring-ring",
className
)}
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="shrink-0 text-muted-foreground me-2"
aria-hidden="true"
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
<input
ref={inputRef}
type="text"
value={currentVal}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder={placeholder}
className="w-full bg-transparent font-mono text-xs text-foreground placeholder:text-muted-foreground focus:outline-none"
{...props}
/>
{currentVal ? (
<button
type="button"
onClick={handleClear}
aria-label="Clear search"
className="ms-2 cursor-pointer font-mono text-xs text-muted-foreground hover:text-foreground"
>
✕
</button>
) : shortcut ? (
<kbd className="ms-2 hidden sm:inline-flex items-center rounded-sm border border-border bg-secondary px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground select-none">
{shortcut}
</kbd>
) : null}
</div>
);
}
Props
| Prop | Type | Description |
|---|---|---|
| value? | string | |
| defaultValue? | string | |
| onValueChange? | (val: string) => void | |
| onSearch? | (val: string) => void | |
| shortcut? | string | |
| className? | string |