// preview in
saved for every page

Table

Shows records in rows and columns so people can compare them, like invoices or stock. For sorting, filtering and pages of data, use Data table.

Pattern WAI-ARIA tableServer componentSource ↗

Installation

Set up Camaleo once (Installation), then import what you need. It renders on the server.

import { Table, TableCaption, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell } from "@camaleo/react";

Anatomy

A native <table> inside a container that scrolls sideways when the table is too wide. Every part has a data-slot you can target from CSS.

Examples

Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.

minimal · light · #3fa34d
InvoiceStatusAmount
INV-4029Paid$340.00
INV-4028Pending$1,280.50
INV-4027Overdue$75.00
Invoices issued in October 2026
InvoiceIssuedAmount
INV-402910/14/2026$340.00
INV-402810/12/2026$1,280.50

With a caption

A TableCaption names the table. Screen readers read it before the rows, and it also names the scroll area when the table gets too wide.

CustomerOrdersRevenue
Acme Logistics142$48,200.00
Vila Nova Design89$24,650.00
Aurora BioTech12$8,400.00

Numbers on the right

Pass align="end" to the header and the cells of number columns. Digits get the same width, so people can compare values at a glance.

InvoiceCustomerAmount
INV-4029Acme Logistics$340.00
INV-4028Vila Nova Design$1,280.50
INV-4027Aurora BioTech$75.00
Total$1,695.50

Totals row

Put the total in a TableFooter, below a stronger line. It stays apart from the data rows, so sorting or adding rows never moves it.

ProductStockPrice
Mechanical keyboard TKLIn stock$149.00
Wireless ergonomic mouse3 left$89.00
USB-C hub, 8 portsOut of stock$65.00

Status in a column

A badge per row is easier to scan than a sentence. Keep the words short and use the same ones in every row.

InvoiceCustomerAmount
No invoices yet

Invoices you send to customers show up here.

No rows yet

Keep the headers and fill the body with one row that spans every column. Say why it is empty and give the next step.

Customer
Vila Nova Design$1,250.00
Acme Logistics$4,800.00
Aurora BioTech$9,320.00

Sortable column

Click Revenue. The button flips the order, and aria-sort on the header tells screen readers which way the rows are sorted.

All invoices, October 2026
InvoiceCustomerIssuedDuePaymentStatusAmount
INV-4029Acme Logistics10/14/202611/13/2026CardPaid$340.00
INV-4028Vila Nova Design10/12/202611/11/2026Bank transferPending$1,280.50
INV-4027Aurora BioTech10/01/202610/15/2026CardOverdue$75.00

Wide table on a phone

On a narrow screen this table scrolls sideways. Only then the container becomes a named scroll area with one Tab stop, so keyboard users can scroll it with the arrow keys.

In context

Tables usually sit in a card or a page section, with actions in the last column. These are whole pieces of UI you can copy, in the skin you picked.

InvoiceIssuedStatusAmountActions
INV-402910/14/2026Paid$340.00
INV-402810/12/2026Pending$1,280.50
INV-402710/01/2026Overdue$75.00
Invoices with downloadsTable · Badge · Button
NameRoleEmailStatus
APAline Prado
Frontend architectaline@camaleo.devActive
TCTiago Costa
DevOps engineertiago@camaleo.devAway
Team membersTable · Avatar · Badge
SKUItemQuantityUnit price
SKU-8921Aluminum monitor arm28$129.00
SKU-8922Wool felt desk mat, large114$34.50
SKU-8923Oak laptop stand0$58.00
Stock listTable

Guidelines

CustomerAmount
Acme Logistics$24,650.00
Vila Nova Design$75.00
DoAlign numbers to the right.

Units line up with units, so the biggest value stands out without reading every row.

CustomerAmount
Acme Logistics$24,650.00
Vila Nova Design$75.00
Don’tNumbers aligned to the left.

Decimals end up in different places, and people have to read each value to compare.

Invoices due in November 2026
InvoiceAmount
INV-4029$340.00
DoName every table.

Use a TableCaption, or aria-label when the title is already on the page. People hear what the data is before the first row.

Column AColumn B
Data 1120.00
Don’tHeaders like "Column A".

Screen readers read the header with each cell. A vague header makes every cell vague.

Props

PropTypeDefaultDescription
scrollLabelstring"Table"Names the scroll region when the table overflows and has no <caption>.
Also accepts every prop of the underlying primitive and native attributes.
PropTypeDefaultDescription
align"end" | "start""start"Aligns the content. Use end for numbers: it aligns them to the right and gives every digit the same width, so values line up down the column.

TableHead and TableCell take align. Every part also takes the attributes of its HTML element, like colSpan and aria-sort. TableCaption (a <caption>, first inside Table) and TableFooter (a <tfoot>) take the attributes of their elements.

Styling hooks

Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the table itself.

Attributes

data-alignstart · end
roleregion on table-container, only while it overflows

Tokens it reads

Color

--cml-border--cml-fg--cml-fg-muted--cml-focus-offset--cml-focus-ring--cml-focus-width--cml-surface-raised

Shape & size

--cml-border-width--cml-font-size-field--cml-radius-popover

Type & motion

--cml-font-label--cml-font-weight-control

This component only

--cml-corner-shape

Accessibility

TabMoves to buttons and links inside cells. When the table is too wide, the scroll area is one extra stop.
← / →Scroll a wide table sideways while its scroll area has focus.
Was this page helpful? · Edit this page on GitHub