Data Table
Sortable column headers and pagination controls for data tables.
| ID | |||
|---|---|---|---|
| PAY001 | success | ken99@example.com | $316.00 |
| PAY002 | success | abe45@example.com | $242.00 |
| PAY003 | processing | monserrat44@example.com | $837.00 |
| PAY004 | success | silas22@example.com | $874.00 |
| PAY005 | failed | carmella@example.com | $721.00 |
#Installation
bunx --bun barefoot add data-table#Usage
| ID | |||
|---|---|---|---|
| PAY001 | success | ken99@example.com | $316.00 |
| PAY002 | success | abe45@example.com | $242.00 |
| PAY003 | processing | monserrat44@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
| ID | Status | Amount | |
|---|---|---|---|
| PAY001 | success | ken99@example.com | $316.00 |
| PAY002 | success | abe45@example.com | $242.00 |
| PAY003 | processing | monserrat44@example.com | $837.00 |
| PAY004 | success | silas22@example.com | $874.00 |
| PAY005 | failed | carmella@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
| ID | Status | Amount | ||
|---|---|---|---|---|
| PAY001 | success | ken99@example.com | $316.00 | |
| PAY002 | success | abe45@example.com | $242.00 | |
| PAY003 | processing | monserrat44@example.com | $837.00 | |
| PAY004 | success | silas22@example.com | $874.00 | |
| PAY005 | failed | carmella@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
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | - | Column title text. |
| sorted | 'asc' | 'desc' | false | false | Current sort direction, or false if unsorted. |
| onSort | () => void | - | Callback when the sort header is clicked. |
DataTablePagination
| Prop | Type | Default | Description |
|---|---|---|---|
| children | Child | - | Page info label (e.g. "Page 1 of 3"). |
| canPrev | boolean | - | Whether previous page is available. |
| canNext | boolean | - | Whether next page is available. |
| onPrev | () => void | - | Callback for previous page. |
| onNext | () => void | - | Callback for next page. |