Column Sizing

Columns are a core concept for Infinite Table and sizing columns is an important topic to master. Here is a summary of how columns can be sized:

For fine-grained controlled-behavior on column sizing, use the controlled columnSizing prop (for uncontrolled variant, see defaultColumnSizing). If you want to get updates to columns changing size as a result of user interaction, use onColumnSizingChange.

Note

Use columnDefaultWidth to configure the default column width. If a column is not sized otherwise, this will be applied. The default value for columnDefaultWidth is 200 (pixels).

For setting a minimum and maximum width for all columns, use columnMinWidth (defaults to 30) and columnMaxWidth (defaults to 2000) respectively.

Understanding default column sizing

The easiest way to get started and specify a sizing behavior for columns is to use column.defaultWidth, column.defaultFlex and/or columnDefaultWidth (including related pros for specifying limits, like column.minWidth, column.maxWidth and columnMinWidth / columnMaxWidth).

Those properties have default in their name because after the initial rendering of a column, you can't change its size by updating those values - more technically, column.defaultWidth and column.defaultFlex are uncontrolled props.

We suggest you use those to get started and if you don't have care about responding to the user changing the widths of those columns via drag&drop. As long as you're not using onColumnSizingChange to be notified of column size changes, you're probably good with those.

Controlled column sizing

However, once you start using onColumnSizingChange and want to have full control of column sizing (maybe you want to restore it later to the state the user had it when the app was closed), you probably want to use controlled columnSizing.

The columnSizing prop is an object of column ids to column sizing objects. Those sizing objects can have the following properties:

  • flex - use this for flexible columns. Behaves like the flex CSS property.
  • width - use this for fixed sized columns
  • minWidth - specifies the minimum width of the column. Useful for flexible columns or for restricting users resizing both fixed and flexible columns.
  • maxWidth - specifies the maximum width of the column. Useful for flexible columns or for restricting users resizing both fixed and flexible columns.

Note

If a column is not specified in the columnSizing prop (or its uncontrolled variant), or sized otherwise (eg: via the column type), it will have a fixed size, defaulting to columnDefaultWidth (which also defaults to 200 if no value is passed in). You can also specify a columnMinWidth and columnMaxWidth - those will be applied for all columns (namely for those that dont explicitly specify other min/max widths).

const columnSizing: InfiniteTablePropColumnSizing = {
  country: {
    flex: 1,
    // minWidth is optional
    minWidth: 200,
  },
  city: {
    width: 400,
    // and so is maxWidth
    maxWidth: 500,
  },
  salary: {
    flex: 3,
  },
};
// any column not specified in the columnSizing (or defaultColumnSizing) prop
// will have fixed width (defaulting to `columnDefaultWidth`, which in turn defaults to 200px)

Note

You might find specifying the column size outside the column object to be a bit verbose to start with, but it will be easier to manage in many cases and is much more flexible. For example, when the user resizes a column via drag & drop and you want to persist the new column sizes, you don't have to update the whole columns object but instead update columnSizing alone. The same principle is true for columnPinning and other column-level props.

Note

The columnSizing prop also has an uncontrolled version, namely defaultColumnSizing.

Using flexible column sizing

Note

The way flex sizing is implemented is similar to how CSS flexbox algorithm works. Explore this section to find out more details.

Imagine you have 1000px of space available to the viewport of InfiniteTable and you have 3 columns:

  • a fixed column 100px wide - name it col A
  • a fixed column 300px wide - name it col B
  • a flexible column with flex: 1 - name it col F1
  • a flexible column with flex: 2 - name it col F2

The space remaining for the flexible columns is 1000px - 400px = 600px and the sum of all flex values is 3, that means each flex unit will be 600px / 3 = 200px.

This means columns will have the following sizes:

  • col A will be 100px
  • col B will be 300px
  • col F1 will be 200px ( so a flex unit)
  • col F2 will be 400px ( so the equivalent of 2 flex units)

If the browser changes the layout of the component, so InfiniteTable has only 700px available, then a flex unit would be (700px - 400px) / 3 = 100px.

This means columns will have the following sizes:

  • col A will be 100px
  • col B will be 300px
  • col F1 will be 100px ( so a flex unit)
  • col F2 will be 200px ( so the equivalent of 2 flex units)

The flexbox algorithm also uses viewportReservedWidth to determine the width of the viewport to use for sizing columns - you can use viewportReservedWidth=100 to always have a 100px reserved area that won't be used for flexing columns.

Using viewportReservedWidth to reserve whitespace when you have flexible columns

This example has a viewportReservedWidth of 50px.

View Mode
Fork
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export const columns: Record<string, InfiniteTableColumn<Employee>> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  city: {
    field: 'city',
    header: 'City',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

const defaultColumnSizing: InfiniteTablePropColumnSizing = {
  country: { flex: 1 },
  city: { flex: 1 },
  salary: { flex: 2 },
};

export default function App() {
  const [viewportReservedWidth, setViewportReservedWidth] = useState(0);
  return (
    <>
      <div style={{ color: 'var(--infinite-cell-color' }}>
        <p>Current viewport reserved width: {viewportReservedWidth}px.</p>

        <button
          style={{
            padding: 5,
            margin: 5,
            border: '2px solid currentColor',
          }}
          onClick={() => {
            setViewportReservedWidth(0);
          }}
        >
          Click to reset viewportReservedWidth to 0
        </button>
      </div>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          columns={columns}
          columnDefaultWidth={50}
          viewportReservedWidth={viewportReservedWidth}
          onViewportReservedWidthChange={setViewportReservedWidth}
          defaultColumnSizing={defaultColumnSizing}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id

Take a look at the snippet below to see column sizing at work with flexible and fixed columns.

Using controlled columnSizing
View Mode
Fork
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export const columns: InfiniteTablePropColumns<Employee> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },

  city: {
    field: 'city',
    header: 'City',
  },

  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

export default function App() {
  const [columnSizing, setColumnSizing] =
    React.useState<InfiniteTablePropColumnSizing>({
      country: { width: 100 },
      city: { flex: 1, minWidth: 100 },
      salary: { flex: 2, maxWidth: 500 },
    });

  const [viewportReservedWidth, setViewportReservedWidth] = useState(0);

  return (
    <>
      <p style={{ color: 'var(--infinite-cell-color)' }}>
        Current column sizing:{' '}
        <code>
          <pre>{JSON.stringify(columnSizing, null, 2)}</pre>
        </code>
        Viewport reserved width: {viewportReservedWidth} -{' '}
        <button onClick={() => setViewportReservedWidth(0)}>
          click to reset to 0
        </button>
      </p>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          columns={columns}
          columnDefaultWidth={50}
          columnSizing={columnSizing}
          onColumnSizingChange={setColumnSizing}
          viewportReservedWidth={viewportReservedWidth}
          onViewportReservedWidthChange={setViewportReservedWidth}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id

Note

You might find viewportReservedWidth useful for advanced configuration when you have flexible columns.

Note

When he user is performing a column resize (via drag & drop), onViewportReservedWidth is called when the resize is finished (not the case for resizing with the SHIFT key pressed, when adjacent columns share the space between them).

Gotcha

You can also size (generated) group columns by using their column.id property.

For groupRenderStrategy="multi-column", if no id is specified in the group column configuration, each column will have a generated id like this: "group-by-${field}".

For groupRenderStrategy="single-column", if no id is specified in the groupColumn it will default to: "group-by".

Resizing columns via drag & drop

Columns are user-resizable via drag & drop. If you don't want a column to be resizable, specify column.resizable=false

Note

By default, all columns are resizable since resizableColumns defaults to true. The resizableColumns prop controls the behavior for all columns that don't explicitly specify their column.resizable property.

When initially rendered, columns are displayed with their columns.defaultWidth (you can also use columnDefaultWidth) or columns.defaultFlex. Flexible columns take up available space taking into account their flex value, as detailed above.

When the user is resizing columns (or column groups), the effect is seen in real-time, so it's very easy to adjust the columns to desired widths. After the user drops the resize handle to the desired position, onColumnSizingChange is being called, to allow the developer to react to column sizing changes. Also onViewportReservedWidth is called as well when the resize is finished (not the case for resizing with the SHIFT key pressed, when adjacent columns share the space between them).

Note

When flexible columns are resized, they are kept flexible even after the resize. Note however that their flex values will be different to the original flex values and will reflect the new proportions each flex column is taking up at the moment of the resize.

More exactly, the new flex values will be the actual pixel widths. As an example, say there are 2 flex columns, first one with flex 1 and second one with flex 3 and they have an available space of 800px.

const columns = {
  first: { flex: 1, field: 'one' },
  second: { flex: 2, field: 'two' },
};

Initially they will occupy 200px and 600px respectively. If the user resizes them to be of equal size, onColumnSizingChange will be called with an object like

{
  first: { flex: 400 },
  second: {flex: 400 }
}

since those are the actual widths measured from the DOM. This works out well, even if the available space of the table grows, as the proportions will be the same.

Resize Restrictions

When resizing, the user needs to drag the resize handle to adjust the columns to new sizes. While doing so, the resize handle has a (green) color to indicate everything is okay. However, when restrictions are hit (either column min or max widths), the resize handle turns red to indicate further resizing is not possible.

Sharing space on resize

By default when resizing a specific column, the following columns are pushed to the right (when making the column wider) or moved to the left (when making the column narrower).

For sharing space between resizable columns when resizing, the user needs to hold the SHIFT key when grabbing the resize handle. When the handle is dropped and the resize confirmed, onColumnSizingChange is called, but onViewportReservedWidth is not called for this scenario, since the reserved width is preserved.

Resizing column groups

Just as columns are being resized, it is also possible to resize column groups. For this, the user needs to hover over the right border of the column group and start dragging the resize handle.

Note

For multi-level column groups, it's possible to resize any of them. Just grab the handle from the desired group and start dragging. The handle height will indicate which column group is being resized.

Note

If a column group has at least one resizable column, it can be resized.

When resizing, the space is shared proportionally betweem all resizable columns in the group.

Once a min/max limit has been reached for a certain column in the group, the column respects the limit and the other columns keep resizing as usual. When the min/max limit has been reached for all columns in the group, the resize handle turns red to indicate further resizing is no longer possible.

Resizing column groups

Try resizing the Finance and Regional Info column groups.

The columns in the Finance group can be resized an extra 30px (they have a maxWidth of 130px).

View Mode
Fork
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumnGroup,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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

const dataSource = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  currency: {
    field: 'currency',
    columnGroup: 'finance',
    maxWidth: 130,
  },
  salary: {
    field: 'salary',
    columnGroup: 'finance',
    maxWidth: 130,
  },
  country: {
    field: 'country',
    columnGroup: 'regionalInfo',
    maxWidth: 400,
  },
  preferredLanguage: {
    field: 'preferredLanguage',
    columnGroup: 'regionalInfo',
  },
  id: { field: 'id', defaultWidth: 80 },
  firstName: {
    field: 'firstName',
  },
  stack: {
    field: 'stack',
  },
};

const columnGrous: Record<string, InfiniteTableColumnGroup> = {
  regionalInfo: {
    header: 'Regional Info',
  },
  finance: {
    header: 'Finance',
    columnGroup: 'regionalInfo',
  },
};

export default function ColumnValueGetterExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          columnGroups={columnGrous}
          columns={columns}
          columnDefaultWidth={100}
        />
      </DataSource>
    </>

Customizing the resize handle colors

It's possible to customize the resize handle colors and width.

For adjusting the handle colors, use the following CSS variables:

  • --infinite-resize-handle-hover-background - the color of the resize handle when it's in a green/all good state.
  • --infinite-resize-handle-constrained-hover-background - the color of the resize handle when it has reached a min/max constraint.

You can also adjust the width of the resize handle:

  • --infinite-resize-handle-width - the width of the green/red column resize handle. Defaults to 2px
  • --infinite-resize-handle-active-area-width - the width of the area you can hover over in order to grab the resize handle. Defaults to 20px. The purpose of this active area is to make it easier to grab the resize handle.

Auto-sizing columns

For sizing columns to the width of their content, you can use autoSizeColumnsKey to declaratively auto-size columns:

  • when autoSizeColumnsKey is a string or number and the value of the prop is changed, all columns will be auto-sized.
  • when autoSizeColumnsKey is an object, it needs to have a key property (of type string or number), so whenever the key changes, the columns will be auto-sized. Specifying an object for autoSizeColumnsKey gives you more control over which columns are auto-sized and if the size measurements include the header or not.

When an object is used, the following properties are available:

  • key - mandatory property, which, when changed, triggers the update
  • includeHeader - optional boolean, - decides whether the header will be included in the auto-sizing calculations. If not specified, true is assumed.
  • columnsToSkip - a list of column ids to skip from auto-sizing. If this is used, all columns except those in the list will be auto-sized.
  • columnsToResize - the list of column ids to include in auto-sizing. If this is used, only columns in the list will be auto-sized.
Auto-sizing columns
View Mode
Fork
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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

const dataSource = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id' },
  firstName: { field: 'firstName' },
  preferredLanguage: { field: 'preferredLanguage' },
  stack: { field: 'stack' },
  country: { field: 'country' },
  age: { field: 'age', type: 'number' },
  salary: { field: 'salary', type: 'number' },
  currency: { field: 'currency', type: 'number' },
};

export default function GroupByExample() {
  const [key, setKey] = React.useState(0);
  const [includeHeader, setIncludeHeader] = React.useState(false);

  const autoSizeColumnsKey = React.useMemo(() => {
    return {
      includeHeader,
      key,
    };
  }, [key, includeHeader]);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          background: 'var(--infinite-background)',
        }}
      >
        <label>
          <input
            checked={includeHeader}
            type={'checkbox'}
            onChange={(e) => {
              setIncludeHeader(e.target.checked);
            }}
          />{' '}
          Include header
        </label>

        <button
          style={{
            margin: 10,
            padding: 10,
            borderRadius: 5,
            border: '2px solid magenta',
          }}
          onClick={() => {
            setKey((key) => key + 1);
          }}
        >
          Click to auto-size
        </button>
      </div>

      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          autoSizeColumnsKey={autoSizeColumnsKey}
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>