Vai al contenuto

Tabella dati

Tabelle ordinabili, filtrabili e impaginate per pannelli di amministrazione, dashboard ed elenchi di ordini — con lo stile neobrutalista caratterizzato da bordi e ombre.

La data table è una guida, non un componente confezionato: la tua tabella la componi dal componente <Table /> e da TanStack Table, un motore headless per ordinamento, filtri, paginazione e selezione delle righe — con la ricetta neobrutalista di bordi spessi, ombre nette e tipografia in grassetto a occuparsi dell’aspetto.

Quando usarlo:

  • Pannelli di amministrazione — ordini, utenti e fatture con colonne ordinabili, filtri e menu di azioni per riga.
  • Dashboard — tabelle di pagamenti o attività con celle formattate, colonne di stato e dati API paginati.
  • Flussi di lavoro in blocco — selezione delle righe tramite checkbox per esportare, archiviare o eliminare molti record in una volta sola.

Introduzione

Ogni tabella di dati o datagrid che ho creato è stata unica. Si comportano tutte in modo diverso, hanno requisiti specifici di ordinamento e filtraggio e funzionano con fonti di dati diverse.

Non ha senso combinare tutte queste varianti in un unico componente. Se lo facessimo, perderemmo la flessibilità offerta dalla UI headless.

Quindi, invece di un componente data-table, ho pensato che fosse più utile fornire una guida su come costruire il tuo.

Inizieremo con il componente base <Table /> e costruiremo una tabella di dati complessa da zero.

Indice

Questa guida ti mostrerà come usare TanStack Table e il componente <Table /> per costruire la tua tabella di dati personalizzata. Tratteremo i seguenti argomenti:

Installazione

  1. Aggiungi il componente <Table /> al tuo progetto:
pnpm dlx shadcn@latest add table
npx shadcn@latest add table
yarn dlx shadcn@latest add table
bunx --bun shadcn@latest add table
  1. Aggiungi la dipendenza tanstack/react-table:
pnpm add @tanstack/react-table
npm install @tanstack/react-table
yarn add @tanstack/react-table
bun add @tanstack/react-table

Prerequisiti

Costruiremo una tabella per mostrare i pagamenti recenti. Ecco come si presentano i nostri dati:

type Payment = {
  id: string
  amount: number
  status: "pending" | "processing" | "success" | "failed"
  email: string
}
 
export const payments: Payment[] = [
  {
    id: "728ed52f",
    amount: 100,
    status: "pending",
    email: "[email protected]",
  },
  {
    id: "489e1d42",
    amount: 125,
    status: "processing",
    email: "[email protected]",
  },
  // ...
]

Struttura del progetto

Inizia creando la seguente struttura di file:

app
└── payments
    ├── columns.tsx
    ├── data-table.tsx
    └── page.tsx

Qui sto usando un esempio con Next.js, ma funziona con qualsiasi altro framework React.

  • columns.tsx (componente client) conterrà le definizioni delle nostre colonne.
  • data-table.tsx (componente client) conterrà il nostro componente <DataTable />.
  • page.tsx (componente server) è dove recupereremo i dati e renderizzeremo la nostra tabella.

Tabella base

Iniziamo costruendo una tabella base.

Definizioni delle colonne

Per prima cosa, definiremo le nostre colonne.

app/payments/columns.tsx
"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",
  },
]

Componente <DataTable />

Successivamente, creeremo un componente <DataTable /> per renderizzare la nostra tabella.

app/payments/data-table.tsx
"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>
  )
}

Renderizzare la tabella

Infine, renderizzeremo la nostra tabella nel componente della pagina.

app/payments/page.tsx
import { columns, Payment } from "./columns"
import { DataTable } from "./data-table"
 
async function getData(): Promise<Payment[]> {
  // Fetch data from your API here.
  return [
    {
      id: "728ed52f",
      amount: 100,
      status: "pending",
      email: "[email protected]",
    },
    // ...
  ]
}
 
export default async function DemoPage() {
  const data = await getData()
 
  return (
    <div className="container mx-auto py-10">
      <DataTable columns={columns} data={data} />
    </div>
  )
}

Formattazione delle celle

Formattiamo la cella dell'importo per mostrare la cifra in dollari. Allineeremo anche la cella a destra.

Aggiornare la definizione delle colonne

Aggiorna le definizioni header e cell dell'importo come segue:

app/payments/columns.tsx
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>
    },
  },
]

Puoi usare lo stesso approccio per formattare altre celle e intestazioni.

Azioni di riga

Aggiungiamo le azioni di riga alla nostra tabella. Per questo useremo un componente <DropdownMenu />.

Aggiornare la definizione delle colonne

Aggiorna la definizione delle colonne per aggiungere una nuova colonna actions. La cella actions restituisce un componente <DropdownMenu />.

app/payments/columns.tsx
"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>[] = [
  // ...
  {
    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>
      )
    },
  },
  // ...
]

Puoi accedere ai dati della riga usando row.original nella funzione cell. Usalo per gestire le azioni della tua riga, ad esempio usa l'id per effettuare una chiamata DELETE alla tua API.

Paginazione

Successivamente, aggiungeremo la paginazione alla nostra tabella.

Aggiornare <DataTable>

app/payments/data-table.tsx
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(),
  })
 
  // ...
}

Questo paginerà automaticamente le tue righe in pagine da 10. Consulta la documentazione sulla paginazione per maggiori informazioni su come personalizzare la dimensione della pagina e implementare la paginazione manuale.

Aggiungere i controlli di paginazione

Possiamo aggiungere i controlli di paginazione alla nostra tabella usando il componente <Button /> e i metodi API table.previousPage(), table.nextPage().

app/payments/data-table.tsx
import { Button } 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>
  )
}

Consulta la sezione Componenti riutilizzabili per un componente di paginazione più avanzato.

Ordinamento

Rendiamo ordinabile la colonna email.

Aggiornare <DataTable>

app/payments/data-table.tsx
"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>
  )
}

Rendere ordinabile la cella dell'intestazione

Ora possiamo aggiornare la cella dell'intestazione email per aggiungere i controlli di ordinamento.

app/payments/columns.tsx
"use client"
 
import { ColumnDef } from "@tanstack/react-table"
import { ArrowUpDown } from "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>
      )
    },
  },
]

Questo ordinerà automaticamente la tabella (crescente e decrescente) quando l'utente agisce sulla cella dell'intestazione.

Filtraggio

Aggiungiamo un campo di ricerca per filtrare le email nella nostra tabella.

Aggiornare <DataTable>

app/payments/data-table.tsx
"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, 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>
  )
}

Il filtraggio è ora abilitato per la colonna email. Puoi aggiungere filtri anche ad altre colonne. Consulta la documentazione sul filtraggio per maggiori informazioni su come personalizzare i filtri.

Visibilità

Aggiungere la visibilità delle colonne è piuttosto semplice usando l'API di visibilità di @tanstack/react-table.

Aggiornare <DataTable>

app/payments/data-table.tsx
"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,
} 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>
  )
}

Questo aggiunge un menu a discesa che puoi usare per attivare o disattivare la visibilità delle colonne.

Selezione delle righe

Successivamente, aggiungeremo la selezione delle righe alla nostra tabella.

Aggiornare le definizioni delle colonne

app/payments/columns.tsx
"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,
  },
]

Aggiornare <DataTable>

app/payments/data-table.tsx
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 [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>
  )
}

Questo aggiunge una checkbox a ogni riga e una checkbox nell'intestazione per selezionare tutte le righe.

Mostrare le righe selezionate

Puoi mostrare il numero di righe selezionate usando l'API table.getFilteredSelectedRowModel().

<div className="flex-1 text-sm text-muted-foreground">
  {table.getFilteredSelectedRowModel().rows.length} of{" "}
  {table.getFilteredRowModel().rows.length} row(s) selected.
</div>

Componenti riutilizzabili

Ecco alcuni componenti che puoi usare per costruire le tue tabelle di dati. Provengono dalla demo Tasks.

Intestazione di colonna

Rendi qualsiasi intestazione di colonna ordinabile e nascondibile.

components/data-table-column-header.tsx
import { type Column } from "@tanstack/react-table"
import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/registry/base/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/registry/base/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={cn(className)}>{title}</div>
  }

  return (
    <div className={cn("flex items-center gap-2", className)}>
      <DropdownMenu>
        <DropdownMenuTrigger
          render={
            <Button
              variant="ghost"
              size="sm"
              className="-ml-3 h-8 data-popup-open:bg-accent"
            />
          }
        >
          <span>{title}</span>
          {column.getIsSorted() === "desc" ? (
            <ArrowDown />
          ) : column.getIsSorted() === "asc" ? (
            <ArrowUp />
          ) : (
            <ChevronsUpDown />
          )}
        </DropdownMenuTrigger>
        <DropdownMenuContent align="start">
          <DropdownMenuItem onClick={() => column.toggleSorting(false)}>
            <ArrowUp />
            Asc
          </DropdownMenuItem>
          <DropdownMenuItem onClick={() => column.toggleSorting(true)}>
            <ArrowDown />
            Desc
          </DropdownMenuItem>
          <DropdownMenuSeparator />
          <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
            <EyeOff />
            Hide
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}
export const columns = [
  {
    accessorKey: "email",
    header: ({ column }) => (
      <DataTableColumnHeader column={column} title="Email" />
    ),
  },
]

Paginazione

Aggiungi i controlli di paginazione alla tua tabella, inclusi la dimensione della pagina e il numero di elementi selezionati.

import { type Table } from "@tanstack/react-table"
import {
  ChevronLeft,
  ChevronRight,
  ChevronsLeft,
  ChevronsRight,
} from "lucide-react"

import { Button } from "@/registry/base/ui/button"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/registry/base/ui/select"

const pageSizeItems = [10, 20, 25, 30, 40, 50].map((pageSize) => ({
  label: `${pageSize}`,
  value: `${pageSize}`,
}))

interface DataTablePaginationProps<TData> {
  table: Table<TData>
}

export function DataTablePagination<TData>({
  table,
}: DataTablePaginationProps<TData>) {
  return (
    <div className="flex items-center justify-between px-2">
      <div className="flex-1 text-sm text-muted-foreground">
        {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
            items={pageSizeItems}
            value={`${table.getState().pagination.pageSize}`}
            onValueChange={(value) => {
              if (value !== null) {
                table.setPageSize(Number(value))
              }
            }}
          >
            <SelectTrigger className="h-8 w-[70px]">
              <SelectValue />
            </SelectTrigger>
            <SelectContent side="top">
              {pageSizeItems.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  {item.label}
                </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"
            size="icon"
            className="hidden size-8 lg:flex"
            onClick={() => table.setPageIndex(0)}
            disabled={!table.getCanPreviousPage()}
          >
            <span className="sr-only">Go to first page</span>
            <ChevronsLeft />
          </Button>
          <Button
            variant="outline"
            size="icon"
            className="size-8"
            onClick={() => table.previousPage()}
            disabled={!table.getCanPreviousPage()}
          >
            <span className="sr-only">Go to previous page</span>
            <ChevronLeft />
          </Button>
          <Button
            variant="outline"
            size="icon"
            className="size-8"
            onClick={() => table.nextPage()}
            disabled={!table.getCanNextPage()}
          >
            <span className="sr-only">Go to next page</span>
            <ChevronRight />
          </Button>
          <Button
            variant="outline"
            size="icon"
            className="hidden size-8 lg:flex"
            onClick={() => table.setPageIndex(table.getPageCount() - 1)}
            disabled={!table.getCanNextPage()}
          >
            <span className="sr-only">Go to last page</span>
            <ChevronsRight />
          </Button>
        </div>
      </div>
    </div>
  )
}
<DataTablePagination table={table} />

Attivazione colonne

Un componente per attivare o disattivare la visibilità delle colonne.

"use client"

import { type Table } from "@tanstack/react-table"
import { Settings2 } from "lucide-react"

import { Button } from "@/registry/base/ui/button"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/registry/base/ui/dropdown-menu"

export function DataTableViewOptions<TData>({
  table,
}: {
  table: Table<TData>
}) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        render={
          <Button
            variant="outline"
            size="sm"
            className="ml-auto hidden h-8 lg:flex"
          />
        }
      >
        <Settings2 />
        View
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end" className="w-[150px]">
        {/* GroupLabel (Base UI) needs a Group ancestor or it throws error #31. */}
        <DropdownMenuGroup>
          <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
        </DropdownMenuGroup>
        <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>
  )
}
<DataTableViewOptions table={table} />

RTL

Per abilitare il supporto RTL in Neobrutalism, consulta la guida alla configurazione RTL.

Accessibilità

TanStack Table è headless — non rende alcun markup, quindi l’accessibilità viene dagli elementi di questa guida. Il componente <Table /> produce elementi nativi <table>, <th> e <td>, che danno agli screen reader il contesto di riga e colonna secondo il pattern WAI-ARIA Table. Due cose da mantenere mentre costruisci: imposta aria-sort sull’intestazione attualmente ordinata, e conserva l’aria-label sulle checkbox di selezione e il testo sr-only sui trigger di sole icone (gli snippet qui sopra fanno entrambe le cose).

Ogni controllo di questa guida è un pulsante, una checkbox o un input nativi, quindi il comportamento da tastiera è quello predefinito del browser:

TastoAzione
Tab / Shift + TabSposta il focus tra i controlli della tabella — input di filtro, intestazioni di ordinamento, menu di riga, paginazione
Space / EnterAttiva il pulsante di ordinamento o di paginazione con il focus
SpaceCommuta la checkbox della riga con il focus o quella del seleziona-tutto