Layout & persistence
Let users hide, reorder, pin, resize and color columns, and keep their layout across visits.
The layout is how a user arranges the columns: which are shown, in what order, pinned where, how wide, in what color. Each user keeps their own, saved in their browser.
It's separate from the URL state on purpose: a shared link shows the same rows to everyone, but each person keeps their own columns.
Save the layout
Give the table a storageKey. The layout is then saved to localStorage on every change, and restored on the next visit:
const table = useDataTable({
data,
columns,
getRowId: (order) => order.id,
storageKey: "orders-table",
})Use one key per table. Without a storageKey, the layout resets when the table unmounts. Tables using the same key stay in sync, in this tab and in others.
Prop
Type
What users can change
| To… | Users… | See |
|---|---|---|
| Reorder | drag a header, or the grip in the Columns menu | Moving columns |
| Resize | drag the edge of a header | Resizing |
| Fit a column to its content | double-click the edge of a header, or pick Fit to content in the column's menu | Fit to content |
| Fit every column | pick Fit all columns in the Columns menu | Fit to content |
| Pin to the start or end | pick Pin to start or Pin to end in the column's menu | Pinning |
| Show or hide a column | pick Hide column in the column's menu, or tick its checkbox in the Columns menu | Hiding columns |
| Color a column | pick Color in the column's menu | Column colors |
| Go back to your defaults | pick Reset layout in the Columns menu | Reset from code |
Each page covers how users do it, the options to limit or turn it off, and how to do it from code. For all the switches in one place, see What users can do.
The column menu
Every header has a ⋯ button, unless the column has nothing to sort, pin, fit or hide, like the selection column. It shows when you hover the header or tab to it, and stays visible on touch screens. Right-clicking a header opens the same menu. It holds everything you can do to that one column:
| Item | Does |
|---|---|
| Ascending, Descending, Clear sort | sorts by this column only (Shift+click the header to sort by several) |
| Pin to start, Pin to end, Unpin | see Pinning |
| Fit to content | see Fit to content |
| Color | see Column colors |
| Hide column | see Hiding columns |
Items a column doesn't allow are left out (sorting, hiding) or disabled (pinning, fitting). The same pin, fit and color items are in each row's ⋯ menu in the Columns menu, which also lists hidden columns.
The header itself shows a grab cursor when it can be dragged, and a thin line on its end edge when you hover where it resizes.
Your column definitions decide what's allowed, and what the layout starts from: see Columns. A column that can't be hidden, pinned or moved, like the selection column, isn't listed in the Columns menu.
When your columns change
You can ship new columns without breaking anyone's saved layout. A saved layout is always matched against your current columns:
- New columns start from their defaults, at the end of the order.
- Removed columns are dropped from the saved layout.
- Locked options (
enableHiding: false,enablePinning: false) follow your definitions, whatever was saved. - Anything unreadable (hand-edited, a wrong shape) falls back to the defaults.
If old layouts would be wrong rather than just outdated, for example after renaming column ids, bump layoutVersion. Layouts saved under another version are ignored.
Reset from code
Reset layout calls table.options.meta.resetLayout(). It goes back to your column definitions and deletes the saved layout. Call it from your own button the same way:
<Button variant="ghost" onClick={() => table.options.meta?.resetLayout()}>
Reset layout
</Button>Use resetLayout, not TanStack's reset methods
table.resetColumnOrder(), resetColumnVisibility() and the like go back to the columns of the first render, and they don't delete the saved layout. meta.resetLayout() always uses your current definitions.
Good to know
- Server rendering. The server has no
localStorage, so the first render uses your column definitions, and the saved layout applies right after. If that brief switch bothers you, render the table on the client only. - Storage can fail. If the browser refuses to store (full, private mode), changes still apply until the page is closed.
- Right-to-left pages. Pass
dir: "rtl"touseDataTable. Resizing follows the pointer, and pinned columns and their shadows flip with the page. See Right-to-left. - Your own table setup.
useTableLayout({ columns, storageKey, version })is the hookuseDataTableuses. It returnsstate,handlersandreset()to spread into your own TanStack table. AddcolumnColorFeatureto its features for colors.