useTable
Provides the behavior and accessibility implementation for a table component. A table displays data in rows and columns and enables a user to navigate its contents via directional navigation keys, and optionally supports row selection and sorting.
| install | yarn add @react-aria/table |
|---|---|
| version | 3.0.0 |
| usage | import {useTable, useTableCell, useTableColumnHeader, useTableRow, useTableHeaderRow, useTableRowGroup, useTableSelectAllCheckbox, useTableSelectionCheckbox} from '@react-aria/table' |
API#
useTable<T>(
props: TableProps<T>,
state: TableState<T>,
ref: RefObject<HTMLElement>
): GridAriauseTableRowGroup(): GridRowGroupAriauseTableHeaderRow<T>(
props: GridRowProps<T>,
state: TableState<T>,
ref: RefObject<HTMLElement>
): GridRowAriauseTableColumnHeader<T>(
props: ColumnHeaderProps,
state: TableState<T>,
ref: RefObject<HTMLElement>
): ColumnHeaderAriauseTableRow<T>(
props: GridRowProps<T>,
state: TableState<T>,
ref: RefObject<HTMLElement>
): GridRowAriauseTableCell<T>(
props: TableCellProps,
state: TableState<T>,
ref: RefObject<HTMLElement>
): TableCellAriauseTableSelectionCheckbox<T>(
(props: SelectionCheckboxProps,
, state: TableState<T>
)): SelectionCheckboxAriauseTableSelectAllCheckbox<T>(
(state: TableState<T>
)): SelectAllCheckboxAriaFeatures#
A table can be built using the <table>, <tr>,
<td>, and other table specific HTML elements, but is very limited in functionality especially when it comes to user interactions.
HTML tables are meant for static content, rather than tables with rich interactions like focusable elements within cells, keyboard navigation, row selection, sorting, etc.
useTable helps achieve accessible and interactive table components that can be styled as needed.
- Exposed to assistive technology as a
gridusing ARIA - Keyboard navigation between columns, rows, cells, and in-cell focusable elements via the arrow keys
- Single, multiple, or no row selection via mouse, touch, or keyboard interactions
- Support for disabled rows, which cannot be selected
- Optional support for checkboxes in each row for selection, as well as in the header to select all rows
- Column sorting support
- Async loading, infinite scrolling, filtering, and sorting support
- Support for column groups via nested columns
- Typeahead to allow focusing rows by typing text
- Automatic scrolling support during keyboard navigation
- Labeling support for accessibility
- Support for marking columns as row headers, which will be read when navigating the rows with a screen reader
- Ensures that selections are announced using an ARIA live region
- Support for using HTML table elements, or custom element types (e.g.
<div>) for layout flexibility - Virtualized scrolling support for performance with large tables
Anatomy#
A table consists of a container element, with columns and rows of cells containing data inside. The cells within a table may contain focusable elements or plain text content. If the table supports row selection, each row can optionally include a selection checkbox in the first column. Additionally, a "select all" checkbox is displayed as the first column header if the table supports multiple row selection.
The useTable, useTableRow, useTableCell, and useTableColumnHeader hooks handle keyboard, mouse, and other interactions to support
row selection, in table navigation, and overall focus behavior. Those hooks, along with useTableRowGroup and useTableHeaderRow, also handle exposing the table and its contents
to assistive technology using ARIA. useTableSelectAllCheckbox and useTableSelectionCheckbox handle row selection and associating each checkbox with its respective rows
for assistive technology. Each of these hooks returns props to be spread onto the appropriate HTML element.
State is managed by the useTableState
hook from @react-stately/table. The state object should be passed as an option to each of the above hooks where applicable.
Note that an aria-label or aria-labelledby must be passed to the table to identify the element to assistive technology.
State management#
useTable requires knowledge of the rows, cells, and columns in the table in order to handle keyboard
navigation and other interactions. It does this using
the Collection
interface, which is a generic interface to access sequential unique keyed data. You can
implement this interface yourself, e.g. by using a prop to pass a list of item objects,
but useTableState from
@react-stately/table implements a JSX based interface for building collections instead.
See Collection Components for more information,
and Collection Interface for internal details.
Data is defined using the TableHeader, Column, TableBody, Row, and Cell components, which support both static and dynamic data.
See the examples in the usage section below for details on how to use these components.
In addition, useTableState
manages the state necessary for multiple selection and exposes
a SelectionManager,
which makes use of the collection to provide an interface to update the selection state.
For more information, see Selection.
Example#
Tables are complex collection components that are built up from many child elements
including columns, rows, and cells. In this example, we'll use the standard HTML table elements along with hooks from React
Aria for each child. You may also use other elements like <div> to render these components as appropriate.
Since there are many pieces, we'll walk through each of them one by one.
The useTable hook will be used to render the outer most table element. It uses
the useTableState hook to construct the table's collection of rows and columns,
and manage state such as the focused row/cell, selection, and sort column/direction. We'll use the collection to iterate through
the rows and cells of the table and render the relevant components, which we'll define below.
import {
Cell
Column
Row
TableBody
TableHeader
useTableState
} from '@react-stately/table';
import {mergeProps} from '@react-aria/utils';
import {useRef} from 'react';
import {useFocusRing} from '@react-aria/focus';
function Table(props) {
let state = useTableState({
...props
showSelectionCheckboxes: propsselectionMode === 'multiple'
});
let ref = useRef();
let {collection} = state;
let {gridProps} = useTable(props state ref);
return (
<table ...gridProps ref=ref style={borderCollapse: 'collapse'}>
<TableRowGroup
type="thead"
style={
borderBottom: '2px solid var(--spectrum-global-color-gray-800)'
}>
collectionheaderRowsmap((headerRow) => (
<TableHeaderRow key=headerRowkey item=headerRow state=state>
[...headerRowchildNodes]map((column) =>
columnpropsisSelectionCell ? (
<TableSelectAllCell
key=columnkey
column=column
state=state
/>
) : (
<TableColumnHeader
key=columnkey
column=column
state=state
/>
)
)
</TableHeaderRow>
))
</TableRowGroup>
<TableRowGroup type="tbody">
[...collectionbodychildNodes]map((row) => (
<TableRow key=rowkey item=row state=state>
[...rowchildNodes]map((cell) =>
cellpropsisSelectionCell ? (
<TableCheckboxCell key=cellkey cell=cell state=state />
) : (
<TableCell key=cellkey cell=cell state=state />
)
)
</TableRow>
))
</TableRowGroup>
</table>
);
}
import {
Cell
Column
Row
TableBody
TableHeader
useTableState
} from '@react-stately/table';
import {mergeProps} from '@react-aria/utils';
import {useRef} from 'react';
import {useFocusRing} from '@react-aria/focus';
function Table(props) {
let state = useTableState({
...props
showSelectionCheckboxes:
propsselectionMode === 'multiple'
});
let ref = useRef();
let {collection} = state;
let {gridProps} = useTable(props state ref);
return (
<table
...gridProps
ref=ref
style={borderCollapse: 'collapse'}>
<TableRowGroup
type="thead"
style={
borderBottom:
'2px solid var(--spectrum-global-color-gray-800)'
}>
collectionheaderRowsmap((headerRow) => (
<TableHeaderRow
key=headerRowkey
item=headerRow
state=state>
[...headerRowchildNodes]map((column) =>
columnpropsisSelectionCell ? (
<TableSelectAllCell
key=columnkey
column=column
state=state
/>
) : (
<TableColumnHeader
key=columnkey
column=column
state=state
/>
)
)
</TableHeaderRow>
))
</TableRowGroup>
<TableRowGroup type="tbody">
[...collectionbodychildNodes]map((row) => (
<TableRow key=rowkey item=row state=state>
[...rowchildNodes]map((cell) =>
cellpropsisSelectionCell ? (
<TableCheckboxCell
key=cellkey
cell=cell
state=state
/>
) : (
<TableCell
key=cellkey
cell=cell
state=state
/>
)
)
</TableRow>
))
</TableRowGroup>
</table>
);
}
import {
Cell
Column
Row
TableBody
TableHeader
useTableState
} from '@react-stately/table';
import {mergeProps} from '@react-aria/utils';
import {useRef} from 'react';
import {useFocusRing} from '@react-aria/focus';
function Table(props) {
let state = useTableState(
{
...props
showSelectionCheckboxes:
propsselectionMode ===
'multiple'
}
);
let ref = useRef();
let {
collection
} = state;
let {
gridProps
} = useTable(
props
state
ref
);
return (
<table
...gridProps
ref=ref
style={
borderCollapse:
'collapse'
}>
<TableRowGroup
type="thead"
style={
borderBottom:
'2px solid var(--spectrum-global-color-gray-800)'
}>
collectionheaderRowsmap(
(
headerRow
) => (
<TableHeaderRow
key=
headerRowkey
item=
headerRow
state=
state
>
[
...headerRowchildNodes
]map(
(
column
) =>
column
props
isSelectionCell ? (
<TableSelectAllCell
key=
columnkey
column=
column
state=
state
/>
) : (
<TableColumnHeader
key=
columnkey
column=
column
state=
state
/>
)
)
</TableHeaderRow>
)
)
</TableRowGroup>
<TableRowGroup type="tbody">
[
...collection
body
childNodes
]map((row) => (
<TableRow
key=rowkey
item=row
state=
state
>
[
...rowchildNodes
]map(
(cell) =>
cell
props
isSelectionCell ? (
<TableCheckboxCell
key=
cellkey
cell=
cell
state=
state
/>
) : (
<TableCell
key=
cellkey
cell=
cell
state=
state
/>
)
)
</TableRow>
))
</TableRowGroup>
</table>
);
}
Table header#
A useTableRowGroup hook will be used to group the rows in the table header and table body. In this example,
we're using HTML table elements, so this will be either a <thead> or <tbody> element, as passed from the
above Table component via the type prop.
function TableRowGroup({type: Element style children}) {
let {rowGroupProps} = useTableRowGroup();
return (
<Element ...rowGroupProps style=style>
children
</Element>
);
}
function TableRowGroup({type: Element style children}) {
let {rowGroupProps} = useTableRowGroup();
return (
<Element ...rowGroupProps style=style>
children
</Element>
);
}
function TableRowGroup({
type: Element
style
children
}) {
let {
rowGroupProps
} = useTableRowGroup();
return (
<Element
...rowGroupProps
style=style>
children
</Element>
);
}
The useTableHeaderRow hook will be used to render a header row. Header rows are similar to other rows,
but they don't support user interaction like selection. In this example, there's only one header
row, but there could be multiple in the case of nested columns. See the example below for details.
function TableHeaderRow({item state children}) {
let ref = useRef();
let {rowProps} = useTableHeaderRow({node: item} state ref);
return (
<tr ...rowProps ref=ref>
children
</tr>
);
}
function TableHeaderRow({item state children}) {
let ref = useRef();
let {rowProps} = useTableHeaderRow(
{node: item}
state
ref
);
return (
<tr ...rowProps ref=ref>
children
</tr>
);
}
function TableHeaderRow({
item
state
children
}) {
let ref = useRef();
let {
rowProps
} = useTableHeaderRow(
{node: item}
state
ref
);
return (
<tr
...rowProps
ref=ref>
children
</tr>
);
}
The useTableColumnHeader hook will be used to render each column header. Column headers act as a label
for all of the cells in that column, and can optionally support user interaction to sort by the column
and change the sort order.
The allowsSorting property of the column object can be used to determine
if the column supports sorting at all.
The sortDescriptor object stored in the state object indicates which column the table is currently sorted by,
as well as the sort direction (ascending or descending). This is used to render an arrow icon to visually
indicate the sort direction. When not sorted by this column, we use visibility: hidden to ensure that
we reserve space for this icon at all times. That way the table's layout doesn't shift when we change the
column we're sorting by. See the example below of all of this in action.
Finally, we use the useFocusRing hook to ensure that a focus ring is rendered when
the cell is navigated to with the keyboard.
function TableColumnHeader({column state}) {
let ref = useRef();
let {columnHeaderProps} = useTableColumnHeader({node: column} state ref);
let {isFocusVisible focusProps} = useFocusRing();
let arrowIcon = statesortDescriptor?direction === 'ascending' ? '▲' : '▼';
return (
<th
...mergeProps(columnHeaderProps focusProps)
colSpan=columncolspan
style={
textAlign: columncolspan > 1 ? 'center' : 'left'
padding: '5px 10px'
outline: isFocusVisible ? '2px solid orange' : 'none'
cursor: 'default'
}
ref=ref>
columnrendered
columnpropsallowsSorting && (
<span
aria-hidden="true"
style={
padding: '0 2px'
visibility:
statesortDescriptor?column === columnkey ? 'visible' : 'hidden'
}>
arrowIcon
</span>
)
</th>
);
}
function TableColumnHeader({column state}) {
let ref = useRef();
let {columnHeaderProps} = useTableColumnHeader(
{node: column}
state
ref
);
let {isFocusVisible focusProps} = useFocusRing();
let arrowIcon =
statesortDescriptor?direction === 'ascending'
? '▲'
: '▼';
return (
<th
...mergeProps(columnHeaderProps focusProps)
colSpan=columncolspan
style={
textAlign: columncolspan > 1 ? 'center' : 'left'
padding: '5px 10px'
outline: isFocusVisible
? '2px solid orange'
: 'none'
cursor: 'default'
}
ref=ref>
columnrendered
columnpropsallowsSorting && (
<span
aria-hidden="true"
style={
padding: '0 2px'
visibility:
statesortDescriptor?column === columnkey
? 'visible'
: 'hidden'
}>
arrowIcon
</span>
)
</th>
);
}
function TableColumnHeader({
column
state
}) {
let ref = useRef();
let {
columnHeaderProps
} = useTableColumnHeader(
{node: column}
state
ref
);
let {
isFocusVisible
focusProps
} = useFocusRing();
let arrowIcon =
statesortDescriptor
?direction ===
'ascending'
? '▲'
: '▼';
return (
<th
...mergeProps(
columnHeaderProps
focusProps
)
colSpan=
columncolspan
style={
textAlign:
columncolspan >
1
? 'center'
: 'left'
padding:
'5px 10px'
outline: isFocusVisible
? '2px solid orange'
: 'none'
cursor: 'default'
}
ref=ref>
columnrendered
columnprops
allowsSorting && (
<span
aria-hidden="true"
style={
padding:
'0 2px'
visibility:
state
sortDescriptor
?column ===
columnkey
? 'visible'
: 'hidden'
}>
arrowIcon
</span>
)
</th>
);
}
Table body#
Now that we've covered the table header, let's move on to the body. We'll use
the useTableRow hook to render each row in the table.
Table rows can be focused and navigated to using the keyboard via the arrow keys. In addition, table rows
can optionally support selection via mouse, touch, or keyboard. Clicking, tapping, or pressing the Space
key anywhere in the row selects it.
We'll use the SelectionManager object exposed
by the state to determine if a row is selected, and render a pink background if so. We'll also use the useFocusRing
hook to render a focus ring when the user navigates to the row with the keyboard.
function TableRow({item children state}) {
let ref = useRef();
let isSelected = stateselectionManagerisSelected(itemkey);
let {rowProps} = useTableRow({node: item} state ref);
let {isFocusVisible focusProps} = useFocusRing();
return (
<tr
style={
background: isSelected
? 'blueviolet'
: itemindex % 2
? 'var(--spectrum-alias-highlight-hover)'
: 'none'
color: isSelected ? 'white' : null
outline: isFocusVisible ? '2px solid orange' : 'none'
}
...mergeProps(rowProps focusProps)
ref=ref>
children
</tr>
);
}
function TableRow({item children state}) {
let ref = useRef();
let isSelected = stateselectionManagerisSelected(
itemkey
);
let {rowProps} = useTableRow({node: item} state ref);
let {isFocusVisible focusProps} = useFocusRing();
return (
<tr
style={
background: isSelected
? 'blueviolet'
: itemindex % 2
? 'var(--spectrum-alias-highlight-hover)'
: 'none'
color: isSelected ? 'white' : null
outline: isFocusVisible
? '2px solid orange'
: 'none'
}
...mergeProps(rowProps focusProps)
ref=ref>
children
</tr>
);
}
function TableRow({
item
children
state
}) {
let ref = useRef();
let isSelected = stateselectionManagerisSelected(
itemkey
);
let {
rowProps
} = useTableRow(
{node: item}
state
ref
);
let {
isFocusVisible
focusProps
} = useFocusRing();
return (
<tr
style={
background: isSelected
? 'blueviolet'
: itemindex %
2
? 'var(--spectrum-alias-highlight-hover)'
: 'none'
color: isSelected
? 'white'
: null
outline: isFocusVisible
? '2px solid orange'
: 'none'
}
...mergeProps(
rowProps
focusProps
)
ref=ref>
children
</tr>
);
}
Finally, we'll use the useTableCell hook to render each cell.
Users can use the left and right arrow keys to navigate to each cell in a row, as well as any focusable elements
within a cell. This is indicated by the focus ring, as created with the useFocusRing
hook. The cell's contents are available in the rendered property of the cell Node
object.
function TableCell({cell state}) {
let ref = useRef();
let {gridCellProps} = useTableCell({node: cell} state ref);
let {isFocusVisible focusProps} = useFocusRing();
return (
<td
...mergeProps(gridCellProps focusProps)
style={
padding: '5px 10px'
outline: isFocusVisible ? '2px solid orange' : 'none'
cursor: 'default'
}
ref=ref>
cellrendered
</td>
);
}
function TableCell({cell state}) {
let ref = useRef();
let {gridCellProps} = useTableCell(
{node: cell}
state
ref
);
let {isFocusVisible focusProps} = useFocusRing();
return (
<td
...mergeProps(gridCellProps focusProps)
style={
padding: '5px 10px'
outline: isFocusVisible
? '2px solid orange'
: 'none'
cursor: 'default'
}
ref=ref>
cellrendered
</td>
);
}
function TableCell({
cell
state
}) {
let ref = useRef();
let {
gridCellProps
} = useTableCell(
{node: cell}
state
ref
);
let {
isFocusVisible
focusProps
} = useFocusRing();
return (
<td
...mergeProps(
gridCellProps
focusProps
)
style={
padding:
'5px 10px'
outline: isFocusVisible
? '2px solid orange'
: 'none'
cursor: 'default'
}
ref=ref>
cellrendered
</td>
);
}
With all of the above components in place, we can render an example of our Table in action. This example shows a static collection, where all of the data is hard coded. See below for examples of using this Table component with dynamic collections (e.g. from a server).
Try tabbing into the table and navigating using the arrow keys.
<Table
aria-label="Example static collection table"
style={height: '210px' maxWidth: '400px'}>
<TableHeader>
<Column>Name</Column>
<Column>Type</Column>
<Column>Date Modified</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Games</Cell>
<Cell>File folder</Cell>
<Cell>6/7/2020</Cell>
</Row>
<Row>
<Cell>Program Files</Cell>
<Cell>File folder</Cell>
<Cell>4/7/2021</Cell>
</Row>
<Row>
<Cell>bootmgr</Cell>
<Cell>System file</Cell>
<Cell>11/20/2010</Cell>
</Row>
<Row>
<Cell>log.txt</Cell>
<Cell>Text Document</Cell>
<Cell>1/18/2016</Cell>
</Row>
</TableBody>
</Table><Table
aria-label="Example static collection table"
style={height: '210px' maxWidth: '400px'}>
<TableHeader>
<Column>Name</Column>
<Column>Type</Column>
<Column>Date Modified</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Games</Cell>
<Cell>File folder</Cell>
<Cell>6/7/2020</Cell>
</Row>
<Row>
<Cell>Program Files</Cell>
<Cell>File folder</Cell>
<Cell>4/7/2021</Cell>
</Row>
<Row>
<Cell>bootmgr</Cell>
<Cell>System file</Cell>
<Cell>11/20/2010</Cell>
</Row>
<Row>
<Cell>log.txt</Cell>
<Cell>Text Document</Cell>
<Cell>1/18/2016</Cell>
</Row>
</TableBody>
</Table><Table
aria-label="Example static collection table"
style={
height: '210px'
maxWidth: '400px'
}>
<TableHeader>
<Column>
Name
</Column>
<Column>
Type
</Column>
<Column>
Date Modified
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>
Games
</Cell>
<Cell>
File folder
</Cell>
<Cell>
6/7/2020
</Cell>
</Row>
<Row>
<Cell>
Program Files
</Cell>
<Cell>
File folder
</Cell>
<Cell>
4/7/2021
</Cell>
</Row>
<Row>
<Cell>
bootmgr
</Cell>
<Cell>
System file
</Cell>
<Cell>
11/20/2010
</Cell>
</Row>
<Row>
<Cell>
log.txt
</Cell>
<Cell>
Text Document
</Cell>
<Cell>
1/18/2016
</Cell>
</Row>
</TableBody>
</Table>Adding selection#
Next, let's add support for selection. For multiple selection, we'll want to add a column of checkboxes to the left
of the table to allow the user to select rows. This is done using the useTableSelectionCheckbox
hook. It is passed the parentKey of the cell, which refers to the row the cell is contained within. When the user
checks or unchecks the checkbox, the row will be added or removed from the Table's selection.
In this example, we pass the result of the checkboxProps into the useCheckbox
hook and render an <input> element directly, but it's likely you'll have a Checkbox component in your component library that uses these hooks already.
See the useCheckbox docs for more information.
import {useToggleState} from '@react-stately/toggle';
import {useCheckbox} from '@react-aria/checkbox';
function TableCheckboxCell({cell state}) {
let ref = useRef();
let {gridCellProps} = useTableCell({node: cell} state ref);
let {checkboxProps} = useTableSelectionCheckbox({key: cellparentKey} state);
let inputRef = useRef(null);
let {inputProps} = useCheckbox(
checkboxProps
useToggleState(checkboxProps)
inputRef
);
return (
<td ...gridCellProps ref=ref>
<input ...inputProps />
</td>
);
}
import {useToggleState} from '@react-stately/toggle';
import {useCheckbox} from '@react-aria/checkbox';
function TableCheckboxCell({cell state}) {
let ref = useRef();
let {gridCellProps} = useTableCell(
{node: cell}
state
ref
);
let {checkboxProps} = useTableSelectionCheckbox(
{key: cellparentKey}
state
);
let inputRef = useRef(null);
let {inputProps} = useCheckbox(
checkboxProps
useToggleState(checkboxProps)
inputRef
);
return (
<td ...gridCellProps ref=ref>
<input ...inputProps />
</td>
);
}
import {useToggleState} from '@react-stately/toggle';
import {useCheckbox} from '@react-aria/checkbox';
function TableCheckboxCell({
cell
state
}) {
let ref = useRef();
let {
gridCellProps
} = useTableCell(
{node: cell}
state
ref
);
let {
checkboxProps
} = useTableSelectionCheckbox(
{
key: cellparentKey
}
state
);
let inputRef = useRef(
null
);
let {
inputProps
} = useCheckbox(
checkboxProps
useToggleState(
checkboxProps
)
inputRef
);
return (
<td
...gridCellProps
ref=ref>
<input
...inputProps
/>
</td>
);
}
We also want the user to be able to select all rows in the table at once. This is possible using the ⌘ Cmd + A
keyboard shortcut, but we'll also add a checkbox into the table header to do this and represent the selection state visually.
This is done using the useTableSelectAllCheckbox hook. When all rows are selected,
the checkbox will be shown as checked, and when only some rows are selected, the checkbox will be rendered in an indeterminate state.
The user can check or uncheck the checkbox to select all or clear the selection, respectively.
Note: Always ensure that the cell has accessible content, even when the checkbox is hidden (i.e. in single selection mode). The VisuallyHidden component can be used to do this.
function TableSelectAllCell({column state}) {
let ref = useRef();
let isSingleSelectionMode = stateselectionManagerselectionMode === 'single';
let {columnHeaderProps} = useTableColumnHeader({node: column} state ref);
let {checkboxProps} = useTableSelectAllCheckbox(state);
let inputRef = useRef(null);
let {inputProps} = useCheckbox(
checkboxProps
useToggleState(checkboxProps)
inputRef
);
return (
<th ...columnHeaderProps ref=ref>
stateselectionManagerselectionMode === 'single' ? (
<VisuallyHidden>inputProps['aria-label']</VisuallyHidden>
) : (
<input ...inputProps ref=inputRef />
)
</th>
);
}
function TableSelectAllCell({column state}) {
let ref = useRef();
let isSingleSelectionMode =
stateselectionManagerselectionMode === 'single';
let {columnHeaderProps} = useTableColumnHeader(
{node: column}
state
ref
);
let {checkboxProps} = useTableSelectAllCheckbox(state);
let inputRef = useRef(null);
let {inputProps} = useCheckbox(
checkboxProps
useToggleState(checkboxProps)
inputRef
);
return (
<th ...columnHeaderProps ref=ref>
stateselectionManagerselectionMode === 'single' ? (
<VisuallyHidden>
inputProps['aria-label']
</VisuallyHidden>
) : (
<input ...inputProps ref=inputRef />
)
</th>
);
}
function TableSelectAllCell({
column
state
}) {
let ref = useRef();
let isSingleSelectionMode =
state
selectionManager
selectionMode ===
'single';
let {
columnHeaderProps
} = useTableColumnHeader(
{node: column}
state
ref
);
let {
checkboxProps
} = useTableSelectAllCheckbox(
state
);
let inputRef = useRef(
null
);
let {
inputProps
} = useCheckbox(
checkboxProps
useToggleState(
checkboxProps
)
inputRef
);
return (
<th
...columnHeaderProps
ref=ref>
state
selectionManager
selectionMode ===
'single' ? (
<VisuallyHidden>
inputProps[
'aria-label'
]
</VisuallyHidden>
) : (
<input
...inputProps
ref=inputRef
/>
)
</th>
);
}
The following example shows how to enable multiple selection support using the Table component we built above.
It's as simple as setting the selectionMode prop to "multiple". Because we set the showSelectionCheckboxes
option of useTableState to true when multiple selection is enabled, an extra column for these checkboxes is
automatically added for us.
<Table aria-label="Table with selection" selectionMode="multiple">
<TableHeader>
<Column>Name</Column>
<Column>Type</Column>
<Column>Level</Column>
</TableHeader>
<TableBody>
<Row key="1">
<Cell>Charizard</Cell>
<Cell>Fire, Flying</Cell>
<Cell>67</Cell>
</Row>
<Row key="2">
<Cell>Blastoise</Cell>
<Cell>Water</Cell>
<Cell>56</Cell>
</Row>
<Row key="3">
<Cell>Venusaur</Cell>
<Cell>Grass, Poison</Cell>
<Cell>83</Cell>
</Row>
<Row key="4">
<Cell>Pikachu</Cell>
<Cell>Electric</Cell>
<Cell>100</Cell>
</Row>
</TableBody>
</Table><Table
aria-label="Table with selection"
selectionMode="multiple">
<TableHeader>
<Column>Name</Column>
<Column>Type</Column>
<Column>Level</Column>
</TableHeader>
<TableBody>
<Row key="1">
<Cell>Charizard</Cell>
<Cell>Fire, Flying</Cell>
<Cell>67</Cell>
</Row>
<Row key="2">
<Cell>Blastoise</Cell>
<Cell>Water</Cell>
<Cell>56</Cell>
</Row>
<Row key="3">
<Cell>Venusaur</Cell>
<Cell>Grass, Poison</Cell>
<Cell>83</Cell>
</Row>
<Row key="4">
<Cell>Pikachu</Cell>
<Cell>Electric</Cell>
<Cell>100</Cell>
</Row>
</TableBody>
</Table><Table
aria-label="Table with selection"
selectionMode="multiple">
<TableHeader>
<Column>
Name
</Column>
<Column>
Type
</Column>
<Column>
Level
</Column>
</TableHeader>
<TableBody>
<Row key="1">
<Cell>
Charizard
</Cell>
<Cell>
Fire, Flying
</Cell>
<Cell>67</Cell>
</Row>
<Row key="2">
<Cell>
Blastoise
</Cell>
<Cell>
Water
</Cell>
<Cell>56</Cell>
</Row>
<Row key="3">
<Cell>
Venusaur
</Cell>
<Cell>
Grass, Poison
</Cell>
<Cell>83</Cell>
</Row>
<Row key="4">
<Cell>
Pikachu
</Cell>
<Cell>
Electric
</Cell>
<Cell>100</Cell>
</Row>
</TableBody>
</Table>And that's it! We now have a fully interactive table component that can support keyboard navigation, single or multiple selection, as well as column sorting. In addition, it is fully accessible for screen readers and other assistive technology. See below for more examples of how to use the Table component that we've built.
Usage#
Dynamic collections#
So far, our examples have shown static collections, where the data is hard coded. Dynamic collections, as shown below, can be used when the table data comes from an external data source such as an API, or updates over time. In the example below, both the columns and the rows are provided to the table via a render function. You can also make the columns static and only the rows dynamic.
function ExampleTable(props) {
let columns = [
{name: 'Name' key: 'name'}
{name: 'Type' key: 'type'}
{name: 'Date Modified' key: 'date'}
];
let rows = [
{id: 1 name: 'Games' date: '6/7/2020' type: 'File folder'}
{id: 2 name: 'Program Files' date: '4/7/2021' type: 'File folder'}
{id: 3 name: 'bootmgr' date: '11/20/2010' type: 'System file'}
{id: 4 name: 'log.txt' date: '1/18/2016' type: 'Text Document'}
];
return (
<Table aria-label="Example dynamic collection table" ...props>
<TableHeader columns=columns>
(column) => <Column>columnname</Column>
</TableHeader>
<TableBody items=rows>
(item) => <Row>(columnKey) => <Cell>item[columnKey]</Cell></Row>
</TableBody>
</Table>
);
}
function ExampleTable(props) {
let columns = [
{name: 'Name' key: 'name'}
{name: 'Type' key: 'type'}
{name: 'Date Modified' key: 'date'}
];
let rows = [
{
id: 1
name: 'Games'
date: '6/7/2020'
type: 'File folder'
}
{
id: 2
name: 'Program Files'
date: '4/7/2021'
type: 'File folder'
}
{
id: 3
name: 'bootmgr'
date: '11/20/2010'
type: 'System file'
}
{
id: 4
name: 'log.txt'
date: '1/18/2016'
type: 'Text Document'
}
];
return (
<Table
aria-label="Example dynamic collection table"
...props>
<TableHeader columns=columns>
(column) => <Column>columnname</Column>
</TableHeader>
<TableBody items=rows>
(item) => (
<Row>
(columnKey) => <Cell>item[columnKey]</Cell>
</Row>
)
</TableBody>
</Table>
);
}
function ExampleTable(
props
) {
let columns = [
{
name: 'Name'
key: 'name'
}
{
name: 'Type'
key: 'type'
}
{
name:
'Date Modified'
key: 'date'
}
];
let rows = [
{
id: 1
name: 'Games'
date: '6/7/2020'
type: 'File folder'
}
{
id: 2
name:
'Program Files'
date: '4/7/2021'
type: 'File folder'
}
{
id: 3
name: 'bootmgr'
date: '11/20/2010'
type: 'System file'
}
{
id: 4
name: 'log.txt'
date: '1/18/2016'
type:
'Text Document'
}
];
return (
<Table
aria-label="Example dynamic collection table"
...props>
<TableHeader
columns=
columns
>
(column) => (
<Column>
columnname
</Column>
)
</TableHeader>
<TableBody
items=rows>
(item) => (
<Row>
(
columnKey
) => (
<Cell>
item[
columnKey
]
</Cell>
)
</Row>
)
</TableBody>
</Table>
);
}
Single selection#
By default, useTableState doesn't allow row selection but this can be enabled using the selectionMode prop. Use defaultSelectedKeys to provide a default set of selected rows.
Note that the value of the selected keys must match the key prop of the row.
The example below enables single selection mode, and uses defaultSelectedKeys to select the row with key equal to "2".
A user can click on a different row to change the selection, or click on the same row again to deselect it entirely.
// Using the example above
<ExampleTable selectionMode="single" defaultSelectedKeys=[2] />// Using the example above
<ExampleTable
selectionMode="single"
defaultSelectedKeys=[2]
/>// Using the example above
<ExampleTable
selectionMode="single"
defaultSelectedKeys=[
2
]
/>Multiple selection#
Multiple selection can be enabled by setting selectionMode to multiple.
// Using the example above
<ExampleTable selectionMode="multiple" defaultSelectedKeys=[2 4] />// Using the example above
<ExampleTable
selectionMode="multiple"
defaultSelectedKeys=[2 4]
/>// Using the example above
<ExampleTable
selectionMode="multiple"
defaultSelectedKeys=[
2
4
]
/>Disallow empty selection#
Table also supports a disallowEmptySelection prop which forces the user to have at least one row in the Table selected at all times.
In this mode, if a single row is selected and the user presses it, it will not be deselected.
// Using the example above
<ExampleTable
selectionMode="single"
defaultSelectedKeys=[2]
disallowEmptySelection
/>// Using the example above
<ExampleTable
selectionMode="single"
defaultSelectedKeys=[2]
disallowEmptySelection
/>// Using the example above
<ExampleTable
selectionMode="single"
defaultSelectedKeys=[
2
]
disallowEmptySelection
/>Controlled selection#
To programmatically control row selection, use the selectedKeys prop paired with the onSelectionChange callback. The key prop from the selected rows will
be passed into the callback when the row is pressed, allowing you to update state accordingly.
function PokemonTable(props) {
let columns = [
{name: 'Name' uid: 'name'}
{name: 'Type' uid: 'type'}
{name: 'Level' uid: 'level'}
];
let rows = [
{id: 1 name: 'Charizard' type: 'Fire, Flying' level: '67'}
{id: 2 name: 'Blastoise' type: 'Water' level: '56'}
{id: 3 name: 'Venusaur' type: 'Grass, Poison' level: '83'}
{id: 4 name: 'Pikachu' type: 'Electric' level: '100'}
];
let [selectedKeys setSelectedKeys] = ReactuseState(new Set([2]));
return (
<Table
aria-label="Table with controlled selection"
selectionMode="multiple"
selectedKeys=selectedKeys
onSelectionChange=setSelectedKeys
...props>
<TableHeader columns=columns>
(column) => <Column key=columnuid>columnname</Column>
</TableHeader>
<TableBody items=rows>
(item) => <Row>(columnKey) => <Cell>item[columnKey]</Cell></Row>
</TableBody>
</Table>
);
}
function PokemonTable(props) {
let columns = [
{name: 'Name' uid: 'name'}
{name: 'Type' uid: 'type'}
{name: 'Level' uid: 'level'}
];
let rows = [
{
id: 1
name: 'Charizard'
type: 'Fire, Flying'
level: '67'
}
{id: 2 name: 'Blastoise' type: 'Water' level: '56'}
{
id: 3
name: 'Venusaur'
type: 'Grass, Poison'
level: '83'
}
{id: 4 name: 'Pikachu' type: 'Electric' level: '100'}
];
let [selectedKeys setSelectedKeys] = ReactuseState(
new Set([2])
);
return (
<Table
aria-label="Table with controlled selection"
selectionMode="multiple"
selectedKeys=selectedKeys
onSelectionChange=setSelectedKeys
...props>
<TableHeader columns=columns>
(column) => (
<Column key=columnuid>columnname</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row>
(columnKey) => <Cell>item[columnKey]</Cell>
</Row>
)
</TableBody>
</Table>
);
}
function PokemonTable(
props
) {
let columns = [
{
name: 'Name'
uid: 'name'
}
{
name: 'Type'
uid: 'type'
}
{
name: 'Level'
uid: 'level'
}
];
let rows = [
{
id: 1
name: 'Charizard'
type:
'Fire, Flying'
level: '67'
}
{
id: 2
name: 'Blastoise'
type: 'Water'
level: '56'
}
{
id: 3
name: 'Venusaur'
type:
'Grass, Poison'
level: '83'
}
{
id: 4
name: 'Pikachu'
type: 'Electric'
level: '100'
}
];
let [
selectedKeys
setSelectedKeys
] = ReactuseState(
new Set([2])
);
return (
<Table
aria-label="Table with controlled selection"
selectionMode="multiple"
selectedKeys=
selectedKeys
onSelectionChange=
setSelectedKeys
...props>
<TableHeader
columns=
columns
>
(column) => (
<Column
key=
columnuid
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=rows>
(item) => (
<Row>
(
columnKey
) => (
<Cell>
item[
columnKey
]
</Cell>
)
</Row>
)
</TableBody>
</Table>
);
}
Disabled rows#
You can disable specific rows by providing an array of keys to useTableState via the disabledKeys prop. This will prevent rows from being selectable as shown in the example below.
Note that you are responsible for the styling of disabled rows, however, the selection checkbox will be automatically disabled.
// Using the same table as above
<PokemonTable selectionMode="multiple" disabledKeys=[3] />// Using the same table as above
<PokemonTable
selectionMode="multiple"
disabledKeys=[3]
/>// Using the same table as above
<PokemonTable
selectionMode="multiple"
disabledKeys=[3]
/>Sorting#
Table supports sorting its data when a column header is pressed. To designate that a Column should support sorting, provide it with
the allowsSorting prop. The Table accepts a sortDescriptor prop that defines the current column key to sort by and the sort direction (ascending/descending).
When the user presses a sortable column header, the column's key and sort direction is passed into the onSortChange callback, allowing you to update
the sortDescriptor appropriately.
This example performs client side sorting by passing a sort function to the useAsyncList hook.
See the docs for more information on how to perform server side sorting.
import {useAsyncList} from '@react-stately/data';
function AsyncSortTable() {
let list = useAsyncList({
async load({signal}) {
let res = await fetch(`https://swapi.dev/api/people/?search` {signal});
let json = await resjson();
return {
items: jsonresults
};
}
async sort({items sortDescriptor}) {
return {
items: itemssort((a b) => {
let first = a[sortDescriptorcolumn];
let second = b[sortDescriptorcolumn];
let cmp =
(parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1;
if (sortDescriptordirection === 'descending') {
cmp *= -1;
}
return cmp;
})
};
}
});
return (
<Table
aria-label="Example table with client side sorting"
sortDescriptor=listsortDescriptor
onSortChange=listsort>
<TableHeader>
<Column key="name" allowsSorting>
Name
</Column>
<Column key="height" allowsSorting>
Height
</Column>
<Column key="mass" allowsSorting>
Mass
</Column>
<Column key="birth_year" allowsSorting>
Birth Year
</Column>
</TableHeader>
<TableBody items=listitems>
(item) => (
<Row key=itemname>
(columnKey) => <Cell>item[columnKey]</Cell>
</Row>
)
</TableBody>
</Table>
);
}
import {useAsyncList} from '@react-stately/data';
function AsyncSortTable() {
let list = useAsyncList({
async load({signal}) {
let res = await fetch(
`https://swapi.dev/api/people/?search`
{signal}
);
let json = await resjson();
return {
items: jsonresults
};
}
async sort({items sortDescriptor}) {
return {
items: itemssort((a b) => {
let first = a[sortDescriptorcolumn];
let second = b[sortDescriptorcolumn];
let cmp =
(parseInt(first) || first) <
(parseInt(second) || second)
? -1
: 1;
if (sortDescriptordirection === 'descending') {
cmp *= -1;
}
return cmp;
})
};
}
});
return (
<Table
aria-label="Example table with client side sorting"
sortDescriptor=listsortDescriptor
onSortChange=listsort>
<TableHeader>
<Column key="name" allowsSorting>
Name
</Column>
<Column key="height" allowsSorting>
Height
</Column>
<Column key="mass" allowsSorting>
Mass
</Column>
<Column key="birth_year" allowsSorting>
Birth Year
</Column>
</TableHeader>
<TableBody items=listitems>
(item) => (
<Row key=itemname>
(columnKey) => <Cell>item[columnKey]</Cell>
</Row>
)
</TableBody>
</Table>
);
}
import {useAsyncList} from '@react-stately/data';
function AsyncSortTable() {
let list = useAsyncList(
{
async load({
signal
}) {
let res = await fetch(
`https://swapi.dev/api/people/?search`
{signal}
);
let json = await resjson();
return {
items:
jsonresults
};
}
async sort({
items
sortDescriptor
}) {
return {
items: itemssort(
(a b) => {
let first =
a[
sortDescriptor
column
];
let second =
b[
sortDescriptor
column
];
let cmp =
(parseInt(
first
) ||
first) <
(parseInt(
second
) ||
second)
? -1
: 1;
if (
sortDescriptordirection ===
'descending'
) {
cmp *= -1;
}
return cmp;
}
)
};
}
}
);
return (
<Table
aria-label="Example table with client side sorting"
sortDescriptor=
listsortDescriptor
onSortChange=
listsort
>
<TableHeader>
<Column
key="name"
allowsSorting>
Name
</Column>
<Column
key="height"
allowsSorting>
Height
</Column>
<Column
key="mass"
allowsSorting>
Mass
</Column>
<Column
key="birth_year"
allowsSorting>
Birth Year
</Column>
</TableHeader>
<TableBody
items=
listitems
>
(item) => (
<Row
key=
itemname
>
(
columnKey
) => (
<Cell>
item[
columnKey
]
</Cell>
)
</Row>
)
</TableBody>
</Table>
);
}
Nested columns#
Columns can be nested to create column groups. This will result in more than one header row to be created, with the colspan
attribute of each column header cell set to the appropriate value so that the columns line up. Data for the leaf columns
appears in each row of the table body.
This example also shows the use of the isRowHeader prop for Column, which controls which columns are included in the
accessibility name for each row. By default, only the first column is included, but in some cases more than one column may
be used to represent the row. In this example, the first and last name columns are combined to form the ARIA label for the row.
Only leaf columns may be marked as row headers.
<Table aria-label="Example table with nested columns">
<TableHeader>
<Column title="Name">
<Column isRowHeader>First Name</Column>
<Column isRowHeader>Last Name</Column>
</Column>
<Column title="Information">
<Column>Age</Column>
<Column>Birthday</Column>
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Sam</Cell>
<Cell>Smith</Cell>
<Cell>36</Cell>
<Cell>May 3</Cell>
</Row>
<Row>
<Cell>Julia</Cell>
<Cell>Jones</Cell>
<Cell>24</Cell>
<Cell>February 10</Cell>
</Row>
<Row>
<Cell>Peter</Cell>
<Cell>Parker</Cell>
<Cell>28</Cell>
<Cell>September 7</Cell>
</Row>
<Row>
<Cell>Bruce</Cell>
<Cell>Wayne</Cell>
<Cell>32</Cell>
<Cell>December 18</Cell>
</Row>
</TableBody>
</Table><Table aria-label="Example table with nested columns">
<TableHeader>
<Column title="Name">
<Column isRowHeader>First Name</Column>
<Column isRowHeader>Last Name</Column>
</Column>
<Column title="Information">
<Column>Age</Column>
<Column>Birthday</Column>
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Sam</Cell>
<Cell>Smith</Cell>
<Cell>36</Cell>
<Cell>May 3</Cell>
</Row>
<Row>
<Cell>Julia</Cell>
<Cell>Jones</Cell>
<Cell>24</Cell>
<Cell>February 10</Cell>
</Row>
<Row>
<Cell>Peter</Cell>
<Cell>Parker</Cell>
<Cell>28</Cell>
<Cell>September 7</Cell>
</Row>
<Row>
<Cell>Bruce</Cell>
<Cell>Wayne</Cell>
<Cell>32</Cell>
<Cell>December 18</Cell>
</Row>
</TableBody>
</Table><Table aria-label="Example table with nested columns">
<TableHeader>
<Column title="Name">
<Column
isRowHeader>
First Name
</Column>
<Column
isRowHeader>
Last Name
</Column>
</Column>
<Column title="Information">
<Column>
Age
</Column>
<Column>
Birthday
</Column>
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Sam</Cell>
<Cell>
Smith
</Cell>
<Cell>36</Cell>
<Cell>
May 3
</Cell>
</Row>
<Row>
<Cell>
Julia
</Cell>
<Cell>
Jones
</Cell>
<Cell>24</Cell>
<Cell>
February 10
</Cell>
</Row>
<Row>
<Cell>
Peter
</Cell>
<Cell>
Parker
</Cell>
<Cell>28</Cell>
<Cell>
September 7
</Cell>
</Row>
<Row>
<Cell>
Bruce
</Cell>
<Cell>
Wayne
</Cell>
<Cell>32</Cell>
<Cell>
December 18
</Cell>
</Row>
</TableBody>
</Table>Dynamic nested columns#
Nested columns can also be defined dynamically using the function syntax and the childColumns prop.
The following example is the same as the example above, but defined dynamically.
let columns = [
{
name: 'Name'
key: 'name'
children: [
{name: 'First Name' key: 'first' isRowHeader: true}
{name: 'Last Name' key: 'last' isRowHeader: true}
]
}
{
name: 'Information'
key: 'info'
children: [
{name: 'Age' key: 'age'}
{name: 'Birthday' key: 'birthday'}
]
}
];
let rows = [
{id: 1 first: 'Sam' last: 'Smith' age: 36 birthday: 'May 3'}
{id: 2 first: 'Julia' last: 'Jones' age: 24 birthday: 'February 10'}
{id: 3 first: 'Peter' last: 'Parker' age: 28 birthday: 'September 7'}
{id: 4 first: 'Bruce' last: 'Wayne' age: 32 birthday: 'December 18'}
];
<Table aria-label="Example table with dynamic nested columns">
<TableHeader columns=columns>
(column) => (
<Column isRowHeader=columnisRowHeader childColumns=columnchildren>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => <Row>(columnKey) => <Cell>item[columnKey]</Cell></Row>
</TableBody>
</Table>let columns = [
{
name: 'Name'
key: 'name'
children: [
{name: 'First Name' key: 'first' isRowHeader: true}
{name: 'Last Name' key: 'last' isRowHeader: true}
]
}
{
name: 'Information'
key: 'info'
children: [
{name: 'Age' key: 'age'}
{name: 'Birthday' key: 'birthday'}
]
}
];
let rows = [
{
id: 1
first: 'Sam'
last: 'Smith'
age: 36
birthday: 'May 3'
}
{
id: 2
first: 'Julia'
last: 'Jones'
age: 24
birthday: 'February 10'
}
{
id: 3
first: 'Peter'
last: 'Parker'
age: 28
birthday: 'September 7'
}
{
id: 4
first: 'Bruce'
last: 'Wayne'
age: 32
birthday: 'December 18'
}
];
<Table aria-label="Example table with dynamic nested columns">
<TableHeader columns=columns>
(column) => (
<Column
isRowHeader=columnisRowHeader
childColumns=columnchildren>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row>
(columnKey) => <Cell>item[columnKey]</Cell>
</Row>
)
</TableBody>
</Table>let columns = [
{
name: 'Name'
key: 'name'
children: [
{
name:
'First Name'
key: 'first'
isRowHeader: true
}
{
name:
'Last Name'
key: 'last'
isRowHeader: true
}
]
}
{
name: 'Information'
key: 'info'
children: [
{
name: 'Age'
key: 'age'
}
{
name: 'Birthday'
key: 'birthday'
}
]
}
];
let rows = [
{
id: 1
first: 'Sam'
last: 'Smith'
age: 36
birthday: 'May 3'
}
{
id: 2
first: 'Julia'
last: 'Jones'
age: 24
birthday:
'February 10'
}
{
id: 3
first: 'Peter'
last: 'Parker'
age: 28
birthday:
'September 7'
}
{
id: 4
first: 'Bruce'
last: 'Wayne'
age: 32
birthday:
'December 18'
}
];
<Table aria-label="Example table with dynamic nested columns">
<TableHeader
columns=columns>
(column) => (
<Column
isRowHeader=
columnisRowHeader
childColumns=
columnchildren
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=rows>
(item) => (
<Row>
(
columnKey
) => (
<Cell>
item[
columnKey
]
</Cell>
)
</Row>
)
</TableBody>
</Table>Internationalization#
useTable handles some aspects of internationalization automatically.
For example, type to select is implemented with an
Intl.Collator
for internationalized string matching, and keyboard navigation is mirrored in right-to-left languages.
You are responsible for localizing all text content within the table.
RTL#
In right-to-left languages, the table layout should be mirrored. The columns should be ordered from right to left and the individual column text alignment should be inverted. Ensure that your CSS accounts for this.
| Name | Type | Default | Description |
layout | Layout<Node<T>> | — | The layout object for the table. Computes what content is visible and how to position and style them. |
isVirtualized | boolean | — | Whether the grid uses virtual scrolling. |
keyboardDelegate | KeyboardDelegate | — | An optional keyboard delegate implementation for type to select, to override the default. |
focusMode | 'row' | 'cell' | 'row' | Whether initial grid focus should be placed on the grid row or grid cell. |
getRowText | (
(key: Key
)) => string | (key) => state.collection.getItem(key)?.textValue | A function that returns the text that should be announced by assistive technology when a row is added or removed from selection. |
scrollRef | RefObject<HTMLElement> | — | The ref attached to the scrollable body. Used to provided automatic scrolling on item focus for non-virtualized grids. |
id | string | — | The element's unique identifier. See MDN. |
aria-label | string | — | Defines a string value that labels the current element. |
aria-labelledby | string | — | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | — | Identifies the element (or elements) that describes the object. |
aria-details | string | — | Identifies the element (or elements) that provide a detailed, extended description for the object. |
[CollectionView]{@link CollectionView} supports arbitrary layout objects, which compute what views are visible, and how to position and style them. However, layouts do not create the views themselves directly. Instead, layouts produce lightweight {@link LayoutInfo} objects which describe various properties of a view, such as its position and size. The {@link CollectionView} is then responsible for creating the actual views as needed, based on this layout information.
Every layout extends from the {@link Layout} abstract base class. Layouts must implement a minimum of the two methods listed below. All other methods can be optionally overridden to implement custom behavior.
Properties
| Name | Type | Description |
virtualizer | Virtualizer<T, any, any> | The CollectionView the layout is currently attached to. |
Methods
| Method | Description |
shouldInvalidate(
(newRect: Rect,
, oldRect: Rect
)): boolean | Returns whether the layout should invalidate in response to visible rectangle changes. By default, it only invalidates when the collection view's size changes. Return true always to make the layout invalidate while scrolling (e.g. sticky headers). |
validate(
(invalidationContext: InvalidationContext<T, any>
)): void | This method allows the layout to perform any pre-computation it needs to in order to prepare {@link LayoutInfo}s for retrieval. Called by the collection view before {@link getVisibleLayoutInfos} or {@link getLayoutInfo} are called. |
getVisibleLayoutInfos(
(rect: Rect
)): LayoutInfo[] | Returns an array of {@link LayoutInfo} objects which are inside the given rectangle. Should be implemented by subclasses. |
getLayoutInfo(
(key: Key
)): LayoutInfo | Returns a {@link LayoutInfo} for the given key. Should be implemented by subclasses. |
getContentSize(): Size | Returns size of the content. By default, it returns collectionView's size. |
getInitialLayoutInfo(
(layoutInfo: LayoutInfo
)): LayoutInfo | Returns a {@link DragTarget} describing a view at the given point to be dragged.
Return null to cancel the drag. The default implementation returns the view at the given point. |
getFinalLayoutInfo(
(layoutInfo: LayoutInfo
)): LayoutInfo | Returns the ending attributes for an animated removal. The view is animated from the {@link LayoutInfo} returned by {@link getLayoutInfo} to the one returned by this method. The default implementation returns its input. |
Represents a rectangle.
Properties
| Name | Type | Description |
x | number | The x-coordinate of the rectangle. |
y | number | The y-coordinate of the rectangle. |
width | number | The width of the rectangle. |
height | number | The height of the rectangle. |
maxX | number | The maximum x-coordinate in the rectangle. |
maxY | number | The maximum y-coordinate in the rectangle. |
area | number | The area of the rectangle. |
topLeft | Point | The top left corner of the rectangle. |
topRight | Point | The top right corner of the rectangle. |
bottomLeft | Point | The bottom left corner of the rectangle. |
bottomRight | Point | The bottom right corner of the rectangle. |
Methods
| Method | Description |
intersects(
(rect: Rect
)): boolean | Returns whether this rectangle intersects another rectangle. |
containsRect(
(rect: Rect
)): boolean | Returns whether this rectangle fully contains another rectangle. |
containsPoint(
(point: Point
)): boolean | Returns whether the rectangle contains the given point. |
getCornerInRect(
(rect: Rect
)): RectCorner | null | Returns the first corner of this rectangle (from top to bottom, left to right) that is contained in the given rectangle, or null of the rectangles do not intersect. |
equals(
(rect: Rect
)): void | |
pointEquals(
(point: Point
| | Rect
)): void | |
sizeEquals(
(size: Size
| | Rect
)): void | |
copy(): Rect | Returns a copy of this rectangle. |
Properties
| Name | Type | Description |
x | number | The x-coordinate of the point. |
y | number | The y-coordinate of the point. |
Methods
| Method | Description |
copy(): Point | Returns a copy of this point. |
equals(
(point: Point
)): boolean | Checks if two points are equal. |
isOrigin(): boolean | Returns true if this point is the origin. |
'topLeft'
| 'topRight'
| 'bottomLeft'
| 'bottomRight'Properties
| Name | Type | Description |
width | number | |
height | number |
Methods
| Method | Description |
copy(): Size | Returns a copy of this size. |
equals(
(other: Size
)): boolean | Returns whether this size is equal to another one. |
Instances of this lightweight class are created by {@link Layout} subclasses to represent each view in the {@link CollectionView}. LayoutInfo objects describe various properties of a view, such as its position and size, and style information. The collection view uses this information when creating actual views to display.
Properties
| Name | Type | Description |
type | string | A string representing the view type. Should be 'item' for item views.
Other types are used by supplementary views. |
key | Key | A unique key for this view. For item views, it should match the content key. |
parentKey | Key | null | The key for a parent layout info, if any. |
rect | Rect | The rectangle describing the size and position of this view. |
estimatedSize | boolean | Whether the size is estimated. false by default. |
isSticky | boolean | Whether the layout info sticks to the viewport when scrolling. |
opacity | number | The view's opacity. 1 by default. |
transform | string | null | A CSS transform string to apply to the view. null by default. |
zIndex | number | The z-index of the view. 0 by default. |
Methods
| Method | Description |
copy(): LayoutInfo | Returns a copy of the LayoutInfo. |
Properties
| Name | Type | Description |
collection | TableCollection<T> | A collection of rows and columns in the table. |
showSelectionCheckboxes | boolean | Whether the row selection checkboxes should be displayed. |
sortDescriptor | SortDescriptor | The current sorted column and direction. |
disabledKeys | Set<Key> | A set of keys for rows that are disabled. |
selectionManager | SelectionManager | A selection manager to read and update row selection state. |
Methods
| Method | Description |
sort(
(columnKey: Key
)): void | Calls the provided onSortChange handler with the provided column key and sort direction. |
Properties
| Name | Type | Description |
headerRows | GridNode<T>[] | A list of header row nodes in the table. |
columns | GridNode<T>[] | A list of column nodes in the table. |
rowHeaderColumnKeys | Set<Key> | A set of column keys that serve as the row header. |
body | GridNode<T> | The node that makes up the body of the table. |
columnCount | number | The number of columns in the grid. |
rows | GridNode<T>[] | A list of rows in the grid. |
size | number | The number of items in the collection. |
Methods
| Method | Description |
getKeys(): Iterable<Key> | Iterate over all keys in the collection. |
getItem(
(key: Key
)): Node | Get an item by its key. |
at(
(idx: number
)): Node | Get an item by the index of its key. |
getKeyBefore(
(key: Key
)): Key | null | Get the key that comes before the given key in the collection. |
getKeyAfter(
(key: Key
)): Key | null | Get the key that comes after the given key in the collection. |
getFirstKey(): Key | null | Get the first key in the collection. |
getLastKey(): Key | null | Get the last key in the collection. |
| Name | Type | Description |
type | string | The type of item this node represents. |
key | Key | A unique key for the node. |
value | T | The object value the node was created from. |
level | number | The level of depth this node is at in the heirarchy. |
hasChildNodes | boolean | Whether this item has children, even if not loaded yet. |
childNodes | Iterable<Node<T>> | The loaded children of this node. |
rendered | ReactNode | The rendered contents of this node (e.g. JSX). |
textValue | string | A string value for this node, used for features like typeahead. |
column | GridNode<T> | |
colspan | number | |
aria-label | string | An accessibility label for this node. |
index | number | The index of this node within its parent. |
wrapper | (
(element: ReactElement
)) => ReactElement | A function that should be called to wrap the rendered node. |
parentKey | Key | The key of the parent node. |
prevKey | Key | The key of the node before this node. |
nextKey | Key | The key of the node after this node. |
props | any | Additional properties specific to a particular node type. |
An interface for reading and updating multiple selection state.
Properties
| Name | Type | Description |
selectionMode | SelectionMode | The type of selection that is allowed in the collection. |
disallowEmptySelection | boolean | Whether the collection allows empty selection. |
selectionBehavior | SelectionBehavior | The selection behavior for the collection. |
isFocused | boolean | Whether the collection is currently focused. |
focusedKey | Key | The current focused key in the collection. |
childFocusStrategy | FocusStrategy | Whether the first or last child of the focused key should receive focus. |
selectedKeys | Set<Key> | The currently selected keys in the collection. |
rawSelection | Selection | The raw selection value for the collection. Either 'all' for select all, or a set of keys. |
isEmpty | boolean | Whether the selection is empty. |
isSelectAll | boolean | Whether all items in the collection are selected. |
firstSelectedKey | Key | null | |
lastSelectedKey | Key | null |
Methods
| Method | Description |
setSelectionBehavior(
(selectionBehavior: SelectionBehavior
)): void | Sets the selection behavior for the collection. |
setFocused(
(isFocused: boolean
)): void | Sets whether the collection is focused. |
setFocusedKey(
(key: Key,
, childFocusStrategy: FocusStrategy
)): void | Sets the focused key. |
isSelected(
(key: Key
)): void | Returns whether a key is selected. |
extendSelection(
(toKey: Key
)): void | Extends the selection to the given key. |
toggleSelection(
(key: Key
)): void | Toggles whether the given key is selected. |
replaceSelection(
(key: Key
)): void | Replaces the selection with only the given key. |
setSelectedKeys(
(keys: Iterable<Key>
)): void | Replaces the selection with the given keys. |
selectAll(): void | Selects all items in the collection. |
clearSelection(): void | Removes all keys from the selection. |
toggleSelectAll(): void | Toggles between select all and an empty selection. |
select(
(key: Key,
, e: PressEvent
| LongPressEvent
| PointerEvent
)): void | |
isSelectionEqual(
(selection: Set<Key>
)): void | Returns whether the current selection is equal to the given selection. |
canSelectItem(
(key: Key
)): void |
Properties
| Name | Type | Description |
selectionMode | SelectionMode | The type of selection that is allowed in the collection. |
selectionBehavior | SelectionBehavior | The selection behavior for the collection. |
disallowEmptySelection | boolean | Whether the collection allows empty selection. |
selectedKeys | Selection | The currently selected keys in the collection. |
disabledKeys | Set<Key> | The currently disabled keys in the collection. |
isFocused | boolean | Whether the collection is currently focused. |
focusedKey | Key | The current focused key in the collection. |
childFocusStrategy | FocusStrategy | Whether the first or last child of the focused key should receive focus. |
Methods
| Method | Description |
setSelectionBehavior(
(selectionBehavior: SelectionBehavior
)): void | Sets the selection behavior for the collection. |
setSelectedKeys(
(keys: Selection
| | (
(v: Selection
)) => Selection
)): void | Sets the selected keys in the collection. |
setFocused(
(isFocused: boolean
)): void | Sets whether the collection is focused. |
setFocusedKey(
(key: Key,
, child: FocusStrategy
)): void | Sets the focused key, and optionally, whether the first or last child of that key should receive focus. |
| Name | Type | Description |
gridProps | HTMLAttributes<HTMLElement> | Props for the grid element. |
| Name | Type | Description |
rowGroupProps | HTMLAttributes<HTMLElement> | Props for the row group element. |
| Name | Type | Description |
node | Node<T> | An object representing the grid row. Contains all the relevant information that makes up the grid row. |
isVirtualized | boolean | Whether the grid row is contained in a virtual scroller. |
shouldSelectOnPressUp | boolean | Whether selection should occur on press up instead of press down. |
onAction | () => void | Handler that is called when a user performs an action on the row. |
| Name | Type | Description |
rowProps | HTMLAttributes<HTMLElement> | Props for the grid row element. |
| Name | Type | Description |
node | GridNode<unknown> | An object representing the column header. Contains all the relevant information that makes up the column header. |
isVirtualized | boolean | Whether the column header is contained in a virtual scroller. |
| Name | Type | Description |
columnHeaderProps | HTMLAttributes<HTMLElement> | Props for the column header element. |
| Name | Type | Description |
node | GridNode<unknown> | An object representing the table cell. Contains all the relevant information that makes up the row header. |
isVirtualized | boolean | Whether the cell is contained in a virtual scroller. |
onAction | () => void | Handler that is called when a user performs an action on the cell. |
| Name | Type | Description |
gridCellProps | HTMLAttributes<HTMLElement> | Props for the table cell element. |
| Name | Type | Description |
key | Key | A unique key for the checkbox. |
| Name | Type | Description |
checkboxProps | AriaCheckboxProps | Props for the row selection checkbox element. |
| Name | Type | Description |
isIndeterminate | boolean | Indeterminism is presentational only. The indeterminate visual representation remains regardless of user interaction. |
children | ReactNode | The label for the element. |
defaultSelected | boolean | Whether the element should be selected (uncontrolled). |
isSelected | boolean | Whether the element should be selected (controlled). |
onChange | (
(isSelected: boolean
)) => void | Handler that is called when the element's selection state changes. |
value | string | The value of the input element, used when submitting an HTML form. See MDN. |
name | string | The name of the input element, used when submitting an HTML form. See MDN. |
isDisabled | boolean | Whether the input is disabled. |
isReadOnly | boolean | Whether the input can be selected but not changed by the user. |
validationState | ValidationState | Whether the input should display its "valid" or "invalid" visual styling. |
isRequired | boolean | Whether user input is required on the input before form submission.
Often paired with the necessityIndicator prop to add a visual indicator to the input. |
autoFocus | boolean | Whether the element should receive focus on render. |
onFocus | (
(e: FocusEvent
)) => void | Handler that is called when the element receives focus. |
onBlur | (
(e: FocusEvent
)) => void | Handler that is called when the element loses focus. |
onFocusChange | (
(isFocused: boolean
)) => void | Handler that is called when the element's focus status changes. |
onKeyDown | (
(e: KeyboardEvent
)) => void | Handler that is called when a key is pressed. |
onKeyUp | (
(e: KeyboardEvent
)) => void | Handler that is called when a key is released. |
aria-controls | string | Identifies the element (or elements) whose contents or presence are controlled by the current element. |
excludeFromTabOrder | boolean | Whether to exclude the element from the sequential tab order. If true, the element will not be focusable via the keyboard by tabbing. This should be avoided except in rare scenarios where an alternative means of accessing the element or its functionality via the keyboard is available. |
id | string | The element's unique identifier. See MDN. |
aria-label | string | Defines a string value that labels the current element. |
aria-labelledby | string | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | Identifies the element (or elements) that describes the object. |
aria-details | string | Identifies the element (or elements) that provide a detailed, extended description for the object. |
aria-errormessage | string | Identifies the element that provides an error message for the object. |
| Name | Type | Description |
checkboxProps | AriaCheckboxProps | Props for the select all checkbox element. |
Provides the behavior and accessibility implementation for a table component. A table displays data in rows and columns and enables a user to navigate its contents via directional navigation keys, and optionally supports row selection and sorting.
useTable<T>(
props: TableProps<T>,
state: TableState<T>,
ref: RefObject<HTMLElement>
): GridAriaProvides the behavior and accessibility implementation for a row in a table.
useTableRow<T>(
props: GridRowProps<T>,
state: TableState<T>,
ref: RefObject<HTMLElement>
): GridRowAriaProvides the behavior and accessibility implementation for a cell in a table.
useTableCell<T>(
props: TableCellProps,
state: TableState<T>,
ref: RefObject<HTMLElement>
): TableCellAriaProvides the behavior and accessibility implementation for a column header in a table.
useTableColumnHeader<T>(
props: ColumnHeaderProps,
state: TableState<T>,
ref: RefObject<HTMLElement>
): ColumnHeaderAriaProvides the accessibility implementation for a row group in a grid.
useTableRowGroup(): GridRowGroupAriaProvides the behavior and accessibility implementation for a header row in a table.
useTableHeaderRow<T>(
props: GridRowProps<T>,
state: TableState<T>,
ref: RefObject<HTMLElement>
): GridRowAriaProvides the behavior and accessibility implementation for the select all checkbox in a table.
useTableSelectAllCheckbox<T>(
(state: TableState<T>
)): SelectAllCheckboxAriaProvides the behavior and accessibility implementation for a selection checkbox in a table.
useTableSelectionCheckbox<T>(
(props: SelectionCheckboxProps,
, state: TableState<T>
)): SelectionCheckboxAriaProvides state management for a table component. Handles building a collection of columns and rows from props. In addition, it tracks row selection and manages sort order changes.
useTableState<T>(
(props: TableStateProps<T>
)): TableState<T>| Name | Type | Description |
children | CollectionChildren<T> | The contents of the collection. |
showSelectionCheckboxes | boolean | Whether the row selection checkboxes should be displayed. |
items | Iterable<T> | Item objects in the collection. |
disabledKeys | Iterable<Key> | The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with. |
selectionBehavior | SelectionBehavior | How multiple selection should behave in the collection. |
selectionMode | SelectionMode | The type of selection that is allowed in the collection. |
disallowEmptySelection | boolean | Whether the collection allows empty selection. |
selectedKeys | 'all' | Iterable<Key> | The currently selected keys in the collection (controlled). |
defaultSelectedKeys | 'all' | Iterable<Key> | The initial selected keys in the collection (uncontrolled). |
onSelectionChange | (
(keys: Selection
)) => any | Handler that is called when the selection changes. |
sortDescriptor | SortDescriptor | The current sorted column and direction. |
onSortChange | (
(descriptor: SortDescriptor
)) => any | Handler that is called when the sorted column or direction changes. |
A TableHeader is a container for the Column elements in a Table. Columns can be statically defined
as children, or generated dynamically using a function based on the data passed to the columns prop.
TableHeader<T>(
(props: TableHeaderProps<T>
)): ReactElement| Name | Type | Description |
children | ColumnElement<T>
| ColumnElement<T>[]
| ColumnRenderer<T> | A list of Column(s) or a function. If the latter, a list of columns must be provided using the columns prop. |
columns | T[] | A list of table columns. |
ReactElement<ColumnProps<T>>| Name | Type | Description |
children | ReactNode
| ColumnElement<T>
| ColumnElement<T>[] | Static child columns or content to render as the column header. |
title | ReactNode | Rendered contents of the column if children contains child columns. |
childColumns | T[] | A list of child columns used when dynamically rendering nested child columns. |
width | number | string | The width of the column. |
minWidth | number | string | The minimum width of the column. |
maxWidth | number | string | The maximum width of the column. |
allowsSorting | boolean | Whether the column allows sorting. |
isRowHeader | boolean | Whether a column is a row header and should be announced by assistive technology during row navigation. |
textValue | string | A string representation of the column's contents, used for accessibility announcements. |
(
(item: T
)) => ColumnElement<T>A Column represents a field of each item within a Table. Columns may also contain nested
Column elements to represent column groups. Nested columns can be statically defined as
children, or dynamically generated using a function based on the childColumns prop.
Column<T>(
(props: ColumnProps<T>
)): ReactElementA TableBody is a container for the Row elements of a Table. Rows can be statically defined
as children, or generated dynamically using a function based on the data passed to the items prop.
TableBody<T>(
(props: TableBodyProps<T>
)): ReactElement| Name | Type | Description |
children | CollectionChildren<T> | The contents of the table body. Supports static items or a function for dynamic rendering. |
items | Iterable<T> | A list of row objects in the table body used when dynamically rendering rows. |
loadingState | LoadingState | The current loading state of the table. |
onLoadMore | () => any | Handler that is called when more items should be loaded, e.g. while scrolling near the bottom. |
A Row represents a single item in a Table and contains Cell elements for each column. Cells can be statically defined as children, or generated dynamically using a function based on the columns defined in the TableHeader.
Row(
(props: RowProps
)): ReactElement| Name | Type | Description |
children | CellElement
| CellElement[]
| CellRenderer | Rendered contents of the row or row child items. |
textValue | string | A string representation of the row's contents, used for features like typeahead. |
ReactElement<CellProps>| Name | Type | Description |
children | ReactNode | The contents of the cell. |
textValue | string | A string representation of the cell's contents, used for features like typeahead. |
(
(columnKey: Key
)) => CellElementA Cell represents the value of a single Column within a Table Row.
Cell(
(props: CellProps
)): ReactElementDetermines whether a focus ring should be shown to indicate keyboard focus. Focus rings are visible only when the user is interacting with a keyboard, not with a mouse, touch, or other input methods.
useFocusRing(
(props: FocusRingProps
)): FocusRingAria| Name | Type | Default | Description |
within | boolean | 'false' | Whether to show the focus ring when something inside the container element has focus (true), or only if the container itself has focus (false). |
isTextInput | boolean | — | Whether the element is a text input. |
autoFocus | boolean | — | Whether the element will be auto focused. |
| Name | Type | Description |
isFocused | boolean | Whether the element is currently focused. |
isFocusVisible | boolean | Whether keyboard focus should be visible. |
focusProps | HTMLAttributes<HTMLElement> | Props to apply to the container element with the focus ring. |
Provides the behavior and accessibility implementation for a checkbox component. Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected.
useCheckbox(
props: AriaCheckboxProps,
state: ToggleState,
inputRef: RefObject<HTMLInputElement>
): CheckboxAriaProperties
| Name | Type | Description |
isSelected | boolean | Whether the toggle is selected. |
Methods
| Method | Description |
setSelected(
(isSelected: boolean
)): void | Updates selection state. |
toggle(): void | Toggle the selection state. |
| Name | Type | Description |
inputProps | InputHTMLAttributes<HTMLInputElement> | Props for the input element. |