| Plan | Per month |
|---|---|
| Starter | $9 |
| Team | $29 |
import {
Table,
TableBody,
TableData,
TableHead,
TableHeading,
TableRow,
} from '@vega-ui/react';
<Table fullWidth>
<TableHead>
<TableRow><TableHeading>Plan</TableHeading><TableHeading>Per month</TableHeading></TableRow>- Browser support
- Widely available
- Chrome 111
- Edge 111
- Firefox 113
- Safari 15.4
- Safari on iOS 15.4
- Bundle size
- Oklab and OkLCh — comes from the design tokens
- :where()
Summary
Table is a compound component for semantic tabular data where native table structure matters more than grid-like keyboard interaction.
Imports
import {
Table,
TableHead,
TableBody,
TableRow,
TableHeading,
TableData,
TableFoot,
type TableProps,
} from '@vega-ui/react';Exported Types
TablePropsTableHeadPropsTableBodyPropsTableRowPropsTableHeadingPropsTableDataPropsTableFootProps
Minimal Composition
import {
Table,
TableBody,
TableData,
TableHead,
TableHeading,
TableRow,
} from '@vega-ui/react';
export default function MinimalComposition() {
return (
<Table>Required Parts
Table: root semantic table and alignment ownerTableRow: row structure for sectionsTableHeadingorTableData: cell-level content
Optional Parts
TableHead: header section for column labelsTableBody: body section for data rowsTableFoot: footer section for summaries or totals
Composition Order
TableTableHeadTableBodyTableFootTableRowTableHeadingTableData
Variants
- Data alignment through
dataAlign fullWidthandfullHeightlayout variants- Edge-padded versus flush edge treatment through
edgePadded
Edge Cases
Tableis a poor fit when the UI needs cell-level keyboard navigation or complex selection models.- Small screens need explicit overflow or stacked-layout treatment in the consumer.
- Alignment choices should stay consistent across heading and data cells.
Common Mistakes
- Reaching for
DataGridwhen semantic table markup is enough. - Dropping semantic sections like
TableHeadjust to simplify styling. - Ignoring overflow behavior on smaller screens.
Examples
Basic
Basic: plan comparison with row headers
A caption names the table, column headings use scope='col' and the first cell of every row is a TableHeading with scope='row', so a screen reader announces “Single sign-on, Team” for each value. Check and dash icons are hidden and paired with visually hidden “Included” / “Not included” text.
import { Check, Minus } from '@vega-ui/icons';
import {
Icon,
Table,
TableBody,
TableData,
TableHead,
TableHeading,
TableRow,
VisuallyHidden,
} from '@vega-ui/react';
Basic: line items with computed totals
Subtotal, tax and total are computed from the line items and placed in TableFoot. The labels are row headings spanning two columns, so each total stays tied to its label.
import {
Table,
TableBody,
TableData,
TableFoot,
TableHead,
TableHeading,
TableRow,
} from '@vega-ui/react';
const lines = [
{ item: 'Team plan · 12 seats', quantity: 12, unitPrice: 29 },Controlled/Stateful
Controlled/Stateful: sortable columns
The sort column and direction live in state; each sortable heading contains a real button and exposes the current order with aria-sort. Amounts sort numerically and dates by their ISO value, while the cells show formatted text.
import { ArrowDown, ArrowUp, ArrowUpDown } from '@vega-ui/icons';
import {
Button,
Icon,
Table,
TableBody,
TableData,
TableHead,
TableHeading,
TableRow,
} from '@vega-ui/react';
import { useState } from 'react';Form/Integration
Form/Integration: selecting rows for a bulk action
Every row has a named checkbox (name='member', value = row id), and the header checkbox selects all rows or shows the indeterminate state for a partial selection. On submit the handler collects the selected ids with FormData.getAll.
import {
Button,
Checkbox,
CheckboxCheckedIcon,
CheckboxIndeterminateIcon,
CheckboxIndicator,
CheckboxInput,
HelperText,
Table,
TableBody,
TableData,
TableHead,Layout/Overlay
Layout/Overlay: scrollable table inside a dialog
A long list of charges inside a dialog scrolls in its own focusable region instead of stretching the dialog. The header cells are sticky and get the dialog surface color, so rows do not show through them.
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogTrigger,
Heading,
Icon,
IconButton,
Table,
TableBody,
TableData,Error
Error: rows failed to load
Loading, error and data states share the same table: the loading and error rows span every column via colSpan={columns.length}. The error is announced with role='alert' and Retry loads the rows on the second attempt.
import {
Button,
HelperText,
Spinner,
Table,
TableBody,
TableData,
TableHead,
TableHeading,
TableRow,
} from '@vega-ui/react';
import { useEffect, useState } from 'react';Disabled
Disabled: archived rows with locked actions
Archived rows use a secondary text color and an “Archived” badge, so the state is not conveyed by color alone, and their row actions are disabled. Each action button has an accessible name that includes its project.
import {
Badge,
Button,
Table,
TableBody,
TableData,
TableHead,
TableHeading,
TableRow,
} from '@vega-ui/react';
const projects = [Edge
Edge: empty result after filtering
When the search matches nothing, the body renders one row spanning all columns with an explanation and a way out. The headers stay in place, so the table does not collapse.
import {
Button,
Label,
Table,
TableBody,
TableData,
TableHead,
TableHeading,
TableRow,
Text,
TextField,
TextFieldInput,Edge: 200 rows and 8 columns in a scroll container
The table keeps its natural width and scrolls in both directions inside a role='region' with tabIndex={0}, so keyboard users can scroll it too. The header row sticks to the top while scrolling.
import {
Table,
TableBody,
TableData,
TableHead,
TableHeading,
TableRow,
Text,
} from '@vega-ui/react';
import { CSSProperties, useId } from 'react';
const columns = ['Order', 'Customer', 'City', 'Items', 'Subtotal', 'Shipping', 'Status', 'Placed'];Edge: long unbreakable text
With table-layout: fixed and column widths in a colgroup, a long file name wraps anywhere (overflow-wrap: anywhere), while an email is truncated with an ellipsis and keeps the full value in title. Short columns never get squeezed.
import {
Table,
TableBody,
TableData,
TableHead,
TableHeading,
TableRow,
} from '@vega-ui/react';
const files = [
{ name: 'q3-board-presentation-final-v7-reviewed-by-legal-and-finance-DO-NOT-SHARE.pdf', owner: 'maya.chen@northwind-traders-international.example.com', size: '18.4 MB' },
{ name: 'logo.svg', owner: 'li@acme.io', size: '4 KB' },Edge: flush edges inside a card
By default edgePadded is false: the first and last cells lose their outer padding so the text lines up with the card heading. Toggle the prop to compare it with padded edges.
import {
Button,
Card,
Heading,
Table,
TableBody,
TableData,
TableRow,
} from '@vega-ui/react';
import { useState } from 'react';
const details = [Anatomy
Overview
Table owns shared alignment and edge-padding context for a semantic table structure. It is a compound component because root and section parts cooperate through one data-display contract.
Required Parts
Table
Required. Owns alignment, edge padding, and full-width or full-height layout flags.
TableRow
Required. Defines row structure inside header, body, or footer sections.
TableHeading or TableData
Required. Provide the actual semantic cells for a row.
Optional Parts
TableHead
Recommended for column labels and accessible column structure.
TableBody
Recommended for data rows.
TableFoot
Optional footer for totals, summaries, or aggregated values.
Composition Order
TableTableHeadTableBodyTableFootTableRowTableHeadingTableData
Valid Composition Patterns
import {
Table,
TableBody,
TableData,
TableFoot,
TableHead,
TableHeading,
TableRow,
} from '@vega-ui/react';
const lines = [
{ item: 'Team plan · 12 seats', quantity: 12, unitPrice: 29 },Invalid Composition Patterns
Visual table layout without semantic table parts
This weakens native table meaning and often hurts accessibility.
Mixing heading and data roles arbitrarily
Column labels should stay in TableHeading rather than becoming styled data cells.
Treating Table like an interactive data grid
If the feature needs cell navigation, selection models, or richer grid behavior, choose DataGrid instead.