# Header Menu

Use the `tool` property in column headers to add custom menus for filtering, column visibility, and other actions.

```tsx
import { createModel } from "cx/data";
import { Controller, Repeater, bind } from "cx/ui";
import { Checkbox, Grid, Icon, Menu, Submenu, TextField } from "cx/widgets";

interface Person {
  id: number;
  fullName: string;
  continent: string;
  browser: string;
  visits: number;
}

interface FilterOption {
  name: string;
  active: boolean;
}

interface PageModel {
  records: Person[];
  filtered: Person[];
  filter: {
    name: string;
    continents: FilterOption[];
    browsers: FilterOption[];
  };
  visibility: {
    continent: boolean;
    browser: boolean;
    visits: boolean;
  };
  $record: Person;
  $option: FilterOption;
}

const m = createModel<PageModel>();

// @util
function unique(data: Person[], field: keyof Person): FilterOption[] {
  let values: Record<string, boolean> = {};
  data.forEach((item) => {
    values[String(item[field])] = true;
  });
  return Object.keys(values).map((name) => ({ name, active: true }));
}

function filterRecords(
  filter: PageModel["filter"],
  records: Person[],
): Person[] {
  return records.filter((record) => {
    let continent = filter.continents.find(
      (c) => c.name === record.continent,
    )?.active;
    let browser = filter.browsers.find(
      (b) => b.name === record.browser,
    )?.active;
    let name = filter.name
      ? record.fullName.toLowerCase().includes(filter.name.toLowerCase())
      : true;
    return continent && browser && name;
  });
}
// @util-end

class PageController extends Controller {
  onInit() {
    this.store.init(m.visibility, {
      continent: true,
      browser: true,
      visits: true,
    });

    let records: Person[] = [
      {
        id: 1,
        fullName: "Alice Johnson",
        continent: "Europe",
        browser: "Chrome",
        visits: 45,
      },
      {
        id: 2,
        fullName: "Bob Smith",
        continent: "Asia",
        browser: "Firefox",
        visits: 23,
      },
      {
        id: 3,
        fullName: "Carol White",
        continent: "North America",
        browser: "Safari",
        visits: 67,
      },
      {
        id: 4,
        fullName: "David Brown",
        continent: "Europe",
        browser: "Chrome",
        visits: 12,
      },
      {
        id: 5,
        fullName: "Eva Green",
        continent: "Asia",
        browser: "Edge",
        visits: 89,
      },
    ];

    this.store.set(m.records, records);
    this.store.set(m.filter.continents, unique(records, "continent"));
    this.store.set(m.filter.browsers, unique(records, "browser"));
    this.store.set(m.filter.name, "");

    this.addTrigger(
      "filter",
      [m.filter],
      (filter) => {
        this.store.set(m.filtered, filterRecords(filter, records));
      },
      true,
    );
  }
}

const visibleColumnsMenu = (
  <Submenu arrow>
    Columns
    <Menu putInto="dropdown">
      <Checkbox value={m.visibility.continent} mod="menu">
        Continent
      </Checkbox>
      <Checkbox value={m.visibility.browser} mod="menu">
        Browser
      </Checkbox>
      <Checkbox value={m.visibility.visits} mod="menu">
        Visits
      </Checkbox>
    </Menu>
  </Submenu>
);

const columnMenu = (filter: any) => (
  <div class="w-full h-full flex items-center">
    <Menu horizontal itemPadding="none">
      <Submenu placement="down-left" class="p-1">
        <Icon name="menu" />
        <Menu putInto="dropdown">
          {filter}
          <hr />
          {visibleColumnsMenu}
        </Menu>
      </Submenu>
    </Menu>
  </div>
);

const checkboxFilterMenu = (valuesPath: string) =>
  columnMenu(
    <Repeater records={bind(valuesPath)} recordAlias={m.$option}>
      <Checkbox mod="menu" value={m.$option.active} text={m.$option.name} />
    </Repeater>,
  );

export default (
  <Grid
    controller={PageController}
    records={m.filtered}
    style="height: 300px;"
    scrollable
    border
    emptyText="No records found matching the given criteria."
    columns={[
      {
        header: {
          text: "Name",
          tool: columnMenu(
            <TextField mod="menu" placeholder="Filter" value={m.filter.name} />,
          ),
        },
        field: "fullName",
        sortable: true,
      },
      {
        header: {
          text: "Continent",
          tool: checkboxFilterMenu("filter.continents"),
        },
        field: "continent",
        visible: m.visibility.continent,
        sortable: true,
      },
      {
        header: {
          text: "Browser",
          tool: checkboxFilterMenu("filter.browsers"),
        },
        field: "browser",
        visible: m.visibility.browser,
        sortable: true,
      },
      {
        header: "Visits",
        field: "visits",
        align: "right",
        visible: m.visibility.visits,
        sortable: true,
      },
    ]}
  />
);

```

Click the menu icon in any header to open the dropdown menu.

## Adding a Header Tool

The `tool` property accepts any widget content that will be rendered in the header cell:

```tsx
{
  header: {
    text: "Name",
    tool: (
      <Menu horizontal>
        <Submenu placement="down-left">
          <Icon name="menu" />
          <Menu putInto="dropdown">
            <TextField placeholder="Filter" value={m.filter.name} />
          </Menu>
        </Submenu>
      </Menu>
    ),
  },
  field: "fullName",
}
```

## Column Visibility

Combine header menus with the `visible` property to let users show/hide columns:

```tsx
{
  header: {
    text: "Browser",
    tool: columnMenu,
  },
  field: "browser",
  visible: m.visibility.browser,
}
```

The menu can contain checkboxes bound to visibility state:

```tsx
<Checkbox value={m.visibility.browser} mod="menu">
  Browser
</Checkbox>
```

## Tips

- Use `mod="menu"` on form controls inside menus for proper styling.
- Use `Submenu` with `placement="down-left"` or `placement="down-right"` to control dropdown direction.
- Combine filtering with `addTrigger` to automatically update displayed records when filters change.

See also: [Complex Headers](/docs/tables/complex-headers), [Grid](/docs/tables/grid)