# Column Headers

> Configure column headers with custom column header, custom sort icon, menu icon and more.

Canonical page: https://infinite-table.com/docs/learn/columns/column-headers

Column headers have the same level of customization as column cells - you can fully control what is being rendered and when. Here's a summary of the things you can do in the column header:

- customize the header label of a column
- specify custom sort icon
- configure and customize the menu icon
- configure the column selection chechbox (for columns configured to display a selection checkbox)
- customize the order of all of the above, and select which ones should be included

## Column Header Label

By default, the label displayed for the column header is the [field](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.field) the column is bound to. If you want to customize this, use the [header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) property.

```tsx
type Developer = {
  id: string;
  firstName: string;
  lastName: string;
  age: number;
};
const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id', // will be used as default label in column header
    defaultWidth: 100,
  },
  name: {
    header: 'First and Last Name', // custom column header label
    valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,
  },
};
```

**Example: Simple table with both default and custom column headers**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id', // will be used as default label in column header
    defaultWidth: 100,
  },
  name: {
    header: 'First and Last Name', // custom column header label
    valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,
  },
};

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  age: number;
};

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const domProps = {
  style: {
    minHeight: 300,
  },
};
export default function App() {
  return (
    <DataSource<Developer> primaryKey="id" data={dataSource}>
      <InfiniteTable<Developer>
        debugId="column-header-example"
        domProps={domProps}
        columns={columns}
      />
    </DataSource>
  );
}
```

Having the [header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) property be a strin value is useful but when you want more flexibility, you can use a function instead.

When [the column header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) is a function, it is called with an object that contains the following properties:

- `column` - the current column object. NOTE: it's not the same as the column object you passed to the [columns](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) prop - but rather an enhanced version of that, which contains additional properties and computed values. It is called a "computed" column - typed as `InfiniteTableComputedColumn<DATA_TYPE>`.
- `columnsMap` - a map of all computed columns available in the table, keyed by the column id. This is useful if at runtime you need access to other columns in the table. NOTE: this map does not contain only the visible columns, but rather ALL the columns.
- `columnSortInfo` - the sorting information for the current column, or `null` if the column is not sorted.
- `api` - a reference to the table [API](https://infinite-table.com/docs/reference/api/index.md) object.
- `columnApi` - a reference to the table [Column API](https://infinite-table.com/docs/reference/column-api/index.md) object for bound to the current column.
- `allRowsSelected: boolean`
- `someRowsSelected: boolean`
- `renderBag` - more on that below - used to reference changes between the different render functions of the column header (those functions are the column header rendering pipeline described in the next section).

All the render props exposed for the rendering pipeline of the column header are called with the same object as the first argument.

Having the [column header](https://infinite-table.com/docs/reference/infinite-table-props.md#column.header) as a function and having access to the state of the column and of the table allows you to create very dynamic column headers that accurately reflect column state.

## Column Header Rendering Pipeline

The rendering pipeline of the column header is similar to the one of the column cells.

It's a series of functions defined on the column that are called while rendering elements found in the column header (the header label, the sort and menu icons, the filtering icon, the selection checkbox).

All of the functions that are part of the column header rendering pipeline are called with the same object as the first argument - the shape of this object is described in the previous section.

- [renderSortIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSortIcon)
- [renderFilterIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderFilterIcon)
- [renderMenuIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon)
- [renderSelectionCheckBox](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSelectionCheckBox)
- [renderHeaderSelectionCheckBox](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderHeaderSelectionCheckBox)
- [header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header)

If you want to customize any of the above, use the corresponding function.

For even more control, the last function in the pipeline that gets called is the [column.renderHeader](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderHeader) function.

This function is called with the same object as the first argument, but it also has a `renderBag` property that contains the result of all the previous functions in the pipeline (eg: `renderBag.sortIcon` - the result of the [renderSortIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSortIcon) call, `renderBag.filterIcon` - the result of the [renderFilterIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderFilterIcon) call, etc).

So if you specify a custom [renderHeader](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderHeader) function, it's up to you to use the results of the previous functions in the pipeline, in order to fully take control of the column header.

#### Available properties on the renderBag

The `renderBag` object contains the following properties available to the render functions of the column header:

- `header` - the label of the column header.
- `sortIcon` - the default sort icon
- `filterIcon` - the filter icon - displayed when the current column is used in filtering
- `filterEditor` - the current filter editor
- `menuIcon` - the menu icon that can be clicked to open the column menu
- `selectionCheckBox` - the selection check box - displays the current selection status and controls the selection for all rows.
- `all` - all of the above combined together in a `React.Fragment`.

### Customizing the Sort Icon

For customizing the sort icon, use the [column.renderSortIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSortIcon) function.

Inside that function you can either use the object passed as a parameter to get information about the sort state of the column

```tsx {1} title="Customizing_the_column_sort_icon"
renderSortIcon({ columnSortInfo }) {
  if (!columnSortInfo) {
    return ' 🤷‍♂️';
  }
  return columnSortInfo.dir === 1 ? '▲' : '▼';
}
```

or you can use the [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) hook to get the same information.

```tsx {8} title="Customizing_the_column_sort_icon"
import {
  useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';

/// ...

renderSortIcon(){
  const { columnSortInfo } = useInfiniteHeaderCell();
  if (!columnSortInfo) {
    return ' 🤷‍♂️';
  }
  return columnSortInfo.dir === 1 ? '▲' : '▼';
},
```

**Example: Custom sort icon for the name column**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
  useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';

import * as React from 'react';

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id', // will be used as default label in column header
    defaultWidth: 100,
  },
  name: {
    header: 'Name', // custom column header label
    valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,
    renderSortIcon: () => {
      const { columnSortInfo } = useInfiniteHeaderCell(); // eslint-disable-line
      if (!columnSortInfo) {
        return ' 🤷‍♂️';
      }
      return columnSortInfo.dir === 1 ? '▲' : '▼';
    },
  },
};

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  age: number;
};

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const domProps = {
  style: {
    minHeight: 300,
  },
};
export default function App() {
  return (
    <DataSource<Developer> primaryKey="id" data={dataSource}>
      <InfiniteTable<Developer>
        debugId="column-sort-icon-example"
        domProps={domProps}
        columns={columns}
      />
    </DataSource>
  );
}
```

### Customizing the Menu Icon

For customizing the menu icon, use the [column.renderMenuIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon) function.

Inside that function you can either use the object passed as a parameter to get information about the column

```tsx {1} title="Customizing_the_menu_icon"
renderMenuIcon({ column }) {
  return `🔧 ${column.id}`;
}
```

or you can use the [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) hook to get the same information.

```tsx {8} title="Customizing_the_menu_icon"
import {
  useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';

/// ...

renderMenuIcon(){
  const { column } = useInfiniteHeaderCell();
  return `🔧 ${column.id}`;
},
```

**Example: Custom menu icon for the name and age columns**

Hover over the header for the `Name` and `Age` columns to see the custom menu icon.

Also, the id column has `renderMenuIcon: false` set, so it doesn't show a column menu at all.

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    defaultWidth: 80,
    renderMenuIcon: false,
  },

  name: {
    header: 'Name', // custom column header label
    valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,

    // custom menu icon
    renderMenuIcon: () => <div>🌎</div>,
  },
  age: {
    field: 'age',
    header: 'Age',
    renderMenuIcon: ({ column }) => {
      return `🔧 ${column.id}`;
    },
  },
};

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  age: number;
};

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const domProps = {
  style: {
    minHeight: 300,
  },
};
export default function App() {
  return (
    <DataSource<Developer> primaryKey="id" data={dataSource}>
      <InfiniteTable<Developer>
        debugId="column-menu-icon-example"
        domProps={domProps}
        columns={columns}
      />
    </DataSource>
  );
}
```

If you don't want to show a column menu (icon) at all, you can set the [column.renderMenuIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon) prop to `false`.

Also, see the [column.renderMenuIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon) docs for an example on how to use the api to open the column menu.

### Customizing the Filter Icon

For customizing the filter icon, use the [column.renderFilterIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderFilterIcon) function.

Inside that function you can either use the object passed as a parameter to get information about the `filtered` state of the column

```tsx {1} title="Customizing_the_filter_icon"
renderFilterIcon({ filtered }) {
  return filtered ? '🔍' : '';
}
```

or you can use the [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) hook to get the same information.

```tsx {8} title="Customizing_the_menu_icon"
import {
  useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';

/// ...

renderMenuIcon(){
  const { filtered } = useInfiniteHeaderCell();
  return filtered ? '🔥' : '';
},
```

In addition, you can use the `filtered` property in the [column.header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) function to determine if the column is filtered or not and render a different header label.

If specified, the [column.renderFilterIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderFilterIcon) function prop is called even if the column is not currently filtered.

**Example: Custom filter icons for salary and name columns**

The `salary` column will show a bolded label when filtered.

The `firstName` column will show a custom filter icon when filtered.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

const data: DataSourceData<Developer> = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 100,
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: ({ filtered }) => {
      return filtered ? <b>Salary</b> : 'Salary';
    },

    renderFilterIcon: () => {
      return null;
    },
  },

  firstName: {
    field: 'firstName',
    renderFilterIcon: ({ filtered }) => {
      return filtered ? '🔥' : '';
    },
  },
  stack: { field: 'stack' },
  currency: { field: 'currency' },
};

const domProps = {
  style: {
    height: '100%',
  },
};
export default () => {
  return (
    <>
      <React.StrictMode>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          defaultFilterValue={[]}
          filterDelay={0}
          filterMode="local"
        >
          <InfiniteTable<Developer>
            debugId="column-filter-icon-example"
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```

#### Changing the display of filters

Infinite Table allows very deep cusstomization of the column header, including the filters.

For example, you might not want to display the column filters under the column header, but rather in a separate menu popover.

This section shows how to do that. You can use [showColumnFilters=false](https://infinite-table.com/docs/reference/infinite-table-props.md#showColumnFilters) to hide the filters from under the column header.

Next, you can use the [column.renderHeader](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderHeader) function to render a custom filter icon that opens a filter popover when clicked.

You don't need to re-implement the filter editor, you have acces to it via the `renderBag.filterEditor` property. The code below shows how to do this.

**Example: Custom display of column filters**

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  InfiniteTableColumn,
  useInfiniteHeaderCell,
  alignNode,
  useInfinitePortalContainer,
} from '@infinite-table/infinite-react';
import { createPortal } from 'react-dom';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

const data: DataSourceData<Developer> = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const FilterIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="16"
    height="16"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
  >
    <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
  </svg>
);

function ColumnFilterMenuIcon() {
  const {
    renderBag,
    htmlElementRef: alignToRef,
    column,
  } = useInfiniteHeaderCell();

  const portalContainer = useInfinitePortalContainer();

  const [visible, setVisible] = React.useState(false);

  React.useEffect(() => {
    if (!domRef.current || !alignToRef.current) {
      return;
    }

    alignNode(domRef.current, {
      alignTo: alignToRef.current,
      alignPosition: [['TopRight', 'BottomRight']],
    });
  });

  const domRef = React.useRef<HTMLDivElement>(null);

  return (
    <div
      onMouseDown={(e) => {
        if (e.nativeEvent) {
          // @ts-ignore
          e.nativeEvent.__insideMenu = column.id;
        }
      }}
      onPointerDown={(event) => {
        event.stopPropagation();

        if (visible) {
          setVisible(false);
          return;
        }

        setVisible(true);

        function handleMouseDown(event: MouseEvent) {
          // @ts-ignore
          if (event.__insideMenu !== column.id) {
            setVisible(false);
            document.documentElement.removeEventListener(
              'mousedown',
              handleMouseDown,
            );
          }
        }
        document.documentElement.addEventListener('mousedown', handleMouseDown);
      }}
    >
      <FilterIcon />
      {createPortal(
        <div
          ref={domRef}
          style={{
            position: 'absolute',
            color: 'white',
            top: 0,
            overflow: 'visible',
            background: 'var(--infinite-background)',
            border: `var(--infinite-cell-border)`,
            left: 0,
            width: column.computedWidth,
            padding: 10,
            display: visible ? 'block' : 'none',
          }}
        >
          {renderBag.filterEditor}
        </div>,
        portalContainer!,
      )}
    </div>
  );
}

const customHeaderWithFilterMenu: InfiniteTableColumn<any>['renderHeader'] = ({
  renderBag,
}) => {
  return (
    <>
      {renderBag.header}
      <div style={{ flex: 1 }}></div>

      {renderBag.filterIcon}

      {renderBag.menuIcon}
      <ColumnFilterMenuIcon />
    </>
  );
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 100,
    renderHeader: customHeaderWithFilterMenu,
  },
  salary: {
    field: 'salary',
    type: 'number',
    renderHeader: customHeaderWithFilterMenu,
  },

  firstName: {
    field: 'firstName',
    renderHeader: customHeaderWithFilterMenu,
  },
  stack: { field: 'stack', renderHeader: customHeaderWithFilterMenu },
  currency: { field: 'currency', renderHeader: customHeaderWithFilterMenu },
};

export default () => {
  return (
    <>
      <React.StrictMode>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          defaultFilterValue={[]}
          shouldReloadData={{
            filterValue: false,
            sortInfo: false,
            groupBy: false,
            pivotBy: false,
          }}
        >
          <InfiniteTable<Developer>
            debugId="custom-column-filter-display-example"
            showColumnFilters={false}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```

### Customizing the Selection Checkbox

For customizing the selection checkbox in the column header, use the [column.renderHeaderSelectionCheckBox](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderHeaderSelectionCheckBox) function.

If you want another column, other than the group column, to show a selection checkbox, you have to also set the [column.renderSelectionCheckBox](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSelectionCheckBox) prop to `true`.

**Example: Custom header checkbox selection for columns**

The group column, as well as the `stack` column display a custom selection checkbox in the column header.

```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type {
  InfiniteTableProps,
  InfiniteTablePropColumns,
  DataSourceProps,
} from '@infinite-table/infinite-react';
import * as React from 'react';

const columns: InfiniteTablePropColumns<Developer> = {
  country: {
    field: 'country',
  },
  firstName: {
    field: 'firstName',
    defaultHiddenWhenGroupedBy: '*',
  },
  stack: {
    renderSelectionCheckBox: true,
    renderHeaderSelectionCheckBox: ({ renderBag }) => {
      // render the default value and decorate it
      return <b>[{renderBag.selectionCheckBox}]</b>;
    },
    field: 'stack',
  },
  age: { field: 'age' },
  id: { field: 'id' },
  preferredLanguage: {
    field: 'preferredLanguage',
  },
  canDesign: {
    field: 'canDesign',
  },
};

const defaultGroupBy: DataSourceProps<Developer>['groupBy'] = [
  {
    field: 'canDesign',
  },
  {
    field: 'stack',
  },
  {
    field: 'preferredLanguage',
  },
];

const groupColumn: InfiniteTableProps<Developer>['groupColumn'] = {
  field: 'firstName',
  renderHeaderSelectionCheckBox: ({ renderBag }) => {
    // render the default value and decorate it
    return <b>[{renderBag.selectionCheckBox}]</b>;
  },
  defaultWidth: 300,
};

const domProps = {
  style: {
    flex: 1,
    minHeight: 500,
  },
};

export default function App() {
  return (
    <DataSource<Developer>
      data={dataSource}
      groupBy={defaultGroupBy}
      selectionMode="multi-row"
      primaryKey="id"
    >
      <InfiniteTable<Developer>
        debugId="column-header-selection-checkbox-example"
        columns={columns}
        domProps={domProps}
        groupColumn={groupColumn}
        columnDefaultWidth={150}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  country: string;
  city: string;
  currency: string;
  email: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';
  hobby: string;
  salary: number;
  age: number;
};
```
