Overview
The Table component provides a complete set of composable elements for building data tables. It supports various visual styles, sticky headers, striping, and compact layouts.
import {
Table,
TableHeader,
TableBody,
TableFooter,
TableRow,
TableHead,
TableCell,
TableCaption
} from '@repo/ui/table'
Basic Table
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@repo/ui/table'
const users = [
{ id: 1, name: 'John Doe', email: '[email protected]', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', role: 'Editor' },
{ id: 3, name: 'Alice Johnson', email: '[email protected]', role: 'Viewer' },
]
export default function Example() {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>{user.role}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}
Striped Table
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@repo/ui/table'
export default function Example() {
return (
<Table striped>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{/* table rows */}
</TableBody>
</Table>
)
}
Compact Table
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@repo/ui/table'
export default function Example() {
return (
<Table compact>
<TableHeader>
<TableRow>
<TableHead>Product</TableHead>
<TableHead>Price</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{/* table rows */}
</TableBody>
</Table>
)
}
When true, applies alternating row background colors.
When true, reduces padding and font size for a more condensed layout.
Visual style variant of the table.Options: default, data
When true, makes the table header stick to the top on scroll with max height of 70vh.
When true, makes the header sticky within a dialog with max height of 96 (384px).
Additional CSS classes to apply to the table.
Additional CSS classes to apply to the table header.
TableBody
Additional CSS classes to apply to the table body.
Additional CSS classes to apply to the table footer.
TableRow
Additional CSS classes to apply to the table row.
Supports data attributes:
data-state="selected" - Applies selected state styling
TableHead
Additional CSS classes to apply to the table head cell.
TableCell
Additional CSS classes to apply to the table cell.
TableCaption
Additional CSS classes to apply to the table caption.
Variants
Default
<Table variant="default">
{/* table content */}
</Table>
Standard table styling with default padding and spacing.
<Table variant="data">
{/* table content */}
</Table>
Optimized for data-heavy tables with:
- Fixed table layout
- Larger font size in headers
- Adjusted padding
Examples
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@repo/ui/table'
const users = [
{ id: 1, name: 'John Doe', email: '[email protected]', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', role: 'Editor' },
// ... many more rows
]
export default function Example() {
return (
<div className="p-4 bg-background rounded-lg">
<Table stickyHeader>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
<TableHead>Last updated</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.id}</TableCell>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>{user.role}</TableCell>
<TableCell>01/01/2024</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@repo/ui/table'
import { EllipsisIcon } from 'lucide-react'
const users = [
{ id: 1, name: 'John Doe', email: '[email protected]', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', role: 'Editor' },
{ id: 3, name: 'Alice Johnson', email: '[email protected]', role: 'Viewer' },
{ id: 4, name: 'Bob Brown', email: '[email protected]', role: 'Contributor' },
]
export default function Example() {
return (
<div className="p-4 bg-background rounded-lg">
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
<TableHead>Last updated</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.id}</TableCell>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>{user.role}</TableCell>
<TableCell>01/01/2024</TableCell>
<TableCell>
<div className="text-teal-500 cursor-pointer">
<EllipsisIcon />
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}
import { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption } from '@repo/ui/table'
export default function Example() {
return (
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>INV001</TableCell>
<TableCell>Paid</TableCell>
<TableCell>$250.00</TableCell>
</TableRow>
<TableRow>
<TableCell>INV002</TableCell>
<TableCell>Pending</TableCell>
<TableCell>$150.00</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={2}>Total</TableCell>
<TableCell>$400.00</TableCell>
</TableRow>
</TableFooter>
</Table>
)
}
Compact Striped Table
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@repo/ui/table'
export default function Example() {
return (
<Table compact striped>
<TableHeader>
<TableRow>
<TableHead>Product</TableHead>
<TableHead>SKU</TableHead>
<TableHead>Price</TableHead>
<TableHead>Stock</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Widget A</TableCell>
<TableCell>WID-001</TableCell>
<TableCell>$19.99</TableCell>
<TableCell>150</TableCell>
</TableRow>
<TableRow>
<TableCell>Widget B</TableCell>
<TableCell>WID-002</TableCell>
<TableCell>$29.99</TableCell>
<TableCell>75</TableCell>
</TableRow>
</TableBody>
</Table>
)
}
Data Variant Table
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@repo/ui/table'
export default function Example() {
return (
<Table variant="data">
<TableHeader>
<TableRow>
<TableHead>Metric</TableHead>
<TableHead>Value</TableHead>
<TableHead>Change</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Revenue</TableCell>
<TableCell>$1,234,567</TableCell>
<TableCell>+12.5%</TableCell>
</TableRow>
<TableRow>
<TableCell>Users</TableCell>
<TableCell>45,678</TableCell>
<TableCell>+8.3%</TableCell>
</TableRow>
</TableBody>
</Table>
)
}
Features
- Responsive: Automatically scrolls horizontally on small screens
- Hover Effects: Row hover effects with smooth transitions
- Striped Rows: Optional alternating row colors
- Compact Mode: Reduced spacing for dense data
- Sticky Headers: Headers stay visible during scroll
- Selection Support: Built-in styling for selected rows
- Checkbox Support: Optimized spacing for checkbox columns
Styling
Row States
Rows support various visual states:
- Hover: Background color change with transition
- Selected: Via
data-state="selected" attribute
- Striped: Even rows with secondary background color
- Sticky positioning when
stickyHeader is enabled
- Border bottom for visual separation
- Background color to remain visible over scrolled content
Accessibility
- Semantic HTML table elements
- Proper
<thead>, <tbody>, and <tfoot> structure
- Caption support for table descriptions
- Checkbox cells have proper spacing and alignment
- Keyboard navigable
View source: packages/ui/src/table/table.tsx