Context MenuDate Picker
Get Started
Introduction
Components
Accordion
Badge
Button
Card
Checkbox
Command
Dialog
Dropdown Menu
Input
Select
Switch
Table
Tabs
Toast
Tooltip
Forms
Controlled Input
Field Arrays
Submit
Validation

Sortable column headers and pagination controls for data tables.

IDEmail
PAY001successken99@example.com$316.00
PAY002successabe45@example.com$242.00
PAY003processingmonserrat44@example.com$837.00
PAY004successsilas22@example.com$874.00
PAY005failedcarmella@example.com$721.00

#Installation

bunx --bun barefoot add data-table

#Usage

IDEmail
PAY001successken99@example.com$316.00
PAY002successabe45@example.com$242.00
PAY003processingmonserrat44@example.com$837.00
Page 1 of 2
1"use client"23import { createSignal, createMemo } from "@barefootjs/dom"4import {5  Table, TableBody, TableCell, TableHead,6  TableHeader, TableRow,7} from "@/components/ui/table"8import {9  DataTableColumnHeader, DataTablePagination,10} from "@/components/ui/data-table"1112type Payment = {13  id: string14  amount: number15  status: string16  email: string17}1819const payments: Payment[] = [20  { id: "PAY001", amount: 316, status: "success", email: "ken99@example.com" },21  { id: "PAY002", amount: 242, status: "success", email: "abe45@example.com" },22  { id: "PAY003", amount: 837, status: "processing", email: "monserrat44@example.com" },23  { id: "PAY004", amount: 874, status: "success", email: "silas22@example.com" },24  { id: "PAY005", amount: 721, status: "failed", email: "carmella@example.com" },25]2627function DataTableDemo() {28  const [sortKey, setSortKey] = createSignal<"amount" | null>(null)29  const [sortDir, setSortDir] = createSignal<"asc" | "desc">("asc")30  const [page, setPage] = createSignal(0)31  const pageSize = 33233  const handleSort = () => {34    if (sortKey() === null) {35      setSortKey("amount")36      setSortDir("asc")37    } else {38      setSortDir(sortDir() === "asc" ? "desc" : "asc")39    }40  }4142  const sorted = createMemo(() => {43    if (!sortKey()) return payments44    const dir = sortDir()45    return [...payments].sort((a, b) =>46      dir === "asc" ? a.amount - b.amount : b.amount - a.amount47    )48  })4950  const pageCount = createMemo(() =>51    Math.max(1, Math.ceil(sorted().length / pageSize))52  )5354  const paginated = createMemo(() =>55    sorted().slice(page() * pageSize, (page() + 1) * pageSize)56  )5758  return (59    <div className="space-y-4">60      <Table>61        <TableHeader>62          <TableRow>63            <TableHead>ID</TableHead>64            <TableHead>Status</TableHead>65            <TableHead>Email</TableHead>66            <TableHead className="text-right">67              <DataTableColumnHeader68                title="Amount"69                sorted={sortKey() ? sortDir() : false}70                onSort={handleSort}71              />72            </TableHead>73          </TableRow>74        </TableHeader>75        <TableBody>76          {paginated().map((p) => (77            <TableRow>78              <TableCell>{p.id}</TableCell>79              <TableCell>{p.status}</TableCell>80              <TableCell>{p.email}</TableCell>81              <TableCell className="text-right">82                ${p.amount.toFixed(2)}83              </TableCell>84            </TableRow>85          ))}86        </TableBody>87      </Table>88      <DataTablePagination89        canPrev={page() > 0}90        canNext={page() < pageCount() - 1}91        onPrev={() => setPage(p => p - 1)}92        onNext={() => setPage(p => p + 1)}93      >94        Page ${page() + 1} of ${pageCount()}95      </DataTablePagination>96    </div>97  )98}

#Examples

#Filtering

IDStatusEmailAmount
PAY001successken99@example.com$316.00
PAY002successabe45@example.com$242.00
PAY003processingmonserrat44@example.com$837.00
PAY004successsilas22@example.com$874.00
PAY005failedcarmella@example.com$721.00
Page 1 of 3
1"use client"23import { createSignal, createMemo } from "@barefootjs/dom"4import {5  Table, TableBody, TableCell, TableHead,6  TableHeader, TableRow,7} from "@/components/ui/table"8import { DataTablePagination } from "@/components/ui/data-table"910const data = [11  { id: "PAY001", amount: 316, status: "success", email: "ken99@example.com" },12  { id: "PAY002", amount: 242, status: "success", email: "abe45@example.com" },13  { id: "PAY003", amount: 837, status: "processing", email: "monserrat44@example.com" },14  { id: "PAY004", amount: 874, status: "success", email: "silas22@example.com" },15  { id: "PAY005", amount: 721, status: "failed", email: "carmella@example.com" },16  // ... more rows17]1819function DataTableFiltering() {20  const [filter, setFilter] = createSignal("")21  const [page, setPage] = createSignal(0)22  const pageSize = 52324  const filteredData = createMemo(() =>25    data.filter(row =>26      row.email.toLowerCase().includes(filter().toLowerCase())27    )28  )2930  const pageCount = createMemo(() =>31    Math.max(1, Math.ceil(filteredData().length / pageSize))32  )33  const paginatedData = createMemo(() =>34    filteredData().slice(page() * pageSize, (page() + 1) * pageSize)35  )3637  return (38    <div className="space-y-4">39      <input40        type="text"41        placeholder="Filter emails..."42        className="flex h-9 w-full max-w-sm rounded-md border border-input bg-transparent px-3 py-1 text-base"43        value={filter()}44        onInput={(e) => { setFilter(e.target.value); setPage(0) }}45      />46      <Table>47        <TableHeader>48          <TableRow>49            <TableHead>ID</TableHead>50            <TableHead>Status</TableHead>51            <TableHead>Email</TableHead>52            <TableHead className="text-right">Amount</TableHead>53          </TableRow>54        </TableHeader>55        <TableBody>56          {paginatedData().map((p) => (57            <TableRow>58              <TableCell>{p.id}</TableCell>59              <TableCell>{p.status}</TableCell>60              <TableCell>{p.email}</TableCell>61              <TableCell className="text-right">62                ${p.amount.toFixed(2)}63              </TableCell>64            </TableRow>65          ))}66        </TableBody>67      </Table>68      <DataTablePagination69        canPrev={page() > 0}70        canNext={page() < pageCount() - 1}71        onPrev={() => setPage(p => p - 1)}72        onNext={() => setPage(p => p + 1)}73      >74        Page ${page() + 1} of ${pageCount()}75      </DataTablePagination>76    </div>77  )78}

#Row Selection

IDStatusEmailAmount
PAY001successken99@example.com$316.00
PAY002successabe45@example.com$242.00
PAY003processingmonserrat44@example.com$837.00
PAY004successsilas22@example.com$874.00
PAY005failedcarmella@example.com$721.00
0 of 5 row(s) selected.
1"use client"23import { createSignal, createMemo } from "@barefootjs/dom"4import {5  Table, TableBody, TableCell, TableHead,6  TableHeader, TableRow,7} from "@/components/ui/table"8import { Checkbox } from "@/components/ui/checkbox"910const data = [11  { id: "PAY001", amount: 316, status: "success", email: "ken99@example.com" },12  { id: "PAY002", amount: 242, status: "success", email: "abe45@example.com" },13  { id: "PAY003", amount: 837, status: "processing", email: "monserrat44@example.com" },14  { id: "PAY004", amount: 874, status: "success", email: "silas22@example.com" },15  { id: "PAY005", amount: 721, status: "failed", email: "carmella@example.com" },16]1718function DataTableSelection() {19  const [selected, setSelected] = createSignal(data.map(() => false))20  const selectedCount = createMemo(() => selected().filter(Boolean).length)21  const isAllSelected = createMemo(() => selectedCount() === data.length)2223  const toggleAll = () => {24    setSelected(isAllSelected() ? data.map(() => false) : data.map(() => true))25  }2627  const toggleRow = (index) => {28    setSelected(prev => prev.map((v, i) => i === index ? !v : v))29  }3031  return (32    <div>33      <Table>34        <TableHeader>35          <TableRow>36            <TableHead>37              <Checkbox38                checked={isAllSelected()}39                onCheckedChange={toggleAll}40                aria-label="Select all"41              />42            </TableHead>43            <TableHead>ID</TableHead>44            <TableHead>Status</TableHead>45            <TableHead>Email</TableHead>46            <TableHead className="text-right">Amount</TableHead>47          </TableRow>48        </TableHeader>49        <TableBody>50          {data.map((row, i) => (51            <TableRow data-state={selected()[i] ? "selected" : undefined}>52              <TableCell>53                <Checkbox54                  checked={selected()[i]}55                  onCheckedChange={() => toggleRow(i)}56                />57              </TableCell>58              <TableCell>{row.id}</TableCell>59              <TableCell>{row.status}</TableCell>60              <TableCell>{row.email}</TableCell>61              <TableCell className="text-right">62                ${row.amount.toFixed(2)}63              </TableCell>64            </TableRow>65          ))}66        </TableBody>67      </Table>68      <div className="text-sm text-muted-foreground">69        {selectedCount()} of {data.length} row(s) selected.70      </div>71    </div>72  )73}

#API Reference

DataTableColumnHeader

PropTypeDefaultDescription
titlestring-Column title text.
sorted'asc' | 'desc' | falsefalseCurrent sort direction, or false if unsorted.
onSort() => void-Callback when the sort header is clicked.

DataTablePagination

PropTypeDefaultDescription
childrenChild-Page info label (e.g. "Page 1 of 3").
canPrevboolean-Whether previous page is available.
canNextboolean-Whether next page is available.
onPrev() => void-Callback for previous page.
onNext() => void-Callback for next page.