Skip to content

Table

A responsive table component.

Code
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@falcon/ui-kit";
const invoices = [
{
invoice: "ORD001",
paymentStatus: "Paid",
totalAmount: "$250.00",
paymentMethod: "Credit Card",
},
{
invoice: "ORD002",
paymentStatus: "Pending",
totalAmount: "$150.00",
paymentMethod: "ACH",
},
{
invoice: "ORD003",
paymentStatus: "Unpaid",
totalAmount: "$350.00",
paymentMethod: "Bank Transfer",
},
{
invoice: "ORD004",
paymentStatus: "Paid",
totalAmount: "$450.00",
paymentMethod: "Credit Card",
},
{
invoice: "ORD005",
paymentStatus: "Paid",
totalAmount: "$550.00",
paymentMethod: "ACH",
},
{
invoice: "ORD006",
paymentStatus: "Pending",
totalAmount: "$200.00",
paymentMethod: "Bank Transfer",
},
{
invoice: "ORD007",
paymentStatus: "Unpaid",
totalAmount: "$300.00",
paymentMethod: "Credit Card",
},
];
export function Example() {
return (
<Table>
<TableCaption>A list of your recent orders.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="tw:w-[100px]">Order</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="tw:text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="tw:font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.paymentStatus}</TableCell>
<TableCell>{invoice.paymentMethod}</TableCell>
<TableCell className="tw:text-right">
{invoice.totalAmount}
</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableHead colSpan={3} scope="row">
Total
</TableHead>
<TableCell className="tw:text-right">$2,250.00</TableCell>
</TableRow>
</TableFooter>
</Table>
);
}

Use the following composition to build a Table:

Table
├── TableCaption
├── TableHeader
│ └── TableRow
│ ├── TableHead
│ ├── TableHead
│ ├── TableHead
│ └── TableHead
├── TableBody
│ ├── TableRow
│ │ ├── TableCell
│ │ ├── TableCell
│ │ ├── TableCell
│ │ └── TableCell
│ └── TableRow
│ ├── TableCell
│ ├── TableCell
│ ├── TableCell
│ └── TableCell
└── TableFooter

Put TableHeadSortable inside a TableHead on a sortable column:

TableHead
└── TableHeadSortable

It renders no <th> itself, so the enclosing TableHead still owns cell concerns like aria-sort. Pass the column’s current sort direction to both — TableHeadSortable for its chevrons, TableHead for aria-sort — and handle the sort in onClick; the component owns none of the sorting logic itself.

Code
import { useState } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeadSortable,
TableHeader,
TableRow,
} from "@falcon/ui-kit";
const accounts = [
{ name: "Adams Ranch", syngentaId: "502081", location: "Abilene, KS" },
{ name: "Albrecht Ag Inc", syngentaId: "1005", location: "Fayette, MO" },
{ name: "Allen Farms", syngentaId: "502101", location: "Minot, ND" },
{
name: "Bailey Agriculture",
syngentaId: "502021",
location: "Freeport, IL",
},
];
type Column = "name" | "syngentaId";
export function Example() {
const [sort, setSort] = useState<{
column: Column;
direction: "asc" | "desc";
}>({ column: "name", direction: "asc" });
const sorted = [...accounts].sort((a, b) => {
const result = a[sort.column].localeCompare(b[sort.column]);
return sort.direction === "asc" ? result : -result;
});
function toggleSort(column: Column) {
setSort((current) =>
current.column === column
? { column, direction: current.direction === "asc" ? "desc" : "asc" }
: { column, direction: "asc" },
);
}
const nameSorted = sort.column === "name" ? sort.direction : false;
const syngentaIdSorted =
sort.column === "syngentaId" ? sort.direction : false;
return (
<Table>
<TableHeader>
<TableRow>
<TableHead sorted={nameSorted}>
<TableHeadSortable
sorted={nameSorted}
onClick={() => toggleSort("name")}
>
Account
</TableHeadSortable>
</TableHead>
<TableHead sorted={syngentaIdSorted}>
<TableHeadSortable
sorted={syngentaIdSorted}
onClick={() => toggleSort("syngentaId")}
>
Syngenta ID
</TableHeadSortable>
</TableHead>
<TableHead>Location</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sorted.map((account) => (
<TableRow key={account.name}>
<TableCell className="tw:font-medium">{account.name}</TableCell>
<TableCell>{account.syngentaId}</TableCell>
<TableCell>{account.location}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}

Use the <TableFooter /> component to add a footer to the table.

Code
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@falcon/ui-kit";
const invoices = [
{
invoice: "ORD001",
paymentStatus: "Paid",
totalAmount: "$250.00",
paymentMethod: "Credit Card",
},
{
invoice: "ORD002",
paymentStatus: "Pending",
totalAmount: "$150.00",
paymentMethod: "ACH",
},
{
invoice: "ORD003",
paymentStatus: "Unpaid",
totalAmount: "$350.00",
paymentMethod: "Bank Transfer",
},
{
invoice: "ORD004",
paymentStatus: "Paid",
totalAmount: "$450.00",
paymentMethod: "Credit Card",
},
{
invoice: "ORD005",
paymentStatus: "Paid",
totalAmount: "$550.00",
paymentMethod: "ACH",
},
{
invoice: "ORD006",
paymentStatus: "Pending",
totalAmount: "$200.00",
paymentMethod: "Bank Transfer",
},
{
invoice: "ORD007",
paymentStatus: "Unpaid",
totalAmount: "$300.00",
paymentMethod: "Credit Card",
},
];
export function Example() {
return (
<Table>
<TableCaption>A list of your recent orders.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="tw:w-[100px]">Order</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="tw:text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.slice(0, 3).map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="tw:font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.paymentStatus}</TableCell>
<TableCell>{invoice.paymentMethod}</TableCell>
<TableCell className="tw:text-right">
{invoice.totalAmount}
</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableHead colSpan={3} scope="row">
Total
</TableHead>
<TableCell className="tw:text-right">$750.00</TableCell>
</TableRow>
</TableFooter>
</Table>
);
}

A table showing actions for each row using a <DropdownMenu /> component.

Code
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
MoreHorizontalIcon,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@falcon/ui-kit";
export function Example() {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Product</TableHead>
<TableHead>Price</TableHead>
<TableHead className="tw:text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="tw:font-medium">Soil Test Kit</TableCell>
<TableCell>$29.99</TableCell>
<TableCell className="tw:text-right">
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="tw:size-8" />
}
>
<MoreHorizontalIcon />
<span className="tw:sr-only">Open menu</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="tw:font-medium">
Field Measuring Wheel
</TableCell>
<TableCell>$129.99</TableCell>
<TableCell className="tw:text-right">
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="tw:size-8" />
}
>
<MoreHorizontalIcon />
<span className="tw:sr-only">Open menu</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="tw:font-medium">Sprayer Nozzle Set</TableCell>
<TableCell>$49.99</TableCell>
<TableCell className="tw:text-right">
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="tw:size-8" />
}
>
<MoreHorizontalIcon />
<span className="tw:sr-only">Open menu</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
</TableBody>
</Table>
);
}

Renders a <table> element and accepts standard table props.

Contains the table’s data rows. Renders a <tbody> element and accepts standard tbody props.

Describes the table. Renders a <caption> element and accepts standard caption props.

Renders a <td> element and accepts standard td props.

Contains summary rows. Renders a <tfoot> element and accepts standard tfoot props.

Renders a <th> element and accepts standard th props. Pass sorted for a sortable column — give it the same value passed to the nested TableHeadSortable’s own sorted prop, which drives its chevrons.

sorted?: SortDirection

Which direction the column is currently sorted by. Sets aria-sort to "ascending", "descending", or (false) "none". Omit for a non-sortable column to leave aria-sort unset.

Sort toggle for a column heading. Wraps its label in a <button> and shows the column’s current sort direction on the trailing chevrons. Place it as the content of a TableHead, which renders no <th> itself; forwards standard <button> props (e.g. onClick); type is fixed to "button" and can’t be overridden. Pass the same value to the enclosing TableHead’s own sorted prop to keep its aria-sort in sync.

sorted?: SortDirection = false

Which direction the column is currently sorted by, if any.

Contains column headings. Renders a <thead> element and accepts standard thead props.

Renders a <tr> element and accepts standard tr props.