Registry Component

Table

Composable table primitives with animated sort helpers, layout-preserving rows, and the same minimal data-grid styling as the original demo.

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.