rapidUI

Table

Semantic, styled HTML table primitives.

Installation

pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/table.json

Installs to @/components/ui/table.

Manual installation

Copy the source into your project:

components/ui/table.tsx
'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" on TableRow
  • 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); add whitespace-normal to a cell to allow wrapping
  • Checkbox-aware cell padding
  • Style with className; TableRow has no variant prop
  • 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 scope to TableHead cells when headers need explicit association
  • Use TableCaption to describe the table for screen readers
  • Label selection checkboxes with aria-label

Best Practices

  1. Structure:

    • Use semantic table elements
    • Include headers for columns
    • Group related data
    • Consider mobile layouts
  2. Content:

    • Keep data concise
    • Align numbers right
    • Use consistent formatting
    • Include sorting when needed
  3. Styling:

    • Maintain sufficient spacing
    • Use clear borders/dividers
    • Consider zebra striping
    • Handle overflow gracefully
  4. Interaction:

    • Support keyboard navigation
    • Provide clear feedback
    • Handle selection states
    • Consider sorting/filtering
  5. 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>
  • Data Table - For sorting, filtering and other advanced table features
  • Pagination - For paginated tables
  • Checkbox - For row selection
  • Badge - For status cells