Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/table.jsonInstalls to @/components/ui/table.
Manual installation
Copy the source into your project:
'use client'
import type * as React from 'react'
import { cn } from '@/lib/utils'
function Table({ className, ...props }: React.ComponentProps<'table'>) {
return (
<div data-slot="table-container" className="relative w-full overflow-x-auto">
<table data-slot="table" className={cn('w-full caption-bottom text-sm', className)} {...props} />
</div>
)
}
function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
return <thead data-slot="table-header" className={cn('[&_tr]:border-b', className)} {...props} />
}
function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {
return <tbody data-slot="table-body" className={cn('[&_tr:last-child]:border-0', className)} {...props} />
}
function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
return (
<tfoot
data-slot="table-footer"
className={cn('border-t bg-muted font-medium [&>tr]:last:border-b-0', className)}
{...props}
/>
)
}
function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
return (
<tr
data-slot="table-row"
className={cn(
'border-b text-foreground transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',
className,
)}
{...props}
/>
)
}
function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
return (
<th
data-slot="table-head"
scope="col"
className={cn(
'h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0',
className,
)}
{...props}
/>
)
}
function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
return (
<td
data-slot="table-cell"
className={cn('p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0', className)}
{...props}
/>
)
}
function TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {
return <caption data-slot="table-caption" className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
}
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }A responsive table component with support for headers, footers, and various styling options.
Features
- Semantic HTML table structure
- Support for headers, footers, and captions
- Row hover state, and a selected state via
data-state="selected"onTableRow - Rows containing an expanded element (
aria-expanded, e.g. an open dropdown trigger) stay highlighted - Horizontal scrolling through a wrapping container (
data-slot="table-container") - Cells don't wrap (
whitespace-nowrap); addwhitespace-normalto a cell to allow wrapping - Checkbox-aware cell padding
- Style with
className;TableRowhas novariantprop - Integration with other components (Checkbox, Badge, Dropdown Menu)
Usage
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
export function InvoicesTable() {
return (
<Table>
<TableCaption>Your invoices from the municipality.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead>Service</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">8830 1142</TableCell>
<TableCell>Waste collection, Q1</TableCell>
<TableCell>Unpaid</TableCell>
<TableCell className="text-right">1 245 kr</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total</TableCell>
<TableCell className="text-right">1 245 kr</TableCell>
</TableRow>
</TableFooter>
</Table>
)
}Examples
Default
A resident's municipal invoices with a caption, status badges, right-aligned amounts and a total in the footer.
With Selection
Row selection with checkboxes in a case inbox. Set data-state="selected" on a TableRow to highlight it; the header
checkbox shows indeterminate when only some rows are selected, and a bulk action appears above the table.
<TableRow data-state={isSelected ? 'selected' : undefined}>
<TableCell>
<Checkbox checked={isSelected} onCheckedChange={...} aria-label="Select case 2026-04812" />
</TableCell>
...
</TableRow>With Actions
Building permit applications with avatars, status badges and a dropdown menu of actions per row. The icon-only
trigger has an aria-label that names the row.
Compact
A denser council agenda, made with className overrides on the table, header and cells.
In Card
A table flush with the edges of a Card (CardContent with px-0), with a Progress bar per row showing demand for
school places.
Loading
While rows load, keep the header, caption and footer and show a Skeleton in each cell where text will appear. Each
placeholder is as tall as the line it replaces (a 20px row with a thinner bar inside), badges get a pill of the same
size, and widths vary per row, so the table keeps its size and nothing moves when the data arrives. Mark the table
aria-busy while loading, hide the placeholder rows from screen readers and announce the result in a live region.
Click Reload to see the switch.
Anatomy
<Table>
<TableCaption />
<TableHeader>
<TableRow>
<TableHead />
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell />
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell />
</TableRow>
</TableFooter>
</Table>Accessibility
The Table component follows WAI-ARIA guidelines:
- Uses semantic HTML table elements (
table,thead,tbody,tfoot,th,td,caption) - Add
scopetoTableHeadcells when headers need explicit association - Use
TableCaptionto describe the table for screen readers - Label selection checkboxes with
aria-label
Best Practices
-
Structure:
- Use semantic table elements
- Include headers for columns
- Group related data
- Consider mobile layouts
-
Content:
- Keep data concise
- Align numbers right
- Use consistent formatting
- Include sorting when needed
-
Styling:
- Maintain sufficient spacing
- Use clear borders/dividers
- Consider zebra striping
- Handle overflow gracefully
-
Interaction:
- Support keyboard navigation
- Provide clear feedback
- Handle selection states
- Consider sorting/filtering
-
Accessibility:
- Use proper headers
- Maintain color contrast
- Support screen readers
- Consider mobile access
Customization
Custom Styling
Modify the appearance using Tailwind classes:
<Table className="border rounded-lg">
<TableHeader className="bg-muted">
<TableRow>
<TableHead className="font-bold">Header</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow className="hover:bg-accent">
<TableCell className="p-4">Cell</TableCell>
</TableRow>
</TableBody>
</Table>Responsive Design
Handle mobile layouts with responsive classes:
<Table className="min-w-[600px] md:min-w-full">
<TableHeader>
<TableRow>
<TableHead className="hidden md:table-cell">Desktop Only</TableHead>
<TableHead>Always Visible</TableHead>
</TableRow>
</TableHeader>
</Table>Related Components
- Data Table - For sorting, filtering and other advanced table features
- Pagination - For paginated tables
- Checkbox - For row selection
- Badge - For status cells