

## Overview [#overview]

Column visibility is composed from two pieces:

* `useDataTableColumnVisibility` owns the visible column ids, optional URL sync, and the derived TanStack `columnVisibility` map
* `Menu` primitives render the trigger and checkbox items

This keeps `DataTable` focused on rendering while the consumer stays free to choose the surrounding UI.

<Tabs className="bg-transparent border-none rounded-none" items="['Preview', 'Code']">
  <Tab value="Preview" className="p-0">
    <Preview name="ColumnVisibilityExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      DataTable,
      type DataTableColumnVisibilityControls,
      useDataTableColumnVisibility,
    } from "@tilt-legal/cubitt-components/composites";
    import {
      Button,
      Menu,
      MenuCheckboxItem,
      MenuContent,
      MenuGroup,
      MenuItem,
      MenuSeparator,
      MenuTrigger,
    } from "@tilt-legal/cubitt-components/primitives";

    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}
          />
        </>
      );
    }
    ```
  </Tab>
</Tabs>

## Locked Columns [#locked-columns]

Columns marked with `enableHiding: false` stay visible and are omitted from the hook's menu items.

```tsx
const columns: ColumnDef<Employee>[] = [
  {
    id: "name",
    header: "Name",
    accessorKey: "name",
    enableHiding: false,
  },
  {
    id: "email",
    header: "Email",
    accessorKey: "email",
  },
];
```

## Defaults and URL State [#defaults-and-url-state]

Pass `defaultValue` as the list of hideable column ids that should start visible.

```tsx
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 [#labels]

By default the hook uses:

* `header` when it is a string
* otherwise the resolved column id

If your headers are custom JSX or functions, pass `getColumnLabel`:

```tsx
const columnView = useDataTableColumnVisibility({
  columns,
  getColumnLabel: (column, columnId) =>
    columnId === "assignee" ? "Assigned To" : columnId,
});
```

## API Reference [#api-reference]

### Hook Options [#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 [#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`.
