Live Playground
Search and sort rows in the live playground while the installed component stays reusable and data-agnostic.
Ada Lovelace
Engineer
Active
$4,200
Alan Turing
Researcher
Active
$5,800
Grace Hopper
Architect
Pending
$3,100
Linus Torvalds
Maintainer
Archived
$2,750
Margaret Hamilton
Lead
Active
$6,400
5 of 5 entries
Install And Iterate
Install the component directly into your codebase, then branch into v0 if you want to iterate on variations.
Install
Build with v0
Send the registry bundle into v0 when you want to explore new colorways, copy, or layout directions quickly.
Usage
Use the canonical `Table` primitives for production data, then add your own filtering, sorting, and row actions around them in app code.
tsx
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
TableSortButton,
TableToolbar,
} from "@/components/ui/table";
import { Search } from "lucide-react";
import { useMemo, useState } from "react";
type Row = {
id: string;
name: string;
role: string;
status: "Active" | "Pending" | "Archived";
amount: number;
};
type SortKey = keyof Pick<Row, "name" | "role" | "status" | "amount">;
const rows: Row[] = [
{ id: "1", name: "Ada Lovelace", role: "Engineer", status: "Active", amount: 4200 },
{ id: "2", name: "Grace Hopper", role: "Architect", status: "Pending", amount: 3100 },
];
export function RevenueTable() {
const [query, setQuery] = useState("");
const [sort, setSort] = useState<{ key: SortKey; dir: "asc" | "desc" }>({
key: "name",
dir: "asc",
});
const visible = useMemo(() => {
const filtered = rows.filter((row) =>
(row.name + row.role + row.status).toLowerCase().includes(query.toLowerCase())
);
return [...filtered].sort((a, b) => {
const av = a[sort.key];
const bv = b[sort.key];
if (av < bv) return sort.dir === "asc" ? -1 : 1;
if (av > bv) return sort.dir === "asc" ? 1 : -1;
return 0;
});
}, [query, sort]);
const toggleSort = (key: SortKey) =>
setSort((current) => ({
key,
dir: current.key === key && current.dir === "asc" ? "desc" : "asc",
}));
return (
<div className="w-full max-w-4xl">
<TableToolbar>
<div className="relative max-w-sm flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<input
className="h-10 w-full border-b border-border bg-transparent pl-9 pr-3 text-sm outline-none transition-colors focus:border-foreground"
onChange={(event) => setQuery(event.target.value)}
placeholder="Search…"
value={query}
/>
</div>
</TableToolbar>
<Table>
<TableHeader>
<TableRow variant="header">
<TableHead>
<TableSortButton
active={sort.key === "name"}
direction={sort.dir}
onClick={() => toggleSort("name")}
>
Name
</TableSortButton>
</TableHead>
<TableHead>
<TableSortButton
active={sort.key === "role"}
direction={sort.dir}
onClick={() => toggleSort("role")}
>
Role
</TableSortButton>
</TableHead>
<TableHead>
<TableSortButton
active={sort.key === "status"}
direction={sort.dir}
onClick={() => toggleSort("status")}
>
Status
</TableSortButton>
</TableHead>
<TableHead align="right">
<TableSortButton
active={sort.key === "amount"}
align="right"
direction={sort.dir}
onClick={() => toggleSort("amount")}
>
Amount
</TableSortButton>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{visible.map((row, index) => (
<TableRow index={index} key={row.id}>
<TableCell className="font-medium text-foreground">
{row.name}
</TableCell>
<TableCell className="text-muted-foreground">{row.role}</TableCell>
<TableCell>{row.status}</TableCell>
<TableCell align="right" className="tabular-nums text-foreground">
${row.amount.toLocaleString()}
</TableCell>
</TableRow>
))}
</TableBody>
<TableCaption>{visible.length} revenue entries</TableCaption>
</Table>
</div>
);
}API Details
Each item below covers the documented props and the behavior that matters during implementation.