GridTable

CSS Grid-based superset of Table for column grouping and content-based sizing.

When to use#

With flexbox-based Table, each row calculates its layout independently. GridTable uses CSS Grid for a single unified column track system across all rows.

Use GridTable when you need:

  • Grouped headers spanning multiple columns
  • Columns sized based on their widest content
  • Precise, declarative column layout

Import#

import {
GridTable,
createGridTemplate,
getGridTemplateColumn
} from '@volue/wave-react';

GridTable is a compound component that consists of multiple parts which can be composed together to achieve the desired table interface:

  • GridTable.Root: The wrapper that contains all the parts of a grid table. Requires gridTemplateColumns prop.
  • GridTable.Head: The container that defines the head of the table's columns (equivalent of <thead> HTML element).
  • GridTable.Header: A convenience container wrapping content into GridTable.Head with single GridTable.Row inside.
  • GridTable.ColumnHeader: Renders the header cell of a column (equivalent of native <th> element). Supports colSpan for spanning multiple columns.
  • GridTable.ColumnHeaderContent: Wrapper for column's header text contents.
  • GridTable.ColumnHeaderTitle: Column's header title.
  • GridTable.ColumnHeaderSubtitle: Column's header subtitle.
  • GridTable.ColumnResizeHandle: The handle for resizing the column. Should be passed to GridTable.ColumnHeader via the resizeHandle prop.
  • GridTable.Body: The container grouping a set of GridTable.Rows that are main content of the table (equivalent of native <tbody> element).
  • GridTable.Row: Renders table row (equivalent of native <tr> element).
  • GridTable.Cell: Cell to be placed in the GridTable.Row that displays single piece of data in the column (equivalent of native <td> element). Wraps text content into GridTable.CellContent automatically.
  • GridTable.CellContent: Wrapper for cell's text contents.
  • GridTable.SelectionCell: Composes GridTable.Cell and GridTable.SelectionCellCheckbox, to render a cell with checkbox for implementing selection of rows.
  • GridTable.SelectionCellCheckbox: Customized Checkbox component to be placed in the table cell.
  • GridTable.Foot: The container grouping a set of GridTable.Rows which is placed at the bottom of the table (equivalent of native <tfoot> element).
  • GridTable.Footer: A convenience container wrapping content into GridTable.Foot with single GridTable.Row inside.
  • GridTable.EmptyState: The wrapper for providing additional context when the table data is either unavailable or nonexistent.

Examples#

These examples focus on GridTable-specific features. For general table patterns, see Table which has the same component structure. Migration is straightforward: replace Table namespace with GridTable and move sizing props from cells to gridTemplateColumns.

Basic#

The gridTemplateColumns prop is required. You can pass any CSS grid-template-columns value directly, or use the createGridTemplate utility for semantic column widths.

Column widths#

GridTable supports several column width modes through the createGridTemplate utility:

  • grow (default): Fills available space. Min width is the width of the widest cell.
  • growCollapse: Grows to fill space but can shrink below content width.
  • auto: The column is the width of its widest cell.
  • Fixed values: Will be exactly that width (e.g., 120, '200px', '10rem').

Each mode can be combined with minWidth and maxWidth constraints.

Column groups#

Use colSpan on GridTable.ColumnHeader to create grouped headers that span multiple columns. This is useful for organizing related columns under a common heading.

Content-based sizing#

Use auto width to size a column to the width of its widest cell.

Keep in mind that column width will change as content changes. For columns with highly variable content, prefer grow with constraints.

When using overflowStrategy="wrap", values with units can break across lines. Use non-breaking spaces (\u00A0) to keep them glued together. This is not needed with the default truncate strategy since content never wraps.

// ❌ Regular space — can break across lines
`${value} MW`
// ✅ Non-breaking space
`${value}\u00A0MW`;

For values without a visible space (e.g., a currency symbol and number like €100), use \u2060 (word joiner) to prevent breaks without adding space.

If you format values with Intl.NumberFormat, the spaces it inserts are already non-breaking — no extra work needed. This only applies when manually concatenating values with units.


Column layout utilities#

Wave provides utility functions to easily construct gridTemplateColumns values.

createGridTemplate#

Converts an array of column configurations to a CSS grid-template-columns value.

import { createGridTemplate } from '@volue/wave-react';
createGridTemplate([
{ width: 'grow', minWidth: 100 },
{ width: 120 },
{ width: 'auto' }
]);
// Returns: "minmax(100px, 1fr) 120px auto"

getGridTemplateColumn#

Converts a single column configuration to a CSS value. Useful when building gridTemplateColumns dynamically.

import { getGridTemplateColumn } from '@volue/wave-react';
// Semantic modes
getGridTemplateColumn({ width: 'grow' }); // "minmax(max-content, 1fr)"
getGridTemplateColumn({ width: 'growCollapse' }); // "minmax(0, 1fr)"
getGridTemplateColumn({ width: 'auto' }); // "auto"
// Fixed widths
getGridTemplateColumn({ width: 120 }); // "120px"
getGridTemplateColumn({ width: '200px' }); // "200px"
// With constraints
getGridTemplateColumn({ width: 'grow', minWidth: 100 }); // "minmax(100px, 1fr)"
getGridTemplateColumn({ width: 'grow', maxWidth: 300 }); // "minmax(auto, 300px)"
getGridTemplateColumn({ width: 'grow', minWidth: 100, maxWidth: 300 }); // "minmax(100px, 300px)"

Quick reference#

InputOutput
{ width: 'grow' }minmax(max-content, 1fr)
{ width: 'growCollapse' }minmax(0, 1fr)
{ width: 'auto' }auto
{ width: 'auto', maxWidth: 200 }minmax(auto, 200px)
{ width: 120 }120px
{ width: '200px' }200px
{ width: 'grow', minWidth: 100 }minmax(100px, 1fr)
{ width: 'grow', maxWidth: 300 }minmax(auto, 300px)
{ width: 'growCollapse', maxWidth: 200 }minmax(0, 200px)

TanStack Table integration#

GridTable works well with TanStack Table for features like sorting, filtering, and column resizing. The key is generating gridTemplateColumns from TanStack's column state.

GridTable shares the same patterns as Table. See Table's TanStack integration for more examples.

Extending column meta#

When using TypeScript, extend TanStack's ColumnMeta type to include semantic grid column width options:

import type { CSSProperties } from 'react';
import type { RowData } from '@tanstack/react-table';
import type { GridTableColumnWidth } from '@volue/wave-react';
declare module '@tanstack/react-table' {
interface ColumnMeta<TData extends RowData, TValue> {
gridWidth?: GridTableColumnWidth;
minWidth?: CSSProperties['minWidth'];
maxWidth?: CSSProperties['maxWidth'];
showDivider?: boolean;
}
}

Generating gridTemplateColumns#

Generate template columns from table.getVisibleLeafColumns(). You can extract a reusable hook (example below) or compute it directly in your component.

import { useMemo } from 'react';
import { useReactTable } from '@tanstack/react-table';
import { getGridTemplateColumn } from '@volue/wave-react';
type ResizeMode = 'fixed' | 'proportional';
function useGridTemplateColumns<TData>(
table: ReturnType<typeof useReactTable<TData>>,
resizeMode: ResizeMode = 'proportional'
) {
return useMemo(() => {
return table
.getVisibleLeafColumns()
.map(column => {
// Optional: handle resizable columns
if (column.getCanResize()) {
const size = column.getSize();
return resizeMode === 'fixed'
? `${size}px`
: `minmax(${size}px, ${size}fr)`;
}
return getGridTemplateColumn({
width: column.columnDef.meta?.gridWidth,
minWidth: column.columnDef.meta?.minWidth,
maxWidth: column.columnDef.meta?.maxWidth
});
})
.join(' ');
}, [
table,
// eslint-disable-next-line react-hooks/exhaustive-deps
table.getState().columnSizing,
// eslint-disable-next-line react-hooks/exhaustive-deps
table.getState().columnSizingInfo,
// eslint-disable-next-line react-hooks/exhaustive-deps
table.getState().columnVisibility,
resizeMode
]);
}

Example with sorting and column groups#

import {
flexRender,
getCoreRowModel,
getSortedRowModel,
useReactTable,
createColumnHelper
} from '@tanstack/react-table';

Rows virtualization#

Rendering large amounts of rows can be inefficient. With virtualization (or windowing) of data, only currently visible rows are rendered to DOM to deliver best performance and user experience.

When only a subset of rows are visible, it's a good practice to let all users know which rows are being displayed.

Use the aria-rowcount attribute on the GridTable.Root to let assistive technologies know the total number of rows available. GridTable.Row should include aria-rowindex attribute to indicate where each row is in relation to the total available rows.

import {
flexRender,
getCoreRowModel,
useReactTable,
createColumnHelper
} from '@tanstack/react-table';
import { useVirtualizer } from '@tanstack/react-virtual';
See more TanStack examples in Storybook

API Reference#

GridTable.Root#

Prop
Type
Default
css
StitchesCss
No default value
gridTemplateColumns*
string
No default value
alignY
enum
"center"
cellSize
enum
"medium"
overflowStrategy
enum
"truncate"
highlightOnHover
boolean
true
syncScroll
boolean
false
aria-label*
string
No default value
rowSeparation
enum
"dividers"
outlined
boolean
true
addHorizontalWhitespace
boolean
false
showCellSeparation
boolean
false
fitContent
boolean
false

GridTable.Head#

Container for header rows. Supports multiple GridTable.Row elements for grouped headers.

Prop
Type
Default
css
StitchesCss
No default value
stickyOffset
number
No default value

GridTable.Header#

Convenience wrapper that composes GridTable.Head with a single GridTable.Row.

Prop
Type
Default
css
StitchesCss
No default value
stickyOffset
number
No default value

GridTable.ColumnHeader#

Header cell for a column. Supports colSpan for grouped headers.

Prop
Type
Default
css
StitchesCss
No default value
align
enum
"left"
colSpan
number
No default value
stickyOffset
number
No default value
showDivider
boolean
No default value
onClick
function
No default value
sort
enum
No default value
sortIndicator
React.ReactElement
No default value
resizeHandle
React.ReactElement
No default value
isGroupHeader
boolean
false

GridTable.ColumnHeaderContent#

Prop
Type
Default
as
enum
div
css
StitchesCss
No default value
overflowStrategy
enum
No default value

GridTable.ColumnHeaderTitle#

Prop
Type
Default
as
enum
span
css
StitchesCss
No default value
overflowStrategy
enum
No default value

GridTable.ColumnHeaderSubtitle#

Prop
Type
Default
as
enum
span
css
StitchesCss
No default value
overflowStrategy
enum
No default value

GridTable.ColumnResizeHandle#

Prop
Type
Default
css
StitchesCss
No default value
isResizing
boolean
false

GridTable.SortIndicator#

Prop
Type
Default
css
StitchesCss
No default value

GridTable.Body#

Container for the main content rows of the table.

Prop
Type
Default
css
StitchesCss
No default value

GridTable.Row#

Prop
Type
Default
css
StitchesCss
No default value
status
enum
No default value
isHighlighted
boolean
false
tone
enum
No default value

GridTable.Cell#

Cell for displaying data. Wraps text content into GridTable.CellContent automatically.

Prop
Type
Default
css
StitchesCss
No default value
align
enum
"left"
stickyOffset
number
No default value
showDivider
boolean
No default value
role
enum
"gridcell"
tone
enum
No default value

GridTable.CellContent#

In addition to the props below, you can pass Text props.

Prop
Type
Default
as
enum
div
css
StitchesCss
No default value
overflowStrategy
enum
No default value

GridTable.SelectionCell#

Composes GridTable.Cell and GridTable.SelectionCellCheckbox for row selection.

In addition to the props below, you can pass all GridTable.SelectionCellCheckbox props.

Prop
Type
Default
css
StitchesCss
No default value
align
enum
"left"
stickyOffset
number
No default value
showDivider
boolean
No default value
role
enum
gridcell
aria-label*
string
No default value

GridTable.SelectionCellCheckbox#

In addition to the props below, you can pass all Checkbox props.

Prop
Type
Default
css
StitchesCss
No default value
aria-label*
string
No default value

GridTable.Foot#

Container for footer rows.

Prop
Type
Default
css
StitchesCss
No default value
stickyOffset
number
No default value

GridTable.Footer#

Convenience wrapper that composes GridTable.Foot with a single GridTable.Row.

Prop
Type
Default
css
StitchesCss
No default value
stickyOffset
number
No default value

GridTable.EmptyState#

Prop
Type
Default
css
StitchesCss
No default value