Keyboard Navigation for Table Cells
By default, keyboard navigation for table cells is enabled in React Infinite Table. When a cell is clicked, it shows a highlight that indicates it is the currently active cell. From that point onwards, the user can use the keyboard to navigate the table cells.
Click on a cell in the table and use the arrow keys to navigate around.
import { InfiniteTable, DataSource, DataSourceData, } 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: DataSourceData<Developer> = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers1k-sql?`) .then((r) => r.json()) .then((data: Developer[]) => data); }; const columns: InfiniteTablePropColumns<Developer> = { preferredLanguage: { field: 'preferredLanguage' }, country: { field: 'country' }, salary: { field: 'salary', type: 'number', }, age: { field: 'age' }, canDesign: { field: 'canDesign' }, firstName: { field: 'firstName' }, stack: { field: 'stack' }, id: { field: 'id' }, hobby: { field: 'hobby' }, city: { field: 'city' }, currency: { field: 'currency' }, }; export default function KeyboardNavigationForCells() { return ( <> <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> // keyboardNavigation="cell" is the default, so no need to specify it columns={columns} /> </DataSource> </>
Note
- Use
ArrowUp
andArrowDown
to navigate to the previous and next cells vertically. - Use
ArrowLeft
andArrowRight
to navigate to the previous and next cells horizontally.
- Use
PageUp
andPageDown
to navigate the cells vertically by pages (a page is considered equal to the visible row count). - Use
Shift+PageUp
andShift+PageDown
to navigate the cells horizontally by pages (a page is considered equal to the visible column count).
- Use
Home
andEnd
to navigate vertically to the cell above (that's on the first row) and the cell below (that's on the last row), - Use
Shift+Home
andShift+End
to navigate horizontally to the first and respectively last cell in the current row.
Keyboard navigation is controlled by the keyboardNavigation
prop, which can be either "cell"
, "row"
or false
. Navigating table cells is the default behavior.
Using a default active cell
You can also specify an initial active cell, by using defaultActiveCellIndex=[2,4]
. This tells the table that there should be a default active cell, namely the one at index 2,4 (row 2, so third row; column 4, so fifth column).
Note
The active cell should be an array of length 2, where the first number is the index of the row and the second number is the index of the column (both are zero-based).
This example starts with cell [2,0]
already active.
import { InfiniteTable, DataSource, DataSourceData, } 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: DataSourceData<Developer> = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers1k-sql?`) .then((r) => r.json()) .then((data: Developer[]) => data); }; const columns: InfiniteTablePropColumns<Developer> = { preferredLanguage: { field: 'preferredLanguage' }, country: { field: 'country' }, salary: { field: 'salary', type: 'number', }, age: { field: 'age' }, canDesign: { field: 'canDesign' }, firstName: { field: 'firstName' }, stack: { field: 'stack' }, id: { field: 'id' }, hobby: { field: 'hobby' }, city: { field: 'city' }, currency: { field: 'currency' }, }; export default function KeyboardNavigationForCells() { return ( <> <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> defaultActiveCellIndex={[2, 0]} columns={columns} /> </DataSource> </>
Listening to active cell changes
You can easily listen to changes in the cell navigation by using the onActiveCellIndexChange
callback.
Note
When you use controlled activeCellIndex
, make sure to use onActiveCellIndexChange
to update the prop value, as otherwise the component will not update on navigation
This example starts with cell [2,0]
already active and uses onActiveCellIndexChange
to update activeCellIndex
.
import { InfiniteTable, DataSource, DataSourceData, } 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: DataSourceData<Developer> = () => { return fetch( 'https://infinite-table.com/.netlify/functions/json-server' + `/developers1k-sql?`, ) .then((r) => r.json()) .then((data: Developer[]) => data); }; const columns: InfiniteTablePropColumns<Developer> = { preferredLanguage: { field: 'preferredLanguage' }, country: { field: 'country' }, salary: { field: 'salary', type: 'number', }, age: { field: 'age' }, canDesign: { field: 'canDesign' }, firstName: { field: 'firstName' }, stack: { field: 'stack' }, id: { field: 'id' }, hobby: { field: 'hobby' }, city: { field: 'city' }, currency: { field: 'currency' }, }; export default function KeyboardNavigationForCells() { const [activeCellIndex, setActiveCellIndex] = React.useState< [number, number] >([2, 0]); return ( <> <div style={{ color: 'var(--infinite-cell-color)', }} > Current active cell: {activeCellIndex[0]}, {activeCellIndex[1]}. </div> <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> activeCellIndex={activeCellIndex} onActiveCellIndexChange={setActiveCellIndex} columns={columns} /> </DataSource> </>
Toggling group rows
When the DataSource is grouped, you can use the keyboard to collapse/expand group rows, by pressing the Enter
key on the active row.
Hint
Your active cell doesn't need to be in the group column in order for Enter
key to collapse/expand the group row - being on a group row is enough.
Press the Enter
key on the active group row to toggle it.
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react'; import type { DataSourceProps, InfiniteTableProps, InfiniteTablePropColumns, } from '@infinite-table/infinite-react'; import * as React from 'react'; const columns: InfiniteTablePropColumns<Developer> = { country: { field: 'country', }, firstName: { field: 'firstName', defaultHiddenWhenGroupedBy: '*', }, stack: { 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', defaultWidth: 300, }; const domProps = { style: { flex: 1, minHeight: 500, }, }; export default function App() { return ( <DataSource<Developer> data={dataSource} groupBy={defaultGroupBy} primaryKey="id" > <InfiniteTable<Developer> columns={columns} domProps={domProps} keyboardNavigation="cell" hideColumnWhenGrouped groupColumn={groupColumn} columnDefaultWidth={150} /> </DataSource> ); } const dataSource = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; type Developer = { id
Selecting Rows with the Keyboard
When rowSelection
is enabled (read more about it in the row selection page), you can use the spacebar key to select a group row (or shift
+ spacebar to do multiple selection).
By default keyboardSelection
is enabled, so you can use the spacebar key to select multiple rows, when selectionMode="multi-row"
. Using the spacebar key is equivalent to doing a mouse click, so expect the combination of spacebar + cmd
/ctrl
/shift
modifier keys to behave just like clicking + the same modifier keys.
Use spacebar + optional cmd
/ctrl
/shift
modifier keys just like you would do clicking + the same modifier keys.
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react'; import * as React from 'react'; import { useState } from 'react'; const columns: InfiniteTablePropColumns<Developer> = { country: { field: 'country', }, firstName: { field: 'firstName', }, stack: { field: 'stack', }, age: { field: 'age' }, id: { field: 'id' }, salary: { field: 'salary', type: 'number', }, canDesign: { field: 'canDesign' }, }; export default function App() { const [keyboardSelection, setKeyboardSelection] = useState(true); return ( <> <div style={{ color: 'var(--infinite-cell-color)', padding: 10, }} > Keyboard selection is now{' '} <b>{keyboardSelection ? 'enabled' : 'disabled'}</b>. <button style={{ border: '1px solid tomato', display: 'block', padding: 5, marginTop: 10, }} onClick={() => setKeyboardSelection((x) => !x)} > Click to toggle keyboard selection </button> </div> <DataSource<Developer> data={dataSource} selectionMode="multi-row" primaryKey="id" > <InfiniteTable<Developer> keyboardSelection={keyboardSelection} columns={columns} columnDefaultWidth={150} /> </DataSource> </> ); } const dataSource = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; type Developer = { id
Note
For selection all the rows in the table, you can use cmd
/ctrl
+ A
keyboard shortcut.
Hint
Keyboard selection is also possible when there's a column configured with checkbox selection - make sure you read more about it.
Theming
There are a number of ways to customize the appearance of the element that highlights the active cell.
The easiest is to override those three CSS variables:
--infinite-active-cell-border-color--r
- thered
component of the border color--infinite-active-cell-border-color--g
- thegreen
component of the border color--infinite-active-cell-border-color--b
- theblue
component of the border color
The initial values for those are 77
, 149
and215
respectively, so the border color is rgb(77, 149, 215)
.
In addition, the background color of the active cell highlight element is set to the same color as the border color (computed based on the above r
, g
and b
variables), but with an opacity of 0.25
, configured via the --infinite-active-cell-background-alpha
CSS variable.
When the table is not focused, the opacity for the background color is set to 0.1
, which is the default value of the --infinite-active-cell-background-alpha--table-unfocused
CSS variable.
Note
To summarize, use
--infinite-active-cell-border-color--r
--infinite-active-cell-border-color--g
--infinite-active-cell-border-color--b
to control border and background color of the active cell highlight element.
There are other CSS variables as well, that give you fined-tuned control over both the border and background color for the active cell, if you don't want to use the above three variables to propagate the same color across both border and background.
--infinite-active-cell-background
- the background color. If you use this, you need to set opacity yourself.--infinite-active-cell-border
- border configuration (eg:2px solid magenta
). If you use this, it will not be propagated to the background color.
Use the color picker to configured the desired color for the active cell highlight
import * as React from 'react'; import { useState, useMemo, HTMLProps, ChangeEvent } from 'react'; import { InfiniteTable, DataSource, DataSourceData, debounce, } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns } from '@infinite-table/infinite-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: DataSourceData<Developer> = () => { return fetch( 'https://infinite-table.com/.netlify/functions/json-server' + `/developers1k-sql?`, ) .then((r) => r.json()) .then((data: Developer[]) => data); }; const columns: InfiniteTablePropColumns<Developer> = { preferredLanguage: { field: 'preferredLanguage' }, country: { field: 'country' }, salary: { field: 'salary', type: 'number', }, age: { field: 'age' }, canDesign: { field: 'canDesign' }, firstName: { field: 'firstName' }, stack: { field: 'stack' }, id: { field: 'id' }, hobby: { field: 'hobby' }, city: { field: 'city' }, currency: { field: 'currency' }, }; const rgb = { r: 77, g: 149, b: 215, }; const defaultColor = `#${rgb.r.toString(16)}${rgb.g.toString( 16, )}${rgb.b.toString(16)}`; export default function KeyboardNavigationForCells() { const [color, setColor] = useState({ ...rgb, }); const domProps = useMemo(() => { return { style: { '--infinite-active-cell-border-color--r': color.r, '--infinite-active-cell-border-color--g': color.g, '--infinite-active-cell-border-color--b': color.b, // for the same of the example being more obvious, // make the opacity of the unfocused table same as the one used on focus '--infinite-active-cell-background-alpha--table-unfocused': '0.25', // but this defaults to 0.1 }, } as HTMLProps<HTMLDivElement>; }, [color]); const onChange = useMemo(() => { const onColorChange = (event: ChangeEvent<HTMLInputElement>) => { const color = event.target.value; const r = parseInt(color.substr(1, 2), 16); const g = parseInt(color.substr(3, 2), 16); const b = parseInt(color.substr(5, 2), 16); setColor({ r, g, b, }); }; return debounce(onColorChange, { wait: 200 }); }, []); return ( <> <div style={{ color: 'var(--infinite-cell-color)', }} > Select color{' '} <input type="color" onChange={onChange} defaultValue={defaultColor} /> </div> <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> defaultActiveCellIndex={[5, 0]} domProps={domProps} columns={columns} /> </DataSource> </>