Table
A semantic table component for displaying tabular data with support for headers, footers, and aligned content.
| Feature | Status |
|---|---|
| Static data display | ✅ |
| Pagination | ✅ |
| Row actions | ✅ |
| Sorting | ❌ |
| Filtering | ❌ |
| Virtualization | ❌ |
For sorting, filtering, row selection, and virtualization, use the DataTable component which is built on Tanstack Table.
Basic Usage
| Status | Type | Invoice Number | Date | Due Date | Amount |
|---|---|---|---|---|---|
| Delayed | Installment rate | 2024-A-10 | 08.10.2025 | 22.10.2025 | 85,00 € |
| Delayed | Installment rate | 2024-A-09 | 08.09.2025 | 22.09.2025 | 85,00 € |
| Paid | Payment | - | 10.07.2025 | - | 45,00 € |
| Paid | Yearly Invoice | 2024-J-001 | 15.06.2025 | 29.06.2025 | 150,00 € |
| Refund | Cashback | 2024-G-003 | 05.05.2025 | - | -50,00 € |
import {
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
Badge,
} from "@epilot/volt-ui"
import { IconAlertTriangle, IconCircleCheck } from "@tabler/icons-react"
<Table>
<TableHeader>
<TableRow>
<TableHead>Status</TableHead>
<TableHead>Type</TableHead>
<TableHead>Invoice Number</TableHead>
<TableHead>Date</TableHead>
<TableHead>Due Date</TableHead>
<TableHead align="right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>
<Badge color="red" style="soft" className="!bg-red-100 !text-red-600 !font-semibold">
<IconAlertTriangle size={14} />
Delayed
</Badge>
</TableCell>
<TableCell>Installment rate</TableCell>
<TableCell>2024-A-10</TableCell>
<TableCell>08.10.2025</TableCell>
<TableCell>22.10.2025</TableCell>
<TableCell align="right" numeric>85,00 €</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Badge color="red" style="soft" className="!bg-red-100 !text-red-600 !font-semibold">
<IconAlertTriangle size={14} />
Delayed
</Badge>
</TableCell>
<TableCell>Installment rate</TableCell>
<TableCell>2024-A-09</TableCell>
<TableCell>08.09.2025</TableCell>
<TableCell>22.09.2025</TableCell>
<TableCell align="right" numeric>85,00 €</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Badge color="green" style="soft" className="!font-semibold">
<IconCircleCheck size={14} />
Paid
</Badge>
</TableCell>
<TableCell>Payment</TableCell>
<TableCell>-</TableCell>
<TableCell>10.07.2025</TableCell>
<TableCell>-</TableCell>
<TableCell align="right" numeric>45,00 €</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Badge color="green" style="soft" className="!font-semibold">
<IconCircleCheck size={14} />
Paid
</Badge>
</TableCell>
<TableCell>Yearly Invoice</TableCell>
<TableCell>2024-J-001</TableCell>
<TableCell>15.06.2025</TableCell>
<TableCell>29.06.2025</TableCell>
<TableCell align="right" numeric>150,00 €</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Badge color="teal" style="soft" className="!font-semibold">
<IconCircleCheck size={14} />
Refund
</Badge>
</TableCell>
<TableCell>Cashback</TableCell>
<TableCell>2024-G-003</TableCell>
<TableCell>05.05.2025</TableCell>
<TableCell>-</TableCell>
<TableCell align="right" numeric>-50,00 €</TableCell>
</TableRow>
</TableBody>
</Table>Simple Table
| Name | Amount | |
|---|---|---|
| John Doe | john@example.com | $250.00 |
| Jane Smith | jane@example.com | $150.00 |
| Bob Wilson | bob@example.com | $350.00 |
| Total | $750.00 | |
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead align="right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>John Doe</TableCell>
<TableCell>john@example.com</TableCell>
<TableCell align="right" numeric>$250.00</TableCell>
</TableRow>
<TableRow>
<TableCell>Jane Smith</TableCell>
<TableCell>jane@example.com</TableCell>
<TableCell align="right" numeric>$150.00</TableCell>
</TableRow>
<TableRow>
<TableCell>Bob Wilson</TableCell>
<TableCell>bob@example.com</TableCell>
<TableCell align="right" numeric>$350.00</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={2}>Total</TableCell>
<TableCell align="right" numeric>$750.00</TableCell>
</TableRow>
</TableFooter>
</Table>With Row Actions
Use DropdownMenu to add action menus to table rows. The empty TableHead with a fixed width creates space for the actions column.
| Status | Invoice | Customer | Amount | |
|---|---|---|---|---|
| Delayed | 2024-A-10 | John Doe | 85.00 € | |
| Paid | 2024-A-09 | Jane Smith | 45.00 € | |
| Paid | 2024-J-001 | Bob Wilson | 150.00 € |
import {
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
Badge,
Button,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
} from "@epilot/volt-ui"
import {
IconDotsVertical,
IconPencil,
IconCopy,
IconTrash,
IconEye,
IconAlertTriangle,
IconCircleCheck,
} from "@tabler/icons-react"
<Table>
<TableHeader>
<TableRow>
<TableHead>Status</TableHead>
<TableHead>Invoice</TableHead>
<TableHead>Customer</TableHead>
<TableHead align="right">Amount</TableHead>
<TableHead className="w-12"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>
<Badge color="red" style="soft" className="!bg-red-100 !text-red-600 !font-semibold">
<IconAlertTriangle size={14} />
Delayed
</Badge>
</TableCell>
<TableCell>2024-A-10</TableCell>
<TableCell>John Doe</TableCell>
<TableCell align="right" numeric>85.00 €</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="tertiary" size="icon-sm">
<IconDotsVertical />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<IconEye />
View
</DropdownMenuItem>
<DropdownMenuItem>
<IconPencil />
Edit
<DropdownMenuShortcut>Ctrl+E</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<IconCopy />
Duplicate
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem destructive>
<IconTrash />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Badge color="green" style="soft" className="!font-semibold">
<IconCircleCheck size={14} />
Paid
</Badge>
</TableCell>
<TableCell>2024-A-09</TableCell>
<TableCell>Jane Smith</TableCell>
<TableCell align="right" numeric>45.00 €</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="tertiary" size="icon-sm">
<IconDotsVertical />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<IconEye />
View
</DropdownMenuItem>
<DropdownMenuItem>
<IconPencil />
Edit
<DropdownMenuShortcut>Ctrl+E</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<IconCopy />
Duplicate
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem destructive>
<IconTrash />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Badge color="green" style="soft" className="!font-semibold">
<IconCircleCheck size={14} />
Paid
</Badge>
</TableCell>
<TableCell>2024-J-001</TableCell>
<TableCell>Bob Wilson</TableCell>
<TableCell align="right" numeric>150.00 €</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="tertiary" size="icon-sm">
<IconDotsVertical />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<IconEye />
View
</DropdownMenuItem>
<DropdownMenuItem>
<IconPencil />
Edit
<DropdownMenuShortcut>Ctrl+E</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<IconCopy />
Duplicate
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem destructive>
<IconTrash />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
</TableBody>
</Table>With Pagination
TablePagination is a controlled component - the parent manages all state and data fetching. This design works seamlessly with both server-side pagination (API returns pages) and client-side pagination (local data array).
| Status | Invoice | Customer | Amount |
|---|---|---|---|
| Paid | 2024-A-001 | John Doe | 202.00 € |
| Paid | 2024-A-002 | Jane Smith | 142.00 € |
| Paid | 2024-A-003 | Bob Wilson | 193.00 € |
| Paid | 2024-A-004 | Alice Brown | 82.00 € |
| Paid | 2024-A-005 | Charlie Davis | 86.00 € |
| Paid | 2024-A-006 | Eva Martinez | 54.00 € |
| Paid | 2024-A-007 | Frank Miller | 232.00 € |
| Delayed | 2024-A-008 | Grace Lee | 130.00 € |
| Paid | 2024-A-009 | Henry Chen | 221.00 € |
| Delayed | 2024-A-010 | Isabel Garcia | 144.00 € |
Client-Side Pagination
For local data arrays, use the useClientPagination hook to handle state and slicing automatically:
import { Table, TablePagination, useClientPagination } from "@epilot/volt-ui"
function MyTable() {
const { pageData, paginationProps } = useClientPagination({
data: allItems, // Your full data array
initialPageSize: 10, // Optional, default 10
})
return (
<div>
<Table>
{pageData.map(item => (
<TableRow key={item.id}>...</TableRow>
))}
</Table>
<TablePagination {...paginationProps} />
</div>
)
}Server-Side Pagination
For paginated APIs (e.g., with TanStack Query), manage state manually and convert page numbers to API offsets:
import { useState } from "react"
import { Table, TablePagination } from "@epilot/volt-ui"
import { useQuery } from "@tanstack/react-query"
function MyTable() {
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
// Convert page to offset for API
const from = (page - 1) * pageSize
const { data } = useQuery({
queryKey: ["items", from, pageSize],
queryFn: () => fetchItems({ from, size: pageSize }),
})
return (
<div>
<Table>
{data?.results.map(item => (
<TableRow key={item.id}>...</TableRow>
))}
</Table>
<TablePagination
page={page}
totalItems={data?.hits ?? 0}
pageSize={pageSize}
onPageChange={setPage}
onPageSizeChange={(size) => {
setPageSize(size)
setPage(1)
}}
/>
</div>
)
}API Reference
Table
Root container that wraps the native <table> element with responsive overflow handling.
| Prop | Type | Default | Description |
|---|---|---|---|
density | "compact" | "normal" | "comfortable" | "normal" | Default cell density for all TableCells. |
className | string | - | Additional classes for the container. |
TableHeader
Wrapper for the table header section (<thead>).
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional classes for the header. |
TableBody
Wrapper for the table body section (<tbody>).
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional classes for the body. |
TableFooter
Wrapper for the table footer section (<tfoot>).
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional classes for the footer. |
TableRow
A table row (<tr>).
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional classes for the row. |
TableHead
A header cell (<th>).
| Prop | Type | Default | Description |
|---|---|---|---|
align | "left" | "center" | "right" | "left" | Horizontal text alignment. |
density | "compact" | "normal" | "comfortable" | - | Override table density. compact (h-8), normal (h-9), comfortable (h-10). |
scope | "col" | "row" | "colgroup" | "rowgroup" | "col" | Scope of the header for screen readers. |
className | string | - | Additional classes for the header. |
TableCell
A data cell (<td>).
| Prop | Type | Default | Description |
|---|---|---|---|
align | "left" | "center" | "right" | "left" | Horizontal text alignment. |
numeric | boolean | false | Enables tabular-nums for consistent number widths. |
density | "compact" | "normal" | "comfortable" | - | Override table density. compact (py-1.5), normal (py-2), comfortable (py-3). |
className | string | - | Additional classes for the cell. |
TableCaption
An accessible caption for the table (<caption>).
| Prop | Type | Default | Description |
|---|---|---|---|
position | "top" | "bottom" | "bottom" | Position of the caption. |
className | string | - | Additional classes for the caption. |
TablePagination
Controlled pagination component for tables with page number buttons, navigation controls, and page size selector.
| Prop | Type | Default | Description |
|---|---|---|---|
page | number | - | Current page number (1-indexed). |
totalItems | number | - | Total number of items across pages. |
pageSize | number | - | Number of items per page. |
onPageChange | (page: number) => void | - | Callback when page changes. |
onPageSizeChange | (pageSize: number) => void | - | Callback when page size changes. |
pageSizeOptions | number[] | [10, 25, 50, 100] | Available page size options. |
siblingCount | number | 2 | Number of sibling pages to show on each side of current page. |
disabled | boolean | false | Disable all pagination controls. |
className | string | - | Additional classes for the container. |
useClientPagination
Hook for client-side pagination of local data arrays. Handles state management and data slicing.
Options:
| Option | Type | Default | Description |
|---|---|---|---|
data | T[] | - | The full data array to paginate. |
initialPage | number | 1 | Initial page number. |
initialPageSize | number | 10 | Initial page size. |
pageSizeOptions | number[] | [10, 25, 50, 100] | Available page size options. |
Returns:
| Property | Type | Description |
|---|---|---|
pageData | T[] | Sliced data for the current page. |
page | number | Current page number. |
pageSize | number | Current page size. |
setPage | (page: number) => void | Set the current page. |
setPageSize | (size: number) => void | Set page size (resets to page 1). |
meta | PaginationMeta | { totalPages, hasNextPage, ... } |
paginationProps | object | Props to spread on <TablePagination />. |