General Resources, guides, and helpful links.
Blocks Larger composed sections for building pages.
Components Copy and paste UI components into your apps.
Animations Beautiful animations and effects for your UI.
Powerful table and datagrids built using TanStack Table.
Introduction
Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.
It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that headless UI provides.
So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.
We'll start with the basic <Table /> component and build a complex data table from scratch.
Tip: If you find yourself using the same table in multiple places in your app, you can always extract it into a reusable component.
Table of Contents
This guide will show you how to use TanStack Table and the <Table /> component to build your own custom data table. We'll cover the following topics:
Installation
Add the <Table /> component to your project:
Add tanstack/react-table dependency:
Prerequisites We are going to build a table to show recent payments. Here's what our data looks like:
Copy type Payment = {
id : string
amount : number
status : "pending" | "processing" | "success" | "failed"
email : string
}
export const payments : Payment [] = [
{
id: "728ed52f" ,
amount: 100 ,
status: "pending" ,
email: "m@example.com" ,
},
{
id: "489e1d42" ,
amount: 125 ,
status: "processing" ,
email: "example@gmail.com" ,
},
// ...
]
Project Structure Start by creating the following file structure:
Copy app
└── payments
├── columns.tsx
├── data-table.tsx
└── page.tsx I'm using a Next.js example here but this works for any other React framework.
columns.tsx (client component) will contain our column definitions.
data-table.tsx (client component) will contain our <DataTable /> component.
page.tsx (server component) is where we'll fetch data and render our table.
Basic Table Let's start by building a basic table.
Column Definitions First, we'll define our columns.
app/payments/columns.tsxCopy "use client"
import { ColumnDef } from "@tanstack/react-table"
// This type is used to define the shape of our data.
// You can use a Zod schema here if you want.
export type Payment = {
id : string
amount : number
status : "pending" | "processing" | "success" | "failed"
email : string
}
export const columns : ColumnDef < Payment >[] = [
{
accessorKey: "status" ,
header: "Status" ,
},
{
accessorKey: "email" ,
header: "Email" ,
},
{
accessorKey: "amount" ,
header: "Amount" ,
},
] Note: Columns are where you define the core of what your table
will look like. They define the data that will be displayed, how it will be
formatted, sorted and filtered.
<DataTable /> componentNext, we'll create a <DataTable /> component to render our table.
app/payments/data-table.tsxCopy Tip : If you find yourself using <DataTable /> in multiple places, this is the component you could make reusable by extracting it to components/ui/data-table.tsx.
<DataTable columns={columns} data={data} />
Render the table Finally, we'll render our table in our page component.
app/payments/page.tsxCopy import { columns, Payment } from "./columns"
import { DataTable } from "./data-table"
Let's format the amount cell to display the dollar amount. We'll also align the cell to the right.
Update columns definition Update the header and cell definitions for amount as follows:
app/payments/columns.tsxCopy export const columns : ColumnDef < Payment >[] = [
{
accessorKey: "amount" ,
header : () => < div className = "text-right" >Amount</ div >,
cell : ({ row }) => {
const amount = parseFloat (row. getValue ( "amount" ))
const formatted = new Intl. NumberFormat ( "en-US" , {
style: "currency" ,
currency: "USD" ,
}). format (amount)
return < div className = "text-right font-medium" > { formatted } </ div >
},
},
] You can use the same approach to format other cells and headers.
Row Actions Let's add row actions to our table. We'll use a <Dropdown /> component for this.
Update columns definition Update our columns definition to add a new actions column. The actions cell returns a <Dropdown /> component.
app/payments/columns.tsxCopy "use client"
import { ColumnDef } from "@tanstack/react-table"
import { MoreHorizontal } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export const columns : ColumnDef < Payment >[] = [
You can access the row data using row.original in the cell function. Use this to handle actions for your row eg. use the id to make a DELETE call to your API.
Next, we'll add pagination to our table.
Update <DataTable> app/payments/data-table.tsxCopy import {
ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
} from "@tanstack/react-table"
export function DataTable < TData , TValue >({
columns ,
data ,
} : DataTableProps < TData , TValue >) {
const table = useReactTable ({
data,
columns,
getCoreRowModel: getCoreRowModel (),
getPaginationRowModel: getPaginationRowModel (),
})
// ...
} This will automatically paginate your rows into pages of 10. See the pagination docs for more information on customizing page size and implementing manual pagination.
We can add pagination controls to our table using the <Button /> component and the table.previousPage(), table.nextPage() API methods.
app/payments/data-table.tsxCopy import { Button }
See Reusable Components section for a more advanced pagination component.
Sorting Let's make the email column sortable.
Update <DataTable> app/payments/data-table.tsxCopy "use client"
import * as React from "react"
import {
ColumnDef,
SortingState,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
export function DataTable < TData , TValue >({
columns ,
data ,
} : DataTableProps < TData , TValue >) {
const [ sorting , setSorting ] = React. useState < SortingState >([])
const table = useReactTable ({
data,
columns,
getCoreRowModel: getCoreRowModel (),
getPaginationRowModel: getPaginationRowModel (),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel (),
state: {
sorting,
},
})
return (
< div >
< div className = "overflow-hidden rounded-md border" >
< Table > { ... } </ Table >
</ div >
</ div >
)
} We can now update the email header cell to add sorting controls.
app/payments/columns.tsxCopy "use client"
import { ColumnDef } from "@tanstack/react-table"
import { ArrowUpDown } from
This will automatically sort the table (asc and desc) when the user toggles on the header cell.
Filtering Let's add a search input to filter emails in our table.
Update <DataTable> app/payments/data-table.tsxCopy "use client"
import * as React from "react"
import {
ColumnDef,
ColumnFiltersState,
SortingState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
export function DataTable < TData ,
Filtering is now enabled for the email column. You can add filters to other columns as well. See the filtering docs for more information on customizing filters.
Visibility Adding column visibility is fairly simple using @tanstack/react-table visibility API.
Update <DataTable> app/payments/data-table.tsxCopy "use client"
import * as React from "react"
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
This adds a dropdown menu that you can use to toggle column visibility.
Row Selection Next, we're going to add row selection to our table.
Update column definitions app/payments/columns.tsxCopy "use client"
import { ColumnDef } from "@tanstack/react-table"
import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox"
export const columns : ColumnDef < Payment >[] = [
{
id: "select" ,
header : ({ table }) => (
< Checkbox
checked = {
table. getIsAllPageRowsSelected () ||
(table. getIsSomePageRowsSelected () && "indeterminate" )
}
onCheckedChange = { ( value ) => table. toggleAllPageRowsSelected ( !! value) }
aria-label = "Select all"
/>
),
cell : ({ row }) => (
< Checkbox
checked = { row. getIsSelected () }
onCheckedChange = { ( value ) => row. toggleSelected ( !! value) }
aria-label = "Select row"
/>
),
enableSorting: false ,
enableHiding: false ,
},
] Update <DataTable> app/payments/data-table.tsxCopy export
This adds a checkbox to each row and a checkbox in the header to select all rows.
Show selected rows You can show the number of selected rows using the table.getFilteredSelectedRowModel() API.
Copy < div className = "text-muted-foreground flex-1 text-sm" >
{ table. getFilteredSelectedRowModel ().rows. length } of { " " }
{ table. getFilteredRowModel ().rows. length } row(s) selected.
</ div >
Reusable Components Here are some components you can use to build your data tables. This is from the Tasks demo.
Column header Make any column header sortable and hideable.
components/data-table-column-header.tsxCopy import { ArrowUpDown, EyeOff } from "lucide-react"
import { Column } from "@tanstack/react-table"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
interface DataTableColumnHeaderProps < TData , TValue >
extends React . HTMLAttributes
Copy export const columns = [
{
accessorKey: "email" ,
header : ({ column }) => (
< DataTableColumnHeader column = { column } title = "Email" />
),
},
]
Add pagination controls to your table including page size and selection count.
components/data-table-pagination.tsxCopy import {
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
} from "lucide-react"
import { Table } from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
interface DataTablePaginationProps < TData > {
table : Table <
Copy < DataTablePagination table = { table } />
Column toggle A component to toggle column visibility.
components/data-table-view-options.tsxCopy import { MixerHorizontalIcon } from "@radix-ui/react-icons"
import { Table } from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
interface DataTableViewOptionsProps < TData > {
table : Table < TData >
}
export
Copy < DataTableViewOptions table = { table } /> 0 of 5 row(s) selected.
Previous Next
"use client"
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
interface DataTableProps < TData , TValue > {
columns : ColumnDef < TData , TValue >[]
data : TData []
}
export function DataTable < TData , TValue >({
columns ,
data ,
} : DataTableProps < TData , TValue >) {
const table = useReactTable ({
data,
columns,
getCoreRowModel: getCoreRowModel (),
})
return (
< div className = "overflow-hidden rounded-md border" >
< Table >
< TableHeader >
{ table. getHeaderGroups (). map (( headerGroup ) => (
< TableRow key = { headerGroup.id } >
{ headerGroup.headers. map (( header ) => {
return (
< TableHead key = { header.id } >
{ header.isPlaceholder
? null
: flexRender (
header.column.columnDef.header,
header. getContext ()
) }
</ TableHead >
)
}) }
</ TableRow >
)) }
</ TableHeader >
< TableBody >
{ table. getRowModel ().rows?. length ? (
table. getRowModel ().rows. map (( row ) => (
< TableRow
key = { row.id }
data-state = { row. getIsSelected () && "selected" }
>
{ row. getVisibleCells (). map (( cell ) => (
< TableCell key = { cell.id } >
{ flexRender (cell.column.columnDef.cell, cell. getContext ()) }
</ TableCell >
)) }
</ TableRow >
))
) : (
< TableRow >
< TableCell colSpan = { columns. length } className = "h-24 text-center" >
No results.
</ TableCell >
</ TableRow >
) }
</ TableBody >
</ Table >
</ div >
)
}
async function getData () : Promise < Payment []> {
// Fetch data from your API here.
return [
{
id: "728ed52f" ,
amount: 100 ,
status: "pending" ,
email: "m@example.com" ,
},
// ...
]
}
export default async function DemoPage () {
const data = await getData ()
return (
< div className = "container mx-auto py-10" >
< DataTable columns = { columns } data = { data } />
</ div >
)
}
// ...
{
id: "actions" ,
cell : ({ row }) => {
const payment = row.original
return (
< DropdownMenu >
< DropdownMenuTrigger asChild >
< Button variant = "ghost" className = "h-8 w-8 p-0" >
< span className = "sr-only" >Open menu</ span >
< MoreHorizontal className = "h-4 w-4" />
</ Button >
</ 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 >
)
},
},
// ...
]
from
"@/components/ui/button"
export function DataTable < TData , TValue >({
columns ,
data ,
} : DataTableProps < TData , TValue >) {
const table = useReactTable ({
data,
columns,
getCoreRowModel: getCoreRowModel (),
getPaginationRowModel: getPaginationRowModel (),
})
return (
< div >
< div className = "overflow-hidden rounded-md border" >
< Table >
{ // .... }
</ Table >
</ div >
< div className = "flex items-center justify-end space-x-2 py-4" >
< Button
variant = "outline"
size = "sm"
onClick = { () => table. previousPage () }
disabled = { ! table. getCanPreviousPage () }
>
Previous
</ Button >
< Button
variant = "outline"
size = "sm"
onClick = { () => table. nextPage () }
disabled = { ! table. getCanNextPage () }
>
Next
</ Button >
</ div >
</ div >
)
}
"lucide-react"
export const columns : ColumnDef < Payment >[] = [
{
accessorKey: "email" ,
header : ({ column }) => {
return (
< Button
variant = "ghost"
onClick = { () => column. toggleSorting (column. getIsSorted () === "asc" ) }
>
Email
< ArrowUpDown className = "ml-2 h-4 w-4" />
</ Button >
)
},
},
]
TValue
>({
columns ,
data ,
} : DataTableProps < TData , TValue >) {
const [ sorting , setSorting ] = React. useState < SortingState >([])
const [ columnFilters , setColumnFilters ] = React. useState < ColumnFiltersState >(
[]
)
const table = useReactTable ({
data,
columns,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel (),
getPaginationRowModel: getPaginationRowModel (),
getSortedRowModel: getSortedRowModel (),
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel (),
state: {
sorting,
columnFilters,
},
})
return (
< div >
< 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"
/>
</ div >
< div className = "overflow-hidden rounded-md border" >
< Table > { ... } </ Table >
</ div >
</ div >
)
}
} from "@/components/ui/dropdown-menu"
export function DataTable < TData , TValue >({
columns ,
data ,
} : DataTableProps < TData , TValue >) {
const [ sorting , setSorting ] = React. useState < SortingState >([])
const [ columnFilters , setColumnFilters ] = React. useState < ColumnFiltersState >(
[]
)
const [ columnVisibility , setColumnVisibility ] =
React. useState < VisibilityState >({})
const table = useReactTable ({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel (),
getPaginationRowModel: getPaginationRowModel (),
getSortedRowModel: getSortedRowModel (),
getFilteredRowModel: getFilteredRowModel (),
onColumnVisibilityChange: setColumnVisibility,
state: {
sorting,
columnFilters,
columnVisibility,
},
})
return (
< div >
< 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 asChild >
< Button variant = "outline" className = "ml-auto" >
Columns
</ Button >
</ 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 className = "overflow-hidden rounded-md border" >
< Table > { ... } </ Table >
</ div >
</ div >
)
}
function
DataTable
<
TData
,
TValue
>({
columns ,
data ,
} : DataTableProps < TData , TValue >) {
const [ sorting , setSorting ] = React. useState < SortingState >([])
const [ columnFilters , setColumnFilters ] = React. useState < ColumnFiltersState >(
[]
)
const [ columnVisibility , setColumnVisibility ] =
React. useState < VisibilityState >({})
const [ rowSelection , setRowSelection ] = React. useState ({})
const table = useReactTable ({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel (),
getPaginationRowModel: getPaginationRowModel (),
getSortedRowModel: getSortedRowModel (),
getFilteredRowModel: getFilteredRowModel (),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
return (
< div >
< div className = "overflow-hidden rounded-md border" >
< Table />
</ div >
</ div >
)
}
<
HTMLDivElement
> {
column : Column < TData , TValue >
title : string
}
export function DataTableColumnHeader < TData , TValue >({
column ,
title ,
className ,
} : DataTableColumnHeaderProps < TData , TValue >) {
if ( ! column. getCanSort ()) {
return < div className = { cn (className) } > { title } </ div >
}
return (
< div className = { cn ( "flex items-center space-x-2" , className) } >
< DropdownMenu >
< DropdownMenuTrigger asChild >
< Button
variant = "ghost"
size = "sm"
className = "-ml-3 h-8 data-[state=open]:bg-accent"
>
< span > { title } </ span >
{ column. getIsSorted () === "desc" ? (
< ArrowUpDown className = "ml-2 h-4 w-4" />
) : column. getIsSorted () === "asc" ? (
< ArrowUpDown className = "ml-2 h-4 w-4" />
) : (
< ArrowUpDown className = "ml-2 h-4 w-4" />
) }
</ Button >
</ DropdownMenuTrigger >
< DropdownMenuContent align = "start" >
< DropdownMenuItem onClick = { () => column. toggleSorting ( false ) } >
Asc
</ DropdownMenuItem >
< DropdownMenuItem onClick = { () => column. toggleSorting ( true ) } >
Desc
</ DropdownMenuItem >
< DropdownMenuSeparator />
< DropdownMenuItem onClick = { () => column. toggleVisibility ( false ) } >
< EyeOff className = "mr-2 h-3.5 w-3.5" />
Hide
</ DropdownMenuItem >
</ DropdownMenuContent >
</ DropdownMenu >
</ div >
)
}
TData
>
}
export function DataTablePagination < TData >({
table ,
} : DataTablePaginationProps < TData >) {
return (
< div className = "flex items-center justify-between px-2" >
< div className = "text-muted-foreground flex-1 text-sm" >
{ table. getFilteredSelectedRowModel ().rows. length } of { " " }
{ table. getFilteredRowModel ().rows. length } row(s) selected.
</ div >
< div className = "flex items-center space-x-6 lg:space-x-8" >
< div className = "flex items-center space-x-2" >
< p className = "text-sm font-medium" >Rows per page</ p >
< Select
value = { `${ table . getState (). pagination . pageSize }` }
onValueChange = { ( value ) => {
table. setPageSize ( Number (value))
} }
>
< SelectTrigger className = "h-8 w-[70px]" >
< SelectValue placeholder = { table. getState ().pagination.pageSize } />
</ SelectTrigger >
< SelectContent side = "top" >
{ [ 10 , 20 , 30 , 40 , 50 ]. map (( pageSize ) => (
< SelectItem key = { pageSize } value = { `${ pageSize }` } >
{ pageSize }
</ SelectItem >
)) }
</ SelectContent >
</ Select >
</ div >
< div className = "flex w-[100px] items-center justify-center text-sm font-medium" >
Page { table. getState ().pagination.pageIndex + 1 } of { " " }
{ table. getPageCount () }
</ div >
< div className = "flex items-center space-x-2" >
< Button
variant = "outline"
className = "hidden h-8 w-8 p-0 lg:flex"
onClick = { () => table. setPageIndex ( 0 ) }
disabled = { ! table. getCanPreviousPage () }
>
< span className = "sr-only" >Go to first page</ span >
< ChevronsLeft className = "h-4 w-4" />
</ Button >
< Button
variant = "outline"
className = "h-8 w-8 p-0"
onClick = { () => table. previousPage () }
disabled = { ! table. getCanPreviousPage () }
>
< span className = "sr-only" >Go to previous page</ span >
< ChevronLeft className = "h-4 w-4" />
</ Button >
< Button
variant = "outline"
className = "h-8 w-8 p-0"
onClick = { () => table. nextPage () }
disabled = { ! table. getCanNextPage () }
>
< span className = "sr-only" >Go to next page</ span >
< ChevronRight className = "h-4 w-4" />
</ Button >
< Button
variant = "outline"
className = "hidden h-8 w-8 p-0 lg:flex"
onClick = { () => table. setPageIndex (table. getPageCount () - 1 ) }
disabled = { ! table. getCanNextPage () }
>
< span className = "sr-only" >Go to last page</ span >
< ChevronsRight className = "h-4 w-4" />
</ Button >
</ div >
</ div >
</ div >
)
}
function
DataTableViewOptions
<
TData
>({
table ,
} : DataTableViewOptionsProps < TData >) {
return (
< DropdownMenu >
< DropdownMenuTrigger asChild >
< Button
variant = "outline"
size = "sm"
className = "ml-auto hidden h-8 lg:flex"
>
< MixerHorizontalIcon className = "mr-2 h-4 w-4" />
View
</ Button >
</ DropdownMenuTrigger >
< DropdownMenuContent align = "end" className = "w-[150px]" >
< DropdownMenuLabel >Toggle columns</ DropdownMenuLabel >
< DropdownMenuSeparator />
{ table
. getAllColumns ()
. filter (
( column ) =>
typeof column.accessorFn !== "undefined" && column. getCanHide ()
)
. map (( column ) => {
return (
< DropdownMenuCheckboxItem
key = { column.id }
className = "capitalize"
checked = { column. getIsVisible () }
onCheckedChange = { ( value ) => column. toggleVisibility ( !! value) }
>
{ column.id }
</ DropdownMenuCheckboxItem >
)
}) }
</ DropdownMenuContent >
</ DropdownMenu >
)
}
Copy "use client"
import * as React from "react"
import {
ColumnDef,
ColumnFiltersState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
SortingState,
useReactTable,
VisibilityState,
} from "@tanstack/react-table"
import { ArrowUpDown, ChevronDown, MoreHorizontal } from "lucide-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@example.com" ,
},
{
id: "3u1reuv4" ,
amount: 242 ,
status: "success" ,
email: "Abe45@example.com" ,
},
{
id: "derv1ws0" ,
amount: 837 ,
status: "processing" ,
email: "Monserrat44@example.com" ,
},
{
id: "5kma53ae" ,
amount: 874 ,
status: "success" ,
email: "Silas22@example.com" ,
},
{
id: "bhqecj4p" ,
amount: 721 ,
status: "failed" ,
email: "carmella@example.com" ,
},
]
export type Payment = {
id : string
amount : number
status : "pending" | "processing" | "success" | "failed"
email : string
}
export const columns : ColumnDef < Payment >[] = [
{
id: "select" ,
header : ({ table }) => (
< Checkbox
checked = {
table. getIsAllPageRowsSelected () ||
(table. getIsSomePageRowsSelected () && "indeterminate" )
}
onCheckedChange = {( value ) => table. toggleAllPageRowsSelected ( !! value)}
aria-label = "Select all"
/>
),
cell : ({ row }) => (
< Checkbox
checked = {row. getIsSelected ()}
onCheckedChange = {( value ) => row. toggleSelected ( !! value)}
aria-label = "Select row"
/>
),
enableSorting: false ,
enableHiding: false ,
},
{
accessorKey: "status" ,
header: "Status" ,
cell : ({ row }) => (
< div className = "capitalize" >{row. getValue ( "status" )}</ div >
),
},
{
accessorKey: "email" ,
header : ({ column }) => {
return (
< Button
variant = "ghost"
onClick = {() => column. toggleSorting (column. getIsSorted () === "asc" )}
>
Email
< ArrowUpDown className = "ml-2 size-4" />
</ Button >
)
},
cell : ({ row }) => < div className = "lowercase" >{row. getValue ( "email" )}</ div >,
},
{
accessorKey: "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-medium" >{formatted}</ div >
},
},
{
id: "actions" ,
enableHiding: false ,
cell : ({ row }) => {
const payment = row.original
return (
< DropdownMenu >
< DropdownMenuTrigger asChild >
< Button variant = "ghost" className = "h-8 w-8 p-0" >
< span className = "sr-only" >Open menu</ span >
< MoreHorizontal />
</ Button >
</ 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 function DataTableDemo () {
const [ sorting , setSorting ] = React. useState < SortingState >([])
const [ columnFilters , setColumnFilters ] = React. useState < ColumnFiltersState >(
[]
)
const [ columnVisibility , setColumnVisibility ] =
React. useState < VisibilityState >({})
const [ rowSelection , setRowSelection ] = React. useState ({})
const table = useReactTable ({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel (),
getPaginationRowModel: getPaginationRowModel (),
getSortedRowModel: getSortedRowModel (),
getFilteredRowModel: getFilteredRowModel (),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
return (
< div className = "w-full" >
< 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 asChild >
< Button variant = "outline" className = "ml-auto" >
Columns < ChevronDown />
</ Button >
</ 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 className = "overflow-hidden rounded-md border" >
< Table >
< TableHeader >
{table. getHeaderGroups (). map (( headerGroup ) => (
< TableRow key = {headerGroup.id}>
{headerGroup.headers. map (( header ) => {
return (
< TableHead key = {header.id}>
{header.isPlaceholder
? null
: flexRender (
header.column.columnDef.header,
header. getContext ()
)}
</ TableHead >
)
})}
</ TableRow >
))}
</ TableHeader >
< TableBody >
{table. getRowModel ().rows?. length ? (
table. getRowModel ().rows. map (( row ) => (
< TableRow
key = {row.id}
data-state = {row. getIsSelected () && "selected" }
>
{row. getVisibleCells (). map (( cell ) => (
< TableCell key = {cell.id}>
{ flexRender (
cell.column.columnDef.cell,
cell. getContext ()
)}
</ 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-muted-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 = "outline"
size = "sm"
onClick = {() => table. previousPage ()}
disabled = { ! table. getCanPreviousPage ()}
>
Previous
</ Button >
< Button
variant = "outline"
size = "sm"
onClick = {() => table. nextPage ()}
disabled = { ! table. getCanNextPage ()}
>
Next
</ Button >
</ div >
</ div >
</ div >
)
}