Skip to content

useTableColumnWidths

useTableColumnWidths manages user-resized column widths and optionally persists them to localStorage. It returns a register function that produces the width, resizable, and onResizeEnd props expected by VirtualTable.Column.

import { useTableColumnWidths } from '@requence/table'
function useTableColumnWidths(options?: UseColumnWidthsOptions): {
register: (key: string, options?: RegisterOptions) => {
width: number | string | undefined
resizable: true
onResizeEnd: (width: number, startWidth: number, frValue: number) => void
}
reset: () => void
}
OptionTypeDefaultDescription
persiststringWhen provided, column widths are persisted to localStorage under the key columnWidths:{persist}. Omit to keep widths in memory only (lost on unmount).
PropertyTypeDescription
register(key: string, options?: RegisterOptions) => ColumnPropsRegisters a column and returns props to spread onto VirtualTable.Column. See register().
reset() => voidResets all column widths to their defaults. If persist is set, the localStorage entry is also removed.

Call register for each column, passing a unique key and optional configuration. The returned object is designed to be spread directly onto a Column component.

const columnWidths = useTableColumnWidths({ persist: 'users-table' })
// In JSX:
<VirtualTable.Column {...columnWidths.register('name', { defaultValue: '2fr' })}>
Name
</VirtualTable.Column>
ParameterTypeDescription
keystringRequired. Unique identifier for this column. Used as the key in the persisted width map.
optionsRegisterOptionsOptional configuration for this column.
OptionTypeDefaultDescription
defaultValuenumber | stringDefault width before any resize has occurred. A number is pixels, a string is a flexible unit (e.g. '1fr', '2fr'). If omitted, the column uses VirtualTable.Column’s own default ('1fr').
relativebooleanfalseWhen true, resized widths are stored as fr values (e.g. '1.5fr') instead of pixel values. This keeps column proportions consistent across different viewport sizes.
PropertyTypeDescription
widthnumber | string | undefinedThe current width for this column — either a previously saved value or the defaultValue.
resizabletrueAlways true. Enables the resize handle on the column.
onResizeEnd(width: number, startWidth: number, frValue: number) => voidCallback that stores the new width. When relative is true, stores frValue as a string like '1.50fr'. Otherwise stores the pixel width as a number.

The relative option on register controls how resized widths are stored:

  • relative: false (default) — The final pixel width from the drag is stored as a number. The column will have a fixed pixel width after resize.

  • relative: true — The frValue from onResizeEnd is stored as a string like '1.50fr'. The column remains flexible and scales with the container. The frValue is rounded to 2 decimal places.

When persist is provided, widths are stored as a JSON object under:

columnWidths:{persist}

For example, with persist: 'users-table', the localStorage key is columnWidths:users-table and the value looks like:

{
"name": "2.00fr",
"email": 250,
"actions": "0.50fr"
}
import { VirtualTable, useTableCache, useTableColumnWidths } from '@requence/table'
function UsersTable() {
const cache = useTableCache('users', {
pageSize: 50,
getItemId: (user) => user.id,
compare: (a, b) => a.name.localeCompare(b.name),
fetchItems: (offset, limit) => api.getUsers({ offset, limit }),
})
const columnWidths = useTableColumnWidths({ persist: 'users-table' })
return (
<VirtualTable
totalCount={cache.totalCount}
rowHeight={40}
onRangeChange={cache.handleRangeChange}
>
<VirtualTable.Header>
<VirtualTable.Column
{...columnWidths.register('name', { defaultValue: '2fr', relative: true })}
>
Name
</VirtualTable.Column>
<VirtualTable.Column
{...columnWidths.register('email', { defaultValue: '1fr', relative: true })}
>
Email
</VirtualTable.Column>
<VirtualTable.Column width={100} resizable={false}>
Actions
</VirtualTable.Column>
</VirtualTable.Header>
<VirtualTable.Body>
{(index) => {
const user = cache.getItem(index)
if (!user) return null
return (
<VirtualTable.Row>
<VirtualTable.Cell>{user.name}</VirtualTable.Cell>
<VirtualTable.Cell>{user.email}</VirtualTable.Cell>
<VirtualTable.Cell showOnHover>
<button>Edit</button>
</VirtualTable.Cell>
</VirtualTable.Row>
)
}}
</VirtualTable.Body>
<VirtualTable.SkeletonRow className="animate-pulse">
<VirtualTable.Cell colSpan={3}>Loading…</VirtualTable.Cell>
</VirtualTable.SkeletonRow>
<VirtualTable.Empty className="py-20 text-gray-400">
No users found.
</VirtualTable.Empty>
<VirtualTable.Footer>
{({ start, end }) => (
<div className="flex justify-between px-4 py-2 text-sm text-gray-500">
<span>Showing {start + 1}{end} of {cache.totalCount}</span>
<button onClick={columnWidths.reset}>Reset columns</button>
</div>
)}
</VirtualTable.Footer>
</VirtualTable>
)
}