---
title: "Column Headers - Custom Components"
framework: react
version: "36.1.0"
---

# Column Headers - Custom Components

The grid provides a default Header Component with sorting, filtering and menu functionality. There are three approaches to customising Column Headers:

- [Custom Template](#custom-template) - Provide an HTML template to the default Header Component for simple layout changes while retaining built-in functionality.
- [Inner Header Component](#inner-header-component) - Replace just the header name display with a custom component while keeping sorting, filtering and menu functionality.
- [Custom Component](#custom-component) - Create a fully custom Header Component with complete control over rendering and behaviour.

## Custom Template

Provide an HTML template to the Provided Header Component for simple layout changes while retaining built-in sorting, filtering and menu functionality.

Set the template using `colDef.headerComponentParams`. Set on the `defaultColDef` grid option to set for all Columns.

```jsx
const defaultColDef = useMemo(() => { 
	return {
        width: 100,
        headerComponentParams: {
            template:
                `<div class="ag-cell-label-container" role="presentation">
                  <span data-ref="eMenu" class="ag-header-icon ag-header-cell-menu-button"></span>
                  <span data-ref="eFilterButton" class="ag-header-icon ag-header-cell-filter-button"></span>
                  <div data-ref="eLabel" class="ag-header-cell-label" role="presentation">
                    <span data-ref="eSortOrder" class="ag-header-icon ag-sort-order"></span>
                    <span data-ref="eSortAsc" class="ag-header-icon ag-sort-ascending-icon"></span>
                    <span data-ref="eSortDesc" class="ag-header-icon ag-sort-descending-icon"></span>
                    <span data-ref="eSortAbsoluteAsc" class="ag-header-icon ag-sort-absolute-ascending-icon"></span>
                    <span data-ref="eSortAbsoluteDesc" class="ag-header-icon ag-sort-absolute-descending-icon"></span>
                    <span data-ref="eSortMixed" class="ag-header-icon ag-sort-mixed-icon"></span>
                    <span data-ref="eSortNone" class="ag-header-icon ag-sort-none-icon"></span>
                    ** <span data-ref="eText" class="ag-header-cell-text" role="columnheader"></span>
                    <span data-ref="eFilter" class="ag-header-icon ag-filter-icon"></span>
                  </div>
                </div>`
        }
    };
}, []);

<AgGridReact defaultColDef={defaultColDef} />
```

The example below demonstrates a custom template. Note that specifying your own templates is compatible with other configurations:

- `suppressHeaderFilterButton` is specified in: **Athlete**, **Country**, **Date** and **Bronze** columns
- `sortable=false` is specified in: **Age**, **Year**, **Date**, **Sport**, **Silver** and **Total** columns
- **Gold** is the only column that doesn't have `sortable=false` or `suppressHeaderFilterButton`

#### Header template

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  NumberFilterModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import { IOlympicData } from "./interfaces";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<IOlympicData[]>();
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    {
      headerName: "Athlete Name",
      field: "athlete",
      suppressHeaderFilterButton: true,
    },
    { field: "age", sortable: false },
    { field: "country", suppressHeaderFilterButton: true },
    { field: "year", sortable: false },
    { field: "date", suppressHeaderFilterButton: true, sortable: false },
    { field: "sport", sortable: false },
    { field: "gold" },
    { field: "silver", sortable: false },
    { field: "bronze", suppressHeaderFilterButton: true },
    { field: "total", sortable: false },
    { field: "prevYearTotalDiff", sort: { type: "absolute", direction: null } },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      filter: true,
      width: 150,
      headerComponentParams: {
        template: `<div class="ag-cell-label-container" role="presentation">
                    <span data-ref="eMenu" class="ag-header-icon ag-header-cell-menu-button"></span>
                    <span data-ref="eFilterButton" class="ag-header-icon ag-header-cell-filter-button"></span>
                    <div data-ref="eLabel" class="ag-header-cell-label" role="presentation">
                        <span data-ref="eSortOrder" class="ag-header-icon ag-sort-order ag-hidden"></span>
                        <span data-ref="eSortAsc" class="ag-header-icon ag-sort-ascending-icon ag-hidden"></span>
                        <span data-ref="eSortDesc" class="ag-header-icon ag-sort-descending-icon ag-hidden"></span>
                        <span data-ref="eSortAbsoluteAsc" class="ag-header-icon ag-sort-absolute-ascending-icon ag-hidden"></span>
                        <span data-ref="eSortAbsoluteDesc" class="ag-header-icon ag-sort-absolute-descending-icon ag-hidden"></span>
                        <span data-ref="eSortMixed" class="ag-header-icon ag-sort-mixed-icon ag-hidden"></span>
                        <span data-ref="eSortNone" class="ag-header-icon ag-sort-none-icon ag-hidden"></span>
                        ** <span data-ref="eText" class="ag-header-cell-text" role="columnheader"></span>
                        <span data-ref="eFilter" class="ag-header-icon ag-filter-icon"></span>
                    </div>
                </div>`,
      },
    };
  }, []);

  const onGridReady = useCallback((params: GridReadyEvent) => {
    fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data: IOlympicData[]) =>
        setRowData(
          data.map((d) => ({
            ...d,
            prevYearTotalDiff: Math.floor(
              (2 * window.agRandom() - 1) * d.total,
            ),
          })),
        ),
      );
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact<IOlympicData>
            rowData={rowData}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
            onGridReady={onGridReady}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Header template](https://www.ag-grid.com/examples/column-headers-components/header-template/reactFunctionalTs)

### Default Template

This is the default template used in AG Grid:

```html
<div class="ag-cell-label-container" role="presentation">
    <span data-ref="eMenu" class="ag-header-icon ag-header-cell-menu-button" aria-hidden="true"></span>
    <span data-ref="eFilterButton" class="ag-header-icon ag-header-cell-filter-button" aria-hidden="true"></span>
    <div data-ref="eLabel" class="ag-header-cell-label" role="presentation">
        <span data-ref="eText" class="ag-header-cell-text"></span>
        <span data-ref="eFilter" class="ag-header-icon ag-header-label-icon ag-filter-icon" aria-hidden="true"></span>
        <span data-ref="eSortOrder" class="ag-header-icon ag-header-label-icon ag-sort-order" aria-hidden="true"></span>
        <span data-ref="eSortAsc" class="ag-header-icon ag-header-label-icon ag-sort-ascending-icon" aria-hidden="true"></span>
        <span data-ref="eSortDesc" class="ag-header-icon ag-header-label-icon ag-sort-descending-icon" aria-hidden="true"></span>
        <span data-ref="eSortAbsoluteAsc" class="ag-header-icon ag-header-label-icon ag-sort-absolute-ascending-icon ag-hidden"></span>
        <span data-ref="eSortAbsoluteDesc" class="ag-header-icon ag-header-label-icon ag-sort-absolute-descending-icon ag-hidden"></span>
        <span data-ref="eSortMixed" class="ag-header-icon ag-header-label-icon ag-sort-mixed-icon ag-hidden"></span>
        <span data-ref="eSortNone" class="ag-header-icon ag-header-label-icon ag-sort-none-icon" aria-hidden="true"></span>
    </div>
</div>
```

When you provide your own template, everything should work as expected as long as you re-use the same `data-ref` attribute names.

| Ref | Description |
| --- | --- |
| `eMenu` | The container where the column menu icon will appear to enable opening the column menu (in AG Grid Community, this is only used when `columnMenu = 'legacy'`). |
| `eFilterButton` | The container where the column filter icon will appear to enable opening the filter (not used when `columnMenu = 'legacy'`). |
| `eLabel` | The container where there is going to be an onClick mouse listener to trigger the sort. |
| `eText` | The text displayed on the column. |
| `eFilter` | The container with the icon that will appear if the user filters this column (only used when `columnMenu = 'legacy'` or `suppressHeaderFilterButton = true`). |
| `eSortOrder` | If multiple columns are sorted, this shows the index that represents the position of this column in the order. |
| `eSortAsc` | If the column is sorted ascending, this shows the ascending icon. |
| `eSortDesc` | If the column is sorted descending, this shows the descending icon. |
| `eSortAbsoluteAsc` | If the column is sorted absolute ascending, this shows the absolute ascending icon. |
| `eSortAbsoluteDesc` | If the column is sorted absolute descending, this shows the absolute descending icon. |
| `eSortMixed` | If the column has a mixed sort state (e.g. grouped data with differing sort directions), this shows the mixed sort icon. |
| `eSortNone` | If no sort is applied, this shows the no sort icon. Note this icon by default is empty. |

The `data-ref` parameters are used by the grid to identify elements to add functionality to. If you leave an element out of your template, the functionality will not be added. For example if you do not specify `eLabel` then the column will not react to click events for sorting.

> **Note**
>
> Templates are not meant to let you configure icons. If you are looking to change the icons, see [Custom Icons](https://www.ag-grid.com/react-data-grid/custom-icons/) for more information.

## Inner Header Component

When using the Header Component, the `agColumnHeader` component will display the header name, adjacent to any configured menu, filter, and checkbox.

This text value can be overridden with a [Custom Component](https://www.ag-grid.com/react-data-grid/components/) by setting the `innerHeaderComponent` and `innerHeaderComponentParams` properties on the `headerComponentParams` property. This is useful when you only need to implement a Component to customise the **Column Name** without having to reimplement the whole header functionality (sorting, filter, menu, etc...).

```js
colDef = {
    ...
    headerComponentParams : {
        innerHeaderComponent: MyInnerHeaderComponent,
        innerHeaderComponentParams: {
            currencySymbol: '£' // the pound symbol will be placed into params
        }
    }
}
```

#### Custom Inner Header Component

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  ModuleRegistry,
  NumberEditorModule,
  NumberFilterModule,
  TextEditorModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import CustomInnerHeader from "./customInnerHeader.tsx";
import { IOlympicData } from "./interfaces";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  NumberEditorModule,
  TextEditorModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "athlete", suppressHeaderFilterButton: true, minWidth: 120 },
    {
      field: "age",
      sortable: false,
      headerComponentParams: {
        icon: "fa-user",
      },
    },
    { field: "country", suppressHeaderFilterButton: true, minWidth: 120 },
    { field: "year", sortable: false },
    { field: "date", suppressHeaderFilterButton: true },
    { field: "sport", sortable: false },
    {
      field: "gold",
      headerComponentParams: { icon: "fa-cog" },
      minWidth: 120,
    },
    { field: "silver", sortable: false },
    { field: "bronze", suppressHeaderFilterButton: true, minWidth: 120 },
    { field: "total", sortable: false },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      editable: true,
      flex: 1,
      minWidth: 100,
      filter: true,
      headerComponentParams: {
        innerHeaderComponent: CustomInnerHeader,
      },
    };
  }, []);

  const { data, loading } = useFetchJson<IOlympicData>(
    "https://www.ag-grid.com/example-assets/olympic-winners.json",
  );

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact<IOlympicData>
            rowData={data}
            loading={loading}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Custom Inner Header Component](https://www.ag-grid.com/examples/column-headers-components/inner-header-component/reactFunctionalTs)

```ts
const CustomInnerHeaderComponent = (props: CustomInnerHeaderProps) => {
    return <div>{props.displayName}</div>;
};
```

The following props are passed to the Custom Component (`CustomInnerHeaderProps` interface).

### CustomInnerHeaderProps

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `column` | [`Column`](https://www.ag-grid.com/react-data-grid/column-object/) |  |  | The column the header is for. |
| `displayName` | `string` |  |  | The name to display for the column. If the column is using a headerValueGetter, the displayName will take this into account. |
| `enableSorting` | `boolean \| undefined` |  |  | Whether sorting is enabled for the column. Only put sort logic into your header if this is true. |
| `enableMenu` | `boolean` |  |  | Whether menu is enabled for the column. Only display a menu button in your header if this is true. |
| `enableFilterButton` | `boolean` |  |  | Whether filter button should be displayed in the header (for new column menu). |
| `enableFilterIcon` | `boolean` |  |  | Whether filter icon should be displayed in the header (for legacy tabbed column menu). |
| `showColumnMenu` | `Function` |  |  | Callback to request the grid to show the column menu. Pass in the html element of the column menu button to have the grid position the menu over the button. If provided, the grid will call `onClosedCallback` when the menu is closed. |
| `showColumnMenuAfterMouseClick` | `Function` |  |  | Callback to request the grid to show the column menu. Similar to `showColumnMenu`, but will position the menu next to the provided `mouseEvent`. If provided, the grid will call `onClosedCallback` when the menu is closed. |
| `showFilter` | `Function` |  |  | Callback to request the grid to show the filter. Pass in the html element of the filter button to have the grid position the menu over the button. |
| `progressSort` | `Function` |  |  | Callback to progress the sort for this column. The grid will decide the next sort direction eg ascending, descending or 'no sort'. Pass `multiSort=true` if you want to do a multi sort (eg user has Shift held down when they click). |
| `setSort` | `Function` |  |  | Callback to set the sort for this column. Pass the sort direction to use ignoring the current sort eg one of 'asc', 'desc' or null (for no sort). Pass `multiSort=true` if you want to do a multi sort (eg user has Shift held down when they click) |
| `template` | `string` |  |  | Custom header template if provided to `headerComponentParams`, otherwise will be `undefined`. See [Header Templates](https://www.ag-grid.com/javascript-data-grid/column-headers/#header-templates) |
| `innerHeaderComponent` | `any` |  |  | The component to use for inside the header (replaces the text value and leaves the remainder of the Grid's original component). |
| `innerHeaderComponentParams` | `any` |  |  | Additional params to customise to the `innerHeaderComponent`. |
| `eGridHeader` | [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) |  |  | The header the grid provides. The custom header component is a child of the grid provided header. The grid's header component is what contains the grid managed functionality such as resizing, keyboard navigation etc. This is provided should you want to make changes to this cell, eg add ARIA tags, or add keyboard event listener (as focus goes here when navigating to the header). |
| `setTooltip` | `Function` |  |  | Sets a tooltip to the main element of this component. `value` The value to be displayed by the tooltip `shouldDisplayTooltip` A function returning a boolean that allows the tooltip to be displayed conditionally. This option does not work when `enableBrowserTooltips={true}`. |
| `api` | [`GridApi`](https://www.ag-grid.com/react-data-grid/grid-api/) |  |  | The grid api. |
| `context` | [`TContext`](https://www.ag-grid.com/react-data-grid/typescript-generics/#context-tcontext) |  |  | Application context as set on `gridOptions.context`. |

## Custom Component

To use a fully customised Header Component, set `headerComponent` on the Column Definition to the custom component. See [Registering Components](https://www.ag-grid.com/react-data-grid/components/) for an overview.

```js
// a list of column definitions
const colDefs = [

    // no Header Comp specified, uses the Provided Header Comp
    {headerName: "Athlete", field: "athlete"},
    {headerName: "Sport", field: "sport"},

    // uses Custom Header Comp
    {headerName: "Age", field: "age", headerComponent: MyHeaderComponent}
]
```

- Column moving and resizing works without custom logic.
- `suppressHeaderFilterButton=true` is used to suppress the filter menu.
- `sortable=false` is used to suppress sorting.
- The menu icon is configurable.

#### Header component

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  ModuleRegistry,
  NumberEditorModule,
  NumberFilterModule,
  TextEditorModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import CustomHeader from "./customHeader.tsx";
import { IOlympicData } from "./interfaces";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  NumberEditorModule,
  TextEditorModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "athlete", suppressHeaderFilterButton: true, minWidth: 120 },
    {
      field: "age",
      sortable: false,
      headerComponentParams: { menuIcon: "fa-external-link-alt" },
    },
    { field: "country", suppressHeaderFilterButton: true, minWidth: 120 },
    { field: "year", sortable: false },
    { field: "date", suppressHeaderFilterButton: true },
    { field: "sport", sortable: false },
    {
      field: "gold",
      headerComponentParams: { menuIcon: "fa-cog" },
      minWidth: 120,
    },
    { field: "silver", sortable: false },
    { field: "bronze", suppressHeaderFilterButton: true, minWidth: 120 },
    { field: "total", sortable: false },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      editable: true,
      flex: 1,
      minWidth: 100,
      filter: true,
      headerComponent: CustomHeader,
      headerComponentParams: {
        menuIcon: "fa-filter",
      },
    };
  }, []);

  const { data, loading } = useFetchJson<IOlympicData>(
    "https://www.ag-grid.com/example-assets/olympic-winners.json",
  );

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact<IOlympicData>
            rowData={data}
            loading={loading}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Header component](https://www.ag-grid.com/examples/column-headers-components/header-component/reactFunctionalTs)

The following props are passed to the Custom Component (`CustomHeaderProps` interface).

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `column` | [`Column`](https://www.ag-grid.com/react-data-grid/column-object/) |  |  | The column the header is for. |
| `displayName` | `string` |  |  | The name to display for the column. If the column is using a headerValueGetter, the displayName will take this into account. |
| `enableSorting` | `boolean \| undefined` |  |  | Whether sorting is enabled for the column. Only put sort logic into your header if this is true. |
| `enableMenu` | `boolean` |  |  | Whether menu is enabled for the column. Only display a menu button in your header if this is true. |
| `enableFilterButton` | `boolean` |  |  | Whether filter button should be displayed in the header (for new column menu). |
| `enableFilterIcon` | `boolean` |  |  | Whether filter icon should be displayed in the header (for legacy tabbed column menu). |
| `showColumnMenu` | `Function` |  |  | Callback to request the grid to show the column menu. Pass in the html element of the column menu button to have the grid position the menu over the button. If provided, the grid will call `onClosedCallback` when the menu is closed. |
| `showColumnMenuAfterMouseClick` | `Function` |  |  | Callback to request the grid to show the column menu. Similar to `showColumnMenu`, but will position the menu next to the provided `mouseEvent`. If provided, the grid will call `onClosedCallback` when the menu is closed. |
| `showFilter` | `Function` |  |  | Callback to request the grid to show the filter. Pass in the html element of the filter button to have the grid position the menu over the button. |
| `progressSort` | `Function` |  |  | Callback to progress the sort for this column. The grid will decide the next sort direction eg ascending, descending or 'no sort'. Pass `multiSort=true` if you want to do a multi sort (eg user has Shift held down when they click). |
| `setSort` | `Function` |  |  | Callback to set the sort for this column. Pass the sort direction to use ignoring the current sort eg one of 'asc', 'desc' or null (for no sort). Pass `multiSort=true` if you want to do a multi sort (eg user has Shift held down when they click) |
| `innerHeaderComponent` | `any` |  |  | The component to use for inside the header (replaces the text value and leaves the remainder of the Grid's original component). |
| `innerHeaderComponentParams` | `any` |  |  | Additional params to customise to the `innerHeaderComponent`. |
| `eGridHeader` | [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) |  |  | The header the grid provides. The custom header component is a child of the grid provided header. The grid's header component is what contains the grid managed functionality such as resizing, keyboard navigation etc. This is provided should you want to make changes to this cell, eg add ARIA tags, or add keyboard event listener (as focus goes here when navigating to the header). |
| `setTooltip` | `Function` |  |  | Sets a tooltip to the main element of this component. `value` The value to be displayed by the tooltip `shouldDisplayTooltip` A function returning a boolean that allows the tooltip to be displayed conditionally. This option does not work when `enableBrowserTooltips={true}`. |
| `api` | [`GridApi`](https://www.ag-grid.com/react-data-grid/grid-api/) |  |  | The grid api. |
| `context` | [`TContext`](https://www.ag-grid.com/react-data-grid/typescript-generics/#context-tcontext) |  |  | Application context as set on `gridOptions.context`. |

### Responsibilities

The grid provides the following features that should not be implemented by Custom Header Components:

- [**Resizing:**](https://www.ag-grid.com/react-data-grid/column-sizing/) When enabled, the grid will put an invisible widget to be grabbed by the mouse for resizing.
- [**Header Checkbox Selection:**](https://www.ag-grid.com/react-data-grid/row-selection-multi-row/#selecting-all-rows) When enabled, the grid displays a checkbox for 'select all' in the header.
- **Column Moving** The grid will react to Column Dragging to reorder columns.

The Custom Header Component is responsible for the following:

- **Sorting:** You will need to process user interaction for sorting. The default grid component sorts when the user clicks the header with the mouse. You may also need to display icons as the sort state of the column changes.
- **Filtering:** You do not filter via the column (you filter from inside the menu), however you may need to display icons as the filter state of the column changes.
- **Menu:** If you want the user to be able to open the column menu, you will need to manage this user interaction. The default grid component provides a button for the user to click to show the menu.
- **Anything Else:** Whatever you want, you are probably creating a custom header to add your own functionality in.

### Sorting

How you interact with the user for sorting (e.g. listening for mouse clicks) is up to you. The grid helps you by providing column state and events for getting and setting the sort.

After the user requests a sort, you should call ONE of the following:

1. `props.progressSort(multiSort)`: Call this method to progress the sort on the column to the next stage. This uses the grid logic to determine the next sort stage (eg 'descending' normally follows 'ascending').
2. `props.setSort(direction, multiSort)`: If you don't want to use the grid's logic for working out the next sort state, use this to set the sort to a specific state.

```js
// option 1) tell the grid when you want to progress the sorting
onSortClicked(event) {
     // in this example, we do multi sort if Shift key is pressed
    props.progressSort(event.shiftKey);
};

// or option 2) tell the grid when you want to set the sort explicitly
// button that always sorts ASCENDING
onSortAscClicked(event) {
    props.setSort('asc', event.shiftKey);
};

// button that always sorts DESCENDING
onSortDescClicked(event) {
    props.setSort('desc', event.shiftKey);
};
```

To know when a column's sort state has changed (e.g. when to update your icons), you should listen for the `sortChanged` event on the column.

```js
// listen to the column for sort events
column.addEventListener('sortChanged', function() {

    // get sort state from column
    var sort = column.getSortDef()?.direction;
    console.log('sort state of column is ' + sort); // prints one of ['asc',desc',null]

    // then do what you need, e.g. set relevant icons visible
    var sortingAscending = sort==='asc';
    var sortingDescending = sort==='desc';
    var notSorting = !sortingAscending && !sortingDescending;
    // how you update your GUI accordingly is up to you
});

// don't forget to remove your listener in your destroy code
```

### Filtering

The header doesn't normally initiate filtering. If it does, use the standard grid API to set the filter. The header will typically display icons when the filter is applied. To know when to show a filter icon, listen to the column for `filterChanged` events.

```js
// listen to the column for filter events
column.addEventListener('filterChanged', function() {
    // when filter changes on the col, this will print one of [true,false]
    console.log('filter of column is ' + column.isFilterActive());
});

// don't forget to remove your listener in your destroy code
```

### Menu

How you get the user to ask for the column menu is up to you. When you want to display the menu, call the `params.showColumnMenu()` callback. The callback takes the HTML element for the button so that it can place the menu over the button (so the menu appears to drop down from the button).

```js
onMenuClicked() {
    props.showColumnMenu(refButton.current);
});
```

### Refreshing Headers

If you are creating your own [Header Components](https://www.ag-grid.com/react-data-grid/column-headers/) then you will need to be aware of how Header Components are refreshed.

All Header Components that still exist after the new Column Definitions are applied (in other words, the Column still exists after the update, it was not removed) will be re-rendered.

It is up to the Header Component to update based on any changes it may find in the Column Definition.

The example below demonstrates refreshing of the headers. Note the following:

- Each column is configured to use a custom Header Component.
- The Header Component logs to the console when its lifecycle methods/functions are called.
- Toggling between 'Upper Header Names' and 'Lower Header Names' causes the Header Component to refresh.
- Toggling between 'Filter On' and 'Filter Off' causes the Header Component to refresh.
- Toggling between 'Resize On' and 'Resize Off' causes the Header Component to refresh. However there is no change to the Header Component as it doesn't depend on resize - the resize UI is provided by the grid.

#### Refresh Headers

```tsx
("use client");

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  ModuleRegistry,
  NumberFilterModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import CustomHeader from "./customHeader.tsx";
import { IOlympicData } from "./interfaces";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
];

const GridExample = () => {
  const gridRef = useRef<AgGridReact<IOlympicData>>(null);
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "athlete" },
    { field: "age" },
    { field: "country" },
    { field: "year" },
    { field: "date" },
    { field: "sport" },
    { field: "gold" },
    { field: "silver" },
    { field: "bronze" },
    { field: "total" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      headerComponent: CustomHeader,
    };
  }, []);

  const { data, loading } = useFetchJson<IOlympicData>(
    "https://www.ag-grid.com/example-assets/olympic-winners.json",
  );

  const onBtUpperNames = useCallback(() => {
    const columnDefs: ColDef[] = [
      { field: "athlete" },
      { field: "age" },
      { field: "country" },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ];
    columnDefs.forEach((c) => {
      c.headerName = c.field!.toUpperCase();
    });
    gridRef.current!.api.setGridOption("columnDefs", columnDefs);
  }, []);

  const onBtLowerNames = useCallback(() => {
    const columnDefs: ColDef[] = [
      { field: "athlete" },
      { field: "age" },
      { field: "country" },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ];
    columnDefs.forEach((c) => {
      c.headerName = c.field;
    });
    gridRef.current!.api.setGridOption("columnDefs", columnDefs);
  }, []);

  const onBtFilterOn = useCallback(() => {
    const columnDefs: ColDef[] = [
      { field: "athlete" },
      { field: "age" },
      { field: "country" },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ];
    columnDefs.forEach((c) => {
      c.filter = true;
    });
    gridRef.current!.api.setGridOption("columnDefs", columnDefs);
  }, []);

  const onBtFilterOff = useCallback(() => {
    const columnDefs: ColDef[] = [
      { field: "athlete" },
      { field: "age" },
      { field: "country" },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ];
    columnDefs.forEach((c) => {
      c.filter = false;
    });
    gridRef.current!.api.setGridOption("columnDefs", columnDefs);
  }, []);

  const onBtResizeOn = useCallback(() => {
    const columnDefs: ColDef[] = [
      { field: "athlete" },
      { field: "age" },
      { field: "country" },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ];
    columnDefs.forEach((c) => {
      c.resizable = true;
    });
    gridRef.current!.api.setGridOption("columnDefs", columnDefs);
  }, []);

  const onBtResizeOff = useCallback(() => {
    const columnDefs: ColDef[] = [
      { field: "athlete" },
      { field: "age" },
      { field: "country" },
      { field: "year" },
      { field: "date" },
      { field: "sport" },
      { field: "gold" },
      { field: "silver" },
      { field: "bronze" },
      { field: "total" },
    ];
    columnDefs.forEach((c) => {
      c.resizable = false;
    });
    gridRef.current!.api.setGridOption("columnDefs", columnDefs);
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div className="test-container">
          <div className="test-header">
            <button onClick={onBtUpperNames}>Upper Header Names</button>
            <button onClick={onBtLowerNames}>Lower Header Names</button>
            &nbsp;&nbsp;&nbsp;
            <button onClick={onBtFilterOn}>Filter On</button>
            <button onClick={onBtFilterOff}>Filter Off</button>
            &nbsp;&nbsp;&nbsp;
            <button onClick={onBtResizeOn}>Resize On</button>
            <button onClick={onBtResizeOff}>Resize Off</button>
          </div>

          <div style={gridStyle} className="test-grid">
            <AgGridReact<IOlympicData>
              ref={gridRef}
              rowData={data}
              loading={loading}
              columnDefs={columnDefs}
              defaultColDef={defaultColDef}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Refresh Headers](https://www.ag-grid.com/examples/column-headers-components/refresh-headers/reactFunctionalTs)

### Custom Props

On top of the props provided by the grid, you can also provide your own parameters. This is useful if you want to allow configuring the header component. For example, you might have a header component for formatting currency which also requires the currency symbol to be provided.

```js
colDef = {
    ...
    headerComponent: MyHeaderComponent;
    headerComponentParams : {
        currencySymbol: '£' // the pound symbol will be placed into params
    }
}
```

### Keyboard Navigation

When using Custom Header Components, the Custom Header Component is responsible for implementing support for keyboard navigation among its focusable elements. This is why by default, focusing a grid header with a Custom Header Component will focus the entire cell instead of any of the elements inside.

Adding support for keyboard navigation and focus requires a custom `suppressHeaderKeyboardEvent` function in grid options. See [Suppress Keyboard Events](https://www.ag-grid.com/react-data-grid/keyboard-navigation/#suppress-keyboard-events).

An example of this is shown below, enabling keyboard navigation through the custom header elements when pressing `⇥ Tab` and `⇧ Shift`+`⇥ Tab`:

- Click on the top left `Athlete` header, press the `⇥ Tab` key and notice that the button, textbox and link in the `Country` header can be tabbed into. At the end of the cell elements, the tab focus moves to the next `Age` header cell
- Use `⇧ Shift`+`⇥ Tab` to navigate in the reverse direction

The `suppressHeaderKeyboardEvent` callback is used to capture tab events and determine if the user is tabbing forward or backwards. It also suppresses the default behaviour of moving to the next cell if tabbing within the child elements.

If the focus is at the beginning or the end of the cell children and moving out of the cell, the keyboard event is not suppressed, so focus can move between the children elements. Also, when moving backwards, the focus needs to be manually set while preventing the default behaviour of the keyboard press event.

#### Custom Header Keyboard Navigation

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  ModuleRegistry,
  SuppressHeaderKeyboardEventParams,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import CustomHeader from "./customHeader.tsx";
import { IOlympicData } from "./interfaces";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [TextFilterModule, ClientSideRowModelModule];

const GRID_CELL_CLASSNAME = "ag-header-cell";

function getAllFocusableElementsOf(el: HTMLElement) {
  return Array.from<HTMLElement>(
    el.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
    ),
  ).filter((focusableEl) => {
    return focusableEl.tabIndex !== -1;
  });
}

const getEventPath: (event: Event) => HTMLElement[] = (event: Event) => {
  const path: HTMLElement[] = [];
  let currentTarget: any = event.target;
  while (currentTarget) {
    path.push(currentTarget);
    currentTarget = currentTarget.parentElement;
  }
  return path;
};

/**
 * Capture whether the user is tabbing forwards or backwards and suppress keyboard event if tabbing
 * outside of the children
 */
function suppressHeaderKeyboardEvent({
  event,
}: SuppressHeaderKeyboardEventParams) {
  const { key, shiftKey } = event;
  const path = getEventPath(event);
  const isTabForward = key === "Tab" && shiftKey === false;
  const isTabBackward = key === "Tab" && shiftKey === true;
  let suppressEvent = false;
  // Handle cell children tabbing
  if (isTabForward || isTabBackward) {
    const eGridCell = path.find((el) => {
      if (el.classList === undefined) return false;
      return el.classList.contains(GRID_CELL_CLASSNAME);
    });
    if (!eGridCell) {
      return suppressEvent;
    }
    const focusableChildrenElements = getAllFocusableElementsOf(eGridCell);
    const lastCellChildEl =
      focusableChildrenElements[focusableChildrenElements.length - 1];
    const firstCellChildEl = focusableChildrenElements[0];
    // Suppress keyboard event if tabbing forward within the cell and the current focused element is not the last child
    if (isTabForward && focusableChildrenElements.length > 0) {
      const isLastChildFocused =
        lastCellChildEl && document.activeElement === lastCellChildEl;
      if (!isLastChildFocused) {
        suppressEvent = true;
      }
    }
    // Suppress keyboard event if tabbing backwards within the cell, and the current focused element is not the first child
    else if (isTabBackward && focusableChildrenElements.length > 0) {
      const cellHasFocusedChildren =
        eGridCell.contains(document.activeElement) &&
        eGridCell !== document.activeElement;
      // Manually set focus to the last child element if cell doesn't have focused children
      if (!cellHasFocusedChildren) {
        lastCellChildEl.focus();
        // Cancel keyboard press, so that it doesn't focus on the last child and then pass through the keyboard press to
        // move to the 2nd last child element
        event.preventDefault();
      }
      const isFirstChildFocused =
        firstCellChildEl && document.activeElement === firstCellChildEl;
      if (!isFirstChildFocused) {
        suppressEvent = true;
      }
    }
  }
  return suppressEvent;
}

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    {
      field: "athlete",
      sortable: false,
    },
    {
      field: "country",
      headerComponent: CustomHeader,
      minWidth: 270,
      flex: 1,
      sortable: false,
    },
    {
      field: "age",
      sortable: false,
    },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      minWidth: 130,
      flex: 1,
      suppressHeaderKeyboardEvent,
    };
  }, []);

  const { data, loading } = useFetchJson<IOlympicData>(
    "https://www.ag-grid.com/example-assets/olympic-winners.json",
  );

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact<IOlympicData>
            rowData={data}
            loading={loading}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Custom Header Keyboard Navigation](https://www.ag-grid.com/examples/column-headers-components/header-component-keyboard-navigation/reactFunctionalTs)

### Dynamic Tooltips

When using Custom Header Components it might be necessary to have a better control of how `Tooltips` are added instead of simply using the `headerTooltip` config. For this purpose, we provide the `setTooltip` method.

Properties available on the `CustomHeaderProps&lt;TData = any, TContext = any&gt;` interface.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `setTooltip` | `Function` |  |  | Sets a tooltip to the main element of this component. `value` The value to be displayed by the tooltip `shouldDisplayTooltip` A function returning a boolean that allows the tooltip to be displayed conditionally. This option does not work when `enableBrowserTooltips={true}`. |

The example below demonstrates using the Dynamic Tooltips with a Custom Header Component.

- Note that only Column Headers where the text is not fully displayed will show tooltips.

#### Header Tooltip

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  ModuleRegistry,
  NumberEditorModule,
  NumberFilterModule,
  TextEditorModule,
  TextFilterModule,
  TooltipModule,
  enableDevValidations,
} from "ag-grid-community";
import CustomHeader from "./customHeader.tsx";
import { IOlympicData } from "./interfaces";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  NumberEditorModule,
  TextEditorModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
  TooltipModule,
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    {
      field: "athlete",
      headerName: "Athlete's Full Name",
      suppressHeaderFilterButton: true,
      minWidth: 120,
    },
    {
      field: "age",
      headerName: "Athlete's Age",
      sortable: false,
      headerComponentParams: { menuIcon: "fa-external-link-alt" },
    },
    {
      field: "country",
      headerName: "Athlete's Country",
      suppressHeaderFilterButton: true,
      minWidth: 120,
    },
    { field: "year", headerName: "Event Year", sortable: false },
    {
      field: "date",
      headerName: "Event Date",
      suppressHeaderFilterButton: true,
    },
    { field: "sport", sortable: false },
    {
      field: "gold",
      headerName: "Gold Medals",
      headerComponentParams: { menuIcon: "fa-cog" },
      minWidth: 120,
    },
    { field: "silver", headerName: "Silver Medals", sortable: false },
    {
      field: "bronze",
      headerName: "Bronze Medals",
      suppressHeaderFilterButton: true,
      minWidth: 120,
    },
    { field: "total", headerName: "Total Medals", sortable: false },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      editable: true,
      filter: true,
      width: 120,
      headerComponent: CustomHeader,
      headerComponentParams: {
        menuIcon: "fa-bars",
      },
    };
  }, []);

  const { data, loading } = useFetchJson<IOlympicData>(
    "https://www.ag-grid.com/example-assets/olympic-winners.json",
  );

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact<IOlympicData>
            rowData={data}
            loading={loading}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Header Tooltip](https://www.ag-grid.com/examples/column-headers-components/dynamic-tooltips/reactFunctionalTs)

### Touch Support

See the [Touch](https://www.ag-grid.com/react-data-grid/touch/) documentation for how the grid will handle touch support, particularly for [Touch Events](https://www.ag-grid.com/react-data-grid/touch/#custom-header-components).
