Component
Data Table

Data Table

Powerful table and datagrids built using TanStack Table.

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

Install the following dependencies

npm i @tanstack/react-table

Add the Table component to your project.

We'll use the Table component for the Data Table. Make sure you have it installed in your project.

Guide

For the data table, we'll use TanStack Table and the <Table /> component. It's a powerful table component that allows you to build any kind of table or datagrid.

Please refer to the detailed guide from shadcn/ui for how to create your own data table.

Reusable Components

Here are some reusable components you can use to build your data tables.

Column Header

Make any column header sortable and hideable.

components/previews/data-table/column-header.tsx
import { ArrowDownIcon, ArrowUpIcon, ArrowUpDownIcon, EyeOffIcon } from 'lucide-react'
import { Column } from '@tanstack/react-table'
import { css } from '@shadow-panda/styled-system/css'
import { Flex } from '@shadow-panda/styled-system/jsx'
import { icon } from '@shadow-panda/styled-system/recipes'
import { Button } from '@/components/ui/button'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
 
interface DataTableColumnHeaderProps<TData, TValue> extends React.HTMLAttributes<HTMLDivElement> {
  column: Column<TData, TValue>
  title: string
}
 
export function DataTableColumnHeader<TData, TValue>({
  column,
  title,
  className,
}: DataTableColumnHeaderProps<TData, TValue>) {
  if (!column.getCanSort()) {
    return <div className={className}>{title}</div>
  }
 
  return (
    <Flex align="center" gap="2">
      <DropdownMenu>
        <DropdownMenuTrigger asChild>
          <Button
            variant="ghost"
            size="sm"
            className={css({
              ml: '-3',
              h: '8',
              '&[data-state="open]': {
                bg: 'accent',
              },
            })}
          >
            <span>{title}</span>
            {column.getIsSorted() === 'desc' ? (
              <ArrowDownIcon className={icon({ left: 'sm' })} />
            ) : column.getIsSorted() === 'asc' ? (
              <ArrowUpIcon className={icon({ left: 'sm' })} />
            ) : (
              <ArrowUpDownIcon className={icon({ left: 'sm' })} />
            )}
          </Button>
        </DropdownMenuTrigger>
        <DropdownMenuContent align="start">
          <DropdownMenuItem onClick={() => column.toggleSorting(false)}>
            <ArrowUpIcon
              className={css({ mr: '2', h: '3.5', w: '3.5', ca: 'muted.foreground/70' })}
            />
            Asc
          </DropdownMenuItem>
          <DropdownMenuItem onClick={() => column.toggleSorting(true)}>
            <ArrowDownIcon
              className={css({ mr: '2', h: '3.5', w: '3.5', ca: 'muted.foreground/70' })}
            />
            Desc
          </DropdownMenuItem>
          <DropdownMenuSeparator />
          <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
            <EyeOffIcon
              className={css({ mr: '2', h: '3.5', w: '3.5', ca: 'muted.foreground/70' })}
            />
            Hide
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </Flex>
  )
}
export const columns = [
  {
    accessorKey: 'email',
    header: ({ column }) => <DataTableColumnHeader column={column} title="Email" />,
  },
]

Pagination

Add pagination controls to your table including page size and selection count.

components/previews/data-table/pagination.tsx
import {
  ChevronLeftIcon,
  ChevronRightIcon,
  ChevronsLeftIcon,
  ChevronsRightIcon,
} from 'lucide-react'
import { Table } from '@tanstack/react-table'
import { css } from '@shadow-panda/styled-system/css'
import { Flex, Box } from '@shadow-panda/styled-system/jsx'
import { icon } from '@shadow-panda/styled-system/recipes'
import { Button } from '@/components/ui/button'
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select'
 
interface DataTablePaginationProps<TData> {
  table: Table<TData>
}
 
export function DataTablePagination<TData>({ table }: DataTablePaginationProps<TData>) {
  return (
    <Flex align="center" justify="space-between" px="2">
      <Box flex="1" textStyle="sm" color="muted.foreground">
        {table.getFilteredSelectedRowModel().rows.length} of{' '}
        {table.getFilteredRowModel().rows.length} row(s) selected.
      </Box>
      <Flex align="center" gap="6" lg={{ gap: '8' }}>
        <Flex align="center" gap="2">
          <p className={css({ textStyle: 'sm', fontWeight: 'medium' })}>Rows per page</p>
          <Select
            value={`${table.getState().pagination.pageSize}`}
            onValueChange={(value) => {
              table.setPageSize(Number(value))
            }}
          >
            <SelectTrigger 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>
        </Flex>
        <Flex w="100px" align="center" justify="center" textStyle="sm" fontWeight="medium">
          Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
        </Flex>
        <Flex align="center" gap="2">
          <Button
            variant="outline"
            display="none"
            h="8"
            w="8"
            p="0"
            lg={{ display: 'flex' }}
            onClick={() => table.setPageIndex(0)}
            disabled={!table.getCanPreviousPage()}
          >
            <span className={css({ srOnly: true })}>Go to first page</span>
            <ChevronsLeftIcon className={icon()} />
          </Button>
          <Button
            variant="outline"
            h="8"
            w="8"
            p="0"
            onClick={() => table.previousPage()}
            disabled={!table.getCanPreviousPage()}
          >
            <span className={css({ srOnly: true })}>Go to previous page</span>
            <ChevronLeftIcon className={icon()} />
          </Button>
          <Button
            variant="outline"
            h="8"
            w="8"
            p="0"
            onClick={() => table.nextPage()}
            disabled={!table.getCanNextPage()}
          >
            <span className={css({ srOnly: true })}>Go to next page</span>
            <ChevronRightIcon className={icon()} />
          </Button>
          <Button
            variant="outline"
            display="none"
            h="8"
            w="8"
            p="0"
            lg={{ display: 'flex' }}
            onClick={() => table.setPageIndex(table.getPageCount() - 1)}
            disabled={!table.getCanNextPage()}
          >
            <span className={css({ srOnly: true })}>Go to last page</span>
            <ChevronsRightIcon className={icon()} />
          </Button>
        </Flex>
      </Flex>
    </Flex>
  )
}
<DataTablePagination table={table} />

Column Toggle

A component to toggle column visibility.

components/previews/data-table/column-toggle.tsx
'use client'
 
import { SlidersHorizontalIcon } from 'lucide-react'
import { Table } from '@tanstack/react-table'
import { icon } from '@shadow-panda/styled-system/recipes'
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 function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps<TData>) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="sm" ml="auto" display="none" h="8" lg={{ display: 'flex' }}>
          <SlidersHorizontalIcon className={icon({ right: 'sm' })} />
          View
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end" w="150px">
        <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
        <DropdownMenuSeparator />
        {table
          .getAllColumns()
          .filter((column) => typeof column.accessorFn !== 'undefined' && column.getCanHide())
          .map((column) => {
            return (
              <DropdownMenuCheckboxItem
                key={column.id}
                textTransform="capitalize"
                checked={column.getIsVisible()}
                onCheckedChange={(value) => column.toggleVisibility(!!value)}
              >
                {column.id}
              </DropdownMenuCheckboxItem>
            )
          })}
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
<DataTableViewOptions table={table} />