Column Visibility
Compose DataTable column visibility with a dedicated hook and menu components.
Overview
Column visibility is composed from two pieces:
useDataTableColumnVisibilityowns the visible column ids, optional URL sync, and the derived TanStackcolumnVisibilitymapMenucomponents render the trigger and checkbox items
This keeps DataTable focused on rendering while the consumer stays free to choose the surrounding UI.
import {
DataTable,
type DataTableColumnVisibilityControls,
useDataTableColumnVisibility,
} from "@tilt-legal/cubitt-components/data-table";
import { Button } from "@tilt-legal/cubitt-components/button";
import {
Menu,
MenuCheckboxItem,
MenuContent,
MenuGroup,
MenuItem,
MenuSeparator,
MenuTrigger,
} from "@tilt-legal/cubitt-components/menu";
function ColumnVisibilityMenu({
columnView,
}: {
columnView: DataTableColumnVisibilityControls;
}) {
return (
<Menu>
<MenuTrigger render={<Button variant="secondary" />}>
Columns
</MenuTrigger>
<MenuContent align="end">
<MenuGroup>
<MenuItem disabled={columnView.allVisible} onClick={columnView.showAll}>
Show all
</MenuItem>
<MenuItem
disabled={!columnView.someVisible}
onClick={columnView.hideAll}
>
Hide all
</MenuItem>
</MenuGroup>
<MenuSeparator />
{columnView.items.map((item) => (
<MenuCheckboxItem
checked={item.visible}
key={item.id}
onCheckedChange={(checked) => {
columnView.setColumnVisible(item.id, Boolean(checked));
}}
onSelect={(event) => {
event.preventDefault();
}}
>
{item.label}
</MenuCheckboxItem>
))}
</MenuContent>
</Menu>
);
}
function TableWithColumnVisibility({ data, columns }: Props) {
const columnView = useDataTableColumnVisibility({
columns,
paramName: "columns",
});
return (
<>
<ColumnVisibilityMenu columnView={columnView} />
<DataTable
columns={columns}
columnVisibility={columnView.columnVisibility}
data={data}
onColumnVisibilityChange={columnView.onColumnVisibilityChange}
/>
</>
);
}Locked Columns
Columns marked with enableHiding: false stay visible and are omitted from the hook's menu items.
const columns: ColumnDef<Employee>[] = [
{
id: "name",
header: "Name",
accessorKey: "name",
enableHiding: false,
},
{
id: "email",
header: "Email",
accessorKey: "email",
},
];Defaults and URL State
Pass defaultValue as the list of hideable column ids that should start visible.
const columnView = useDataTableColumnVisibility({
columns,
defaultValue: ["role", "department", "status"],
paramName: "columns",
});The hook stores visible hideable column ids and derives the columnVisibility object that DataTable expects.
Labels
By default the hook uses:
headerwhen it is a string- otherwise the resolved column id
If your headers are custom JSX or functions, pass getColumnLabel:
const columnView = useDataTableColumnVisibility({
columns,
getColumnLabel: (column, columnId) =>
columnId === "assignee" ? "Assigned To" : columnId,
});API Reference
Hook Options
| Option | Type | Default | Description |
|---|---|---|---|
columns | ColumnDef<TData>[] | Required | DataTable column definitions |
value | string[] | - | Controlled list of visible hideable column ids |
defaultValue | string[] | All hideable column ids | Initial visible hideable column ids |
onValueChange | (value: string[]) => void | - | Called when the visible id list changes |
getColumnLabel | (column, columnId) => string | - | Override the label for a column |
paramName | string | - | URL parameter used to persist the visible id list |
paramClearOnDefault | boolean | true | Remove the query param when equal to the default visible ids |
paramThrottle | number | - | Throttle URL updates in milliseconds |
paramDebounce | number | - | Debounce URL updates in milliseconds |
onUrlValueChange | (value: string[] | null) => void | - | Called when the URL-backed visible id list changes |
Hook Return Value
| Field | Type | Description |
|---|---|---|
items | DataTableColumnVisibilityItem[] | Menu-safe hideable columns with derived ids, labels, and visible state |
resolvedItems | DataTableColumnVisibilityResolvedItem<TData>[] | Row-typed hideable columns including the original column definition |
value | string[] | Current visible hideable column ids |
columnVisibility | VisibilityState | Derived state to pass to DataTable |
onValueChange | (value: string[]) => void | Controlled setter for visible ids |
onColumnVisibilityChange | OnChangeFn<VisibilityState> | Adapter for DataTable updates |
isColumnVisible | (columnId: string) => boolean | Check whether a hideable column is visible |
setColumnVisible | (columnId: string, visible: boolean) => void | Set one hideable column visible or hidden |
toggleColumn | (columnId: string) => void | Toggle one hideable column |
showAll | () => void | Show every hideable column |
hideAll | () => void | Hide every hideable column |
allVisible | boolean | Whether every hideable column is visible |
someVisible | boolean | Whether at least one hideable column is visible |
items is intentionally non-generic so reusable menus and view switchers can accept column visibility state from multiple table row types. If you need the original ColumnDef<TData>, use resolvedItems.