Column resizing
Every column has a width you set, and a handle the user drags to change it. Double-click the handle and the column fits its content.
| Ananya Patel | ananya.patel@team.dev | Active | Platform | $1,276.00 | — |
| Arjun Iyer | arjun.iyer@example.com | Invited | Support | $464.00 | — |
| Mia Okafor | mia.okafor@team.dev | Active | Research | $0.00 | — |
| Kwame Iyer | kwame.iyer@example.com | Active | Finance | $36.00 | — |
| Vihaan Das | vihaan.das@example.com | Active | Design | $36.00 | — |
| Haruto Khan | haruto.khan@team.dev | Invited | Growth | $0.00 | — |
| Aarav Silva | aarav.silva@mail.test | Churned | Support | $0.00 | Wants SSO. |
| Kwame Nguyen | kwame.nguyen@example.com | Paused | Sales | $24.00 | — |
| Ananya Okafor | ananya.okafor@mail.test | Paused | Growth | $36.00 | — |
| Vihaan Iyer | vihaan.iyer@mail.test | Active | Design | $0.00 | — |
0 of 50 row(s) selected.
Page 1 of 5
import { DataGrid, createDataGridColumns } from "@/components/ui/data-grid";
import { makePeople, STATUS_OPTIONS, type Person } from "@/lib/data-grid-demo-data";
const col = createDataGridColumns<Person>();
const columns = col.columns([ // Starts at 200 px and can be dragged between 120 and 400. col.text("name", { header: "Name", size: 200, minSize: 120, maxSize: 400 }), col.text("email", { header: "Email", size: 260 }), // No handle, no "Fit to content": this column keeps its width. col.enum("status", { header: "Status", options: STATUS_OPTIONS, size: 120, enableResizing: false, }), col.text("team", { header: "Team", size: 140 }), col.currency("mrr", { header: "MRR", currency: "USD", size: 120 }), // Too narrow on purpose: double-click its edge to fit. col.text("notes", { header: "Notes", size: 120 }),]);
const data = makePeople(50, 19);
/** Drag a header's trailing edge to resize; double-click the edge to fit the column to its content. */export default function DataGridResizing() { return <DataGrid name="resizing" data={data} columns={columns} getRowId={(row) => row.id} />;}Use it
Section titled “Use it”Move the pointer to the trailing edge of a header: a thin handle appears. Drag it, with a mouse or a finger. Double-click it, or choose Fit to content from the column menu, to size the column to its widest visible cell.
Set the starting width and the bounds on the column:
const columns = col.columns([ col.text("name", { header: "Name", size: 200, minSize: 120, maxSize: 400 }), col.enum("status", { header: "Status", options: STATUS_OPTIONS, size: 120, enableResizing: false }), col.text("notes", { header: "Notes", size: 320 }),])From code, the TanStack sizing API applies:
grid.table.setColumnSizing((old) => ({ ...old, name: 240 }))grid.table.getColumn("name")?.resetSize()grid.table.resetColumnSizing()How it behaves
Section titled “How it behaves”- Fixed layout. The table uses
table-layout: fixedand is exactly as wide as the sum of its visible columns (table.getTotalSize()). A column is its size, never squeezed to make room for the others, and columns never stretch to fill a wide viewport: if the sizes add up to less than the grid’s width, the table ends early. Size your columns for the data, and give a long text column the room it needs. - Live.
columnResizeModeis"onChange", so the column follows the pointer while you drag, and the columns after it move with it. Pinned columns update their sticky offsets as you go. - Fit to content (
autosizeColumn) measures the rendered content of every cell in the column, takes the widest, adds 24 px for the cell padding and the header’s icons, and clamps the result to the column’sminSizeandmaxSize. It only sees what is in the DOM: the current page, or the rows in view when virtualized. A wider value on another page is not measured. - Defaults. Every column starts at 160 px, between 48 and 2000, unless it says otherwise. The utility columns are fixed: the checkbox and the expander at 40 px, the row number and the actions menu at 56 px (minimum 48).
- Clicks on the handle don’t sort. The handle stops the click so a resize never toggles the sort of the header it sits on.
- Right to left. Drag offsets are computed for a left-to-right layout. Under
dir="rtl", passtable={{ columnResizeDirection: "rtl" }}so the handle moves the right way. - Persistence. Widths are part of the view the grid remembers
with
persist. The write is debounced 300 ms, so a drag does not write to storage on every pixel. Reset view restores the sizes you defined.
Options
Section titled “Options”Column options:
| Option | Type | Default | What it does |
|---|---|---|---|
size |
number |
160 |
Starting width in pixels, until the user or columnSizing state overrides it. |
minSize |
number |
48 |
Lower bound for dragging and for Fit to content. |
maxSize |
number |
2000 |
Upper bound for dragging and for Fit to content. |
enableResizing |
boolean |
true |
false hides the handle and the Fit to content item; the column keeps its size. |
Grid options, through the table escape hatch:
| Option | Type | Default | What it does |
|---|---|---|---|
table.enableColumnResizing |
boolean |
true |
Turns resizing off for every column. |
table.columnResizeMode |
"onChange" | "onEnd" |
"onChange" |
When sizes commit. The grid draws no preview line, so with "onEnd" nothing moves until release. |
table.columnResizeDirection |
"ltr" | "rtl" |
"ltr" |
Which way a drag grows the column. |
initialState.columnSizing |
Record<string, number> |
{} |
Widths by column id, over the columns’ size. |
Helpers exported from the grid:
| Export | What it does |
|---|---|
autosizeColumn(rootElement, column) |
What double-click and Fit to content call. rootElement is the grid root (useDataGridUi().rootRef.current). |
See also
Section titled “See also”- Column pinning: sticky offsets follow the widths.
- Columns: every other column option.
- Saved views and persistence.