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:
- fixed-sized columns can be specified via
columns.defaultWidth
- flexible columns need
columns.defaultFlex
columns.minWidth
specifies the minimum size for a columncolumns.maxWidth
is for the maximum width a column can take- default values are available for all of the above:
columnDefaultWidth
gives all columns (that are otherwise unconfigured) a default sizecolumnMinWidth
specifies the minimum width for all columns (that don't have one)columnMaxWidth
specifies the maximum width for all columns (that don't have one)
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 columnsminWidth
- 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 colA
- a fixed column
300px
wide - name it colB
- a flexible column with
flex: 1
- name it colF1
- a flexible column with
flex: 2
- name it colF2
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 be100px
- col
B
will be300px
- col
F1
will be200px
( so a flex unit) - col
F2
will be400px
( so the equivalent of2
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 be100px
- col
B
will be300px
- col
F1
will be100px
( so a flex unit) - col
F2
will be200px
( so the equivalent of2
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.
This example has a viewportReservedWidth
of 50px
.
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.
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.
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
).
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 agreen
/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 thegreen
/red
column resize handle. Defaults to2px
--infinite-resize-handle-active-area-width
- the width of the area you can hover over in order to grab the resize handle. Defaults to20px
. 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 astring
ornumber
and the value of the prop is changed, all columns will be auto-sized. - when
autoSizeColumnsKey
is an object, it needs to have akey
property (of typestring
ornumber
), so whenever thekey
changes, the columns will be auto-sized. Specifying an object forautoSizeColumnsKey
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 updateincludeHeader
- 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.
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> </>