Table
Structured data table component with loading and empty states for PyColors UI.
Structured data for SaaS interfacesLink to section
The Table component displays structured, comparable data.
Use it for:
- users
- projects
- invoices
- subscriptions
- logs
- reports
- audit events
- admin records
Table provides a consistent visual foundation for dense product data:
- semantic table structure
- design-token styling
- horizontal overflow handling
- row hover feedback
- built-in empty state
- built-in loading state
Core idea
Use Table when users need to compare structured records across columns. Use cards when the content is more editorial, visual, or action-led.
ImportLink to section
import { Table } from "@pycolors/ui";Basic usageLink to section
| Name | Role | |
|---|---|---|
| Jane Doe | jane@example.com | Admin |
| John Smith | john@example.com | User |
import {
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
} from "@pycolors/ui";
export function UsersTable() {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Jane Doe</TableCell>
<TableCell>jane@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
<TableRow>
<TableCell>John Smith</TableCell>
<TableCell>john@example.com</TableCell>
<TableCell>User</TableCell>
</TableRow>
</TableBody>
</Table>
);
}When to use TableLink to section
Use Table when the interface needs scanning, comparison, and operational density.
Repeatable records
Use for users, projects, invoices, subscriptions, logs, and admin records.
Comparable data
Use when users compare values across stable columns.
Operational views
Use with filters, pagination, row actions, and bulk operations.
CaptionLink to section
Use TableCaption when the table needs a concise summary.
<Table>
<TableCaption>List of active users</TableCaption>
{/* rows */}
</Table>Captions should explain what the table contains, not repeat the page heading.
Empty stateLink to section
Use TableEmpty when the request succeeded but no rows are available.
<TableBody>
<TableEmpty colSpan={3} />
</TableBody><TableBody>
<TableEmpty
colSpan={3}
title="No users found"
description="Invite your first user to get started."
/>
</TableBody>Empty state rule
Empty is not an error. Explain why the table is empty and what the user can do next.
Loading stateLink to section
Use TableLoading when the table structure is known but the data is still loading.
<TableBody>
<TableLoading colSpan={3} />
</TableBody>Use loading rows instead of removing the table from the page.
This preserves layout and reduces visual jump.
Table compositionLink to section
A production table usually belongs inside a larger feature composition.
export function ProjectsTableSection() {
return (
<section className="space-y-4">
<ProjectsToolbar />
<Table>
<ProjectsTableHeader />
<ProjectsTableBody />
</Table>
<ProjectsPagination />
</section>
);
}The table renders structure.
The feature layer owns:
- data fetching
- filters
- sorting
- pagination
- permissions
- mutations
- row actions
Architecture rule
The table should not own the data source. Keep fetching, filtering, sorting, and mutations in the feature layer.
Usage patternsLink to section
Define the columnsLink to section
Choose stable columns that help users scan and compare records.
Add loading and empty statesLink to section
Do not leave the table area blank while waiting for data or when no data exists.
Keep row actions compactLink to section
Use a Dropdown Menu for secondary row actions and a Dialog for destructive confirmation.
Add pagination when neededLink to section
Use Pagination when the data set is larger than a single page.
Move complex behavior into a patternLink to section
Use the Data Table Pattern when you need filters, actions, pagination, loading, empty, and error states together.
Table vs Card vs Data Table PatternLink to section
| Situation | Use |
|---|---|
| Structured comparable data | Table |
| Visual or content-heavy items | Card |
| Small list with one primary action per item | Card or custom list |
| Filters, pagination, row actions, states | Data Table Pattern |
| Timeline or activity feed | Custom list |
| Mobile-first browsing | Cards or responsive list |
Decision rule
Table is a primitive. Data Table Pattern is the full product experience around it.
APILink to section
ComponentsLink to section
| Component | Description |
|---|---|
Table | Wrapper with border and overflow handling |
TableHeader | Table header section |
TableBody | Table body section |
TableRow | Table row with hover feedback |
TableHead | Header cell |
TableCell | Data cell |
TableCaption | Optional caption |
TableEmpty | Empty state row |
TableLoading | Loading state row |
TableEmpty propsLink to section
| Prop | Type | Default | Description |
|---|---|---|---|
colSpan | number | — | Number of columns to span |
title | string | default copy | Empty state title |
description | string | default copy | Empty state description |
className | string | — | Custom classes |
TableLoading propsLink to section
| Prop | Type | Default | Description |
|---|---|---|---|
colSpan | number | — | Number of columns to span |
label | string | default copy | Loading message |
className | string | — | Custom classes |
All base table components extend their respective native HTML element props.
React.HTMLAttributes<HTMLTableElement>
React.HTMLAttributes<HTMLTableSectionElement>
React.HTMLAttributes<HTMLTableRowElement>
React.ThHTMLAttributes<HTMLTableCellElement>
React.TdHTMLAttributes<HTMLTableCellElement>
React.HTMLAttributes<HTMLTableCaptionElement>AccessibilityLink to section
Tables should preserve semantic structure.
Guidelines:
- use
TableHeadfor column headers - include a caption when the context is not obvious
- avoid using tables for layout
- keep cells readable and concise
- ensure interactive row actions are keyboard accessible
- avoid making the whole row clickable unless the interaction is clear
- use visible labels for icon-only row actions
Accessibility rule
Tables are for tabular data. Do not use table markup only to align content visually.
Prefer / avoidLink to section
Prefer
- clear column headers
- reasonable column count
- empty and loading states
- Dropdown Menu for row actions
- Pagination for larger data sets
Avoid
- tables for non-tabular content
- too many columns in narrow layouts
- blank states while loading
- complex layouts inside cells
- data fetching inside the table primitive
Product copy guidelinesLink to section
Table labels should be short and scannable.
Column headers
Prefer concise labels like Name, Email, Role, Status, Amount, Created, Actions.
Empty state
Explain the state and provide a next step: “No users found. Invite your first user to get started.”
Loading state
Keep the message neutral and short: “Loading data…” or “Loading users…”