| Status | Amount | |||
|---|---|---|---|---|
success | ken99@yahoo.com | $316.00 | ||
success | Abe45@gmail.com | $242.00 | ||
processing | Monserrat44@gmail.com | $837.00 | ||
success | Silas22@gmail.com | $874.00 | ||
failed | carmella@hotmail.com | $721.00 |
0 of 5 row(s) selected.
Installation
The data table is built with the Table, Button, Checkbox, Dropdown Menu and Input components and TanStack Table v9.
pnpm add @tanstack/react-tableGo to the shadcn/ui docs for a full guide on building your own data table. When you're done, you can copy the demo below and paste it inside your project:
"use client"
import {
columnFilteringFeature,
columnVisibilityFeature,
createColumnHelper,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
filterFn_includesString,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
sortFn_alphanumeric,
sortFn_text,
tableFeatures,
useTable,
type ColumnFiltersState,
type ColumnVisibilityState,
type RowSelectionState,
type SortingState,
} from "@tanstack/react-table"
import { ArrowUpDown, ChevronDown, MoreHorizontal } from "lucide-react"
import * as React from "react"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
const data: Payment[] = [
{
id: "m5gr84i9",
amount: 316,
status: "success",
email: "ken99@yahoo.com",
},
{
id: "3u1reuv4",
amount: 242,
status: "success",
email: "Abe45@gmail.com",
},
{
id: "derv1ws0",
amount: 837,
status: "processing",
email: "Monserrat44@gmail.com",
},
{
id: "5kma53ae",
amount: 874,
status: "success",
email: "Silas22@gmail.com",
},
{
id: "bhqecj4p",
amount: 721,
status: "failed",
email: "carmella@hotmail.com",
},
]
export type Payment = {
id: string
amount: number
status: "pending" | "processing" | "success" | "failed"
email: string
}
// TanStack Table v9 is modular: only the features listed here are bundled.
const features = tableFeatures({
columnFilteringFeature,
columnVisibilityFeature,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
filteredRowModel: createFilteredRowModel(),
paginatedRowModel: createPaginatedRowModel(),
sortedRowModel: createSortedRowModel(),
filterFns: { includesString: filterFn_includesString },
sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
})
const columnHelper = createColumnHelper<typeof features, Payment>()
export const columns = columnHelper.columns([
columnHelper.display({
id: "select",
header: ({ table }) => (
<Checkbox
className="data-checked:bg-secondary-background data-indeterminate:bg-secondary-background"
checked={table.getIsAllPageRowsSelected()}
indeterminate={
table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected()
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
className="data-checked:bg-secondary-background data-indeterminate:bg-secondary-background"
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
}),
columnHelper.accessor("status", {
header: "Status",
cell: ({ row }) => (
<div className="capitalize">{row.getValue("status")}</div>
),
}),
columnHelper.accessor("email", {
header: ({ column }) => {
return (
<Button
variant="noShadow"
className="bg-secondary-background text-foreground"
size="sm"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Email
<ArrowUpDown />
</Button>
)
},
cell: ({ row }) => <div className="lowercase">{row.getValue("email")}</div>,
}),
columnHelper.accessor("amount", {
header: () => <div className="text-right">Amount</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue("amount"))
// Format the amount as a dollar amount
const formatted = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount)
return <div className="text-right font-base">{formatted}</div>
},
}),
columnHelper.display({
id: "actions",
enableHiding: false,
cell: ({ row }) => {
const payment = row.original
return (
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="noShadow"
className="size-8 bg-secondary-background p-0 text-foreground"
/>
}
>
<span className="sr-only">Open menu</span>
<MoreHorizontal />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(payment.id)}
>
Copy payment ID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
},
}),
])
export default function DataTableDemo() {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
)
const [columnVisibility, setColumnVisibility] =
React.useState<ColumnVisibilityState>({})
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({})
const table = useTable({
features,
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
return (
<div className="w-full font-base text-foreground">
<div className="flex items-center py-4">
<Input
placeholder="Filter emails..."
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("email")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="noShadow"
className="ml-auto bg-secondary-background text-foreground"
/>
}
>
Columns <ChevronDown />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
)
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div>
<Table>
<TableHeader className="font-heading">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow
className="bg-background text-foreground"
key={headerGroup.id}
>
{headerGroup.headers.map((header) => {
return (
<TableHead className="text-foreground" key={header.id}>
{header.isPlaceholder ? null : (
<table.FlexRender header={header} />
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
className="bg-background text-foreground data-[state=selected]:bg-main data-[state=selected]:text-main-foreground"
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell className="px-4 py-2" key={cell.id}>
<table.FlexRender cell={cell} />
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="text-foreground flex-1 text-sm">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="space-x-2">
<Button
variant="noShadow"
className="bg-secondary-background text-foreground"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="noShadow"
className="bg-secondary-background text-foreground"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
)
}About
TanStack Table v9 is modular. Instead of useReactTable with getCoreRowModel(), getSortedRowModel() and so on, you declare the features you need with tableFeatures and pass them to useTable. Columns are created with createColumnHelper and rendered with table.FlexRender. Read the TanStack Table migration guide if you are upgrading from v8.
Usage
import DataTableDemo from '@/components/ui/data-table'<DataTableDemo />