Table
Tables are containers for displaying information. They allow users to quickly scan, sort, compare, and take action on large amounts of data.
| install | yarn add @react-spectrum/table |
|---|---|
| version | 3.0.0-alpha.11 |
| usage | import {Cell, Column, Row, Table, TableBody, TableHeader} from '@react-spectrum/table' |
Example#
<Flex flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with static contents"
width="100%"
height="100%">
<TableHeader>
<Column>Name</Column>
<Column>Type</Column>
<Column align="end">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>
</Flex><Flex flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with static contents"
width="100%"
height="100%">
<TableHeader>
<Column>Name</Column>
<Column>Type</Column>
<Column align="end">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>
</Flex><Flex
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="Example table with static contents"
width="100%"
height="100%">
<TableHeader>
<Column>
Name
</Column>
<Column>
Type
</Column>
<Column align="end">
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>
</Flex>Content#
Table expects a TableHeader and a TableBody as children. In turn, TableHeader and TableBody follow the Collection Components API.
The TableHeader accepts either static Columns or a columns prop with a renderer function for dynamic rendering. Similarly, TableBody accepts either static Rows or a items prop with a renderer function.
Row follows the same pattern, accepting Cells as children instead.
Basic usage of Table, seen in the example above, shows multiple Columns and Rows populated with strings and Cells respectively. Static collections, as in this example, can be used when the contents of the Table is known ahead of time.
Dynamic collections, as shown below, can be used when the options come from an external data source such as an API call, or update over time. Providing the data in this way allows Table to automatically cache the rendering of each item,
which dramatically improves performance. Make sure that each rendered property in the Row object matches with a Column's key. In the example below, the uid of each Column is set as its key and matches with a
property within each Row object.
function Example() {
let columns = [
{name: 'Name' uid: 'name'}
{name: 'Type' uid: 'type'}
{name: 'Date Modified' uid: '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 (
<Flex flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with dynamic content"
width="100%"
height="100%">
<TableHeader columns=columns>
(column) => (
<Column
key=columnuid
align=columnuid === 'date' ? 'end' : 'start'>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row key=itemid>
/* Note this key is equal to the key of the the column,
not the key set on the Row prior */
(key) => <Cell>item[key]</Cell>
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
function Example() {
let columns = [
{name: 'Name' uid: 'name'}
{name: 'Type' uid: 'type'}
{name: 'Date Modified' uid: '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 (
<Flex flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with dynamic content"
width="100%"
height="100%">
<TableHeader columns=columns>
(column) => (
<Column
key=columnuid
align=
columnuid === 'date' ? 'end' : 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row key=itemid>
/* Note this key is equal to the key of the the column,
not the key set on the Row prior */
(key) => <Cell>item[key]</Cell>
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
function Example() {
let columns = [
{
name: 'Name'
uid: 'name'
}
{
name: 'Type'
uid: 'type'
}
{
name:
'Date Modified'
uid: '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 (
<Flex
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="Example table with dynamic content"
width="100%"
height="100%">
<TableHeader
columns=
columns
>
(column) => (
<Column
key=
columnuid
align=
columnuid ===
'date'
? 'end'
: 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=rows>
(item) => (
<Row
key=
itemid
>
/* Note this key is equal to the key of the the column,
not the key set on the Row prior */
(key) => (
<Cell>
item[
key
]
</Cell>
)
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
Internationalization#
To internationalize a Table, all text content within the Table should be replaced with localized strings. This includes the aria-label provided to the Table if any.
For languages that are read right-to-left (e.g. Hebrew and Arabic), the layout of Table is automatically flipped.
Labeling#
Accessibility#
An aria-label must be provided to the Table for accessibility. If the Table is labeled by a separate element, an aria-labelledby prop must be provided using the id of the labeling element instead.
By default, the first column of the Table is used as the row header and is announced by assistive technology when navigating through the rows. You can override this behavior by providing the isRowHeader prop
to one or more Columns, allowing you to customize which columns should label the rows of the Table.
The example below applies isRowHeader to the "First Name" and "Last Name" columns so that each row is announced with the person's full name (e.g. "John Doe").
<Flex flexGrow=1 maxWidth="size-3000">
<Table
aria-label="Example table with static contents"
width="100%"
height="100%">
<TableHeader>
<Column isRowHeader>First Name</Column>
<Column isRowHeader>Last Name</Column>
<Column align="end">Age</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>John</Cell>
<Cell>Doe</Cell>
<Cell>45</Cell>
</Row>
<Row>
<Cell>Jane</Cell>
<Cell>Doe</Cell>
<Cell>37</Cell>
</Row>
<Row>
<Cell>Joe</Cell>
<Cell>Schmoe</Cell>
<Cell>67</Cell>
</Row>
</TableBody>
</Table>
</Flex><Flex flexGrow=1 maxWidth="size-3000">
<Table
aria-label="Example table with static contents"
width="100%"
height="100%">
<TableHeader>
<Column isRowHeader>First Name</Column>
<Column isRowHeader>Last Name</Column>
<Column align="end">Age</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>John</Cell>
<Cell>Doe</Cell>
<Cell>45</Cell>
</Row>
<Row>
<Cell>Jane</Cell>
<Cell>Doe</Cell>
<Cell>37</Cell>
</Row>
<Row>
<Cell>Joe</Cell>
<Cell>Schmoe</Cell>
<Cell>67</Cell>
</Row>
</TableBody>
</Table>
</Flex><Flex
flexGrow=1
maxWidth="size-3000">
<Table
aria-label="Example table with static contents"
width="100%"
height="100%">
<TableHeader>
<Column
isRowHeader>
First Name
</Column>
<Column
isRowHeader>
Last Name
</Column>
<Column align="end">
Age
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>
John
</Cell>
<Cell>
Doe
</Cell>
<Cell>45</Cell>
</Row>
<Row>
<Cell>
Jane
</Cell>
<Cell>
Doe
</Cell>
<Cell>37</Cell>
</Row>
<Row>
<Cell>
Joe
</Cell>
<Cell>
Schmoe
</Cell>
<Cell>67</Cell>
</Row>
</TableBody>
</Table>
</Flex>Asynchronous loading#
Table supports loading data asynchronously, and will display a progress circle reflecting the current load state,
set by the isLoading prop. It also supports infinite scrolling to load more data on demand as the user scrolls, via the onLoadMore prop.
This example uses the useAsyncList hook to handle loading the data. See the docs for more information.
import {useAsyncList} from '@react-stately/data';
function AsyncTable() {
let columns = [
{name: 'Name' key: 'name'}
{name: 'Height' key: 'height'}
{name: 'Mass' key: 'mass'}
{name: 'Birth Year' key: 'birth_year'}
];
let list = useAsyncList({
async load({signal cursor}) {
if (cursor) {
cursor = cursorreplace(/^http:\/\//i 'https://');
}
let res = await fetch(cursor || `https://swapi.dev/api/people/?search=` {
signal
});
let json = await resjson();
return {
items: jsonresults
cursor: jsonnext
};
}
});
return (
<Flex height="size-2000" flexGrow=1 maxWidth="size-6000">
<Table
aria-label="example async loading table"
height="100%"
width="100%">
<TableHeader columns=columns>
(column) => (
<Column align=columnkey !== 'name' ? 'end' : 'start'>
columnname
</Column>
)
</TableHeader>
<TableBody
items=listitems
isLoading=listisLoading
onLoadMore=listloadMore>
(item) => (
<Row key=itemname>(key) => <Cell>item[key]</Cell></Row>
)
</TableBody>
</Table>
</Flex>
);
}
import {useAsyncList} from '@react-stately/data';
function AsyncTable() {
let columns = [
{name: 'Name' key: 'name'}
{name: 'Height' key: 'height'}
{name: 'Mass' key: 'mass'}
{name: 'Birth Year' key: 'birth_year'}
];
let list = useAsyncList({
async load({signal cursor}) {
if (cursor) {
cursor = cursorreplace(/^http:\/\//i 'https://');
}
let res = await fetch(
cursor || `https://swapi.dev/api/people/?search=`
{signal}
);
let json = await resjson();
return {
items: jsonresults
cursor: jsonnext
};
}
});
return (
<Flex
height="size-2000"
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="example async loading table"
height="100%"
width="100%">
<TableHeader columns=columns>
(column) => (
<Column
align=
columnkey !== 'name' ? 'end' : 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=listitems
isLoading=listisLoading
onLoadMore=listloadMore>
(item) => (
<Row key=itemname>
(key) => <Cell>item[key]</Cell>
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
import {useAsyncList} from '@react-stately/data';
function AsyncTable() {
let columns = [
{
name: 'Name'
key: 'name'
}
{
name: 'Height'
key: 'height'
}
{
name: 'Mass'
key: 'mass'
}
{
name: 'Birth Year'
key: 'birth_year'
}
];
let list = useAsyncList(
{
async load({
signal
cursor
}) {
if (cursor) {
cursor = cursorreplace(
/^http:\/\//i
'https://'
);
}
let res = await fetch(
cursor ||
`https://swapi.dev/api/people/?search=`
{signal}
);
let json = await resjson();
return {
items:
jsonresults
cursor:
jsonnext
};
}
}
);
return (
<Flex
height="size-2000"
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="example async loading table"
height="100%"
width="100%">
<TableHeader
columns=
columns
>
(column) => (
<Column
align=
columnkey !==
'name'
? 'end'
: 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=
listitems
isLoading=
listisLoading
onLoadMore=
listloadMore
>
(item) => (
<Row
key=
itemname
>
(key) => (
<Cell>
item[
key
]
</Cell>
)
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
Events#
Selection#
Table supports multiple selection modes. By default, selection is disabled, but this can be modified 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 uses defaultSelectedKeys to select the row with key equal to "2".
<Flex flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with single selection (uncontrolled)"
width="100%"
height="100%"
selectionMode="single"
defaultSelectedKeys=['2']>
<TableHeader>
<Column>Name</Column>
<Column>Type</Column>
<Column align="end">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>
</Flex><Flex flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with single selection (uncontrolled)"
width="100%"
height="100%"
selectionMode="single"
defaultSelectedKeys=['2']>
<TableHeader>
<Column>Name</Column>
<Column>Type</Column>
<Column align="end">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>
</Flex><Flex
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="Example table with single selection (uncontrolled)"
width="100%"
height="100%"
selectionMode="single"
defaultSelectedKeys=[
'2'
]>
<TableHeader>
<Column>
Name
</Column>
<Column>
Type
</Column>
<Column align="end">
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>
</Flex>To programmatically control row selection, use the selectedKeys prop paired with the onSelectionChange callback. The key prop from the selected row will
be passed into the callback when the row is pressed, allowing you to update selectedKeys accordingly.
Here is how you would control selection for the above example.
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 [selected setSelected] = ReactuseState(new Set(['2']));
return (
<Flex flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with single selection (controlled)"
width="100%"
height="100%"
selectionMode="single"
selectedKeys=selected
onSelectionChange=setSelected
...props>
<TableHeader columns=columns>
(column) => (
<Column
key=columnuid
align=columnuid === 'level' ? 'end' : 'start'>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row key=itemid>(key) => <Cell>item[key]</Cell></Row>
)
</TableBody>
</Table>
</Flex>
);
}
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 [selected setSelected] = ReactuseState(
new Set(['2'])
);
return (
<Flex flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with single selection (controlled)"
width="100%"
height="100%"
selectionMode="single"
selectedKeys=selected
onSelectionChange=setSelected
...props>
<TableHeader columns=columns>
(column) => (
<Column
key=columnuid
align=
columnuid === 'level' ? 'end' : 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row key=itemid>
(key) => <Cell>item[key]</Cell>
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
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 [
selected
setSelected
] = ReactuseState(
new Set(['2'])
);
return (
<Flex
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="Example table with single selection (controlled)"
width="100%"
height="100%"
selectionMode="single"
selectedKeys=
selected
onSelectionChange=
setSelected
...props>
<TableHeader
columns=
columns
>
(column) => (
<Column
key=
columnuid
align=
columnuid ===
'level'
? 'end'
: 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=rows>
(item) => (
<Row
key=
itemid
>
(key) => (
<Cell>
item[
key
]
</Cell>
)
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
Multiple selection can be enabled by setting selectionMode to multiple.
// Using the same table as above
<PokemonTable selectionMode="multiple" />// Using the same table as above
<PokemonTable selectionMode="multiple" />// Using the same table as above
<PokemonTable selectionMode="multiple" />Table also supports a disallowEmptySelection prop which forces the user to have at least one row in the Table selected at all times.
// Using the same table as above
<PokemonTable selectionMode="single" disallowEmptySelection />// Using the same table as above
<PokemonTable
selectionMode="single"
disallowEmptySelection
/>// Using the same table as above
<PokemonTable
selectionMode="single"
disallowEmptySelection
/>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 column's sort icon, 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.
function AsyncSortTable() {
let columns = [
{name: 'Name' key: 'name'}
{name: 'Height' key: 'height'}
{name: 'Mass' key: 'mass'}
{name: 'Birth Year' key: 'birth_year'}
];
let list = useAsyncList({
async load({signal cursor}) {
if (cursor) {
cursor = cursorreplace(/^http:\/\//i 'https://');
}
let res = await fetch(cursor || `https://swapi.dev/api/people/?search` {
signal
});
let json = await resjson();
return {
items: jsonresults
cursor: jsonnext
};
}
async sort({items sortDescriptor}) {
let sorted = itemssort((a b) => {
let cmp;
let first = a[sortDescriptorcolumn]replace('BBY' '');
let second = b[sortDescriptorcolumn]replace('BBY' '');
if (+first || +second) {
cmp = +first < +second ? -1 : 1;
} else {
cmp = first <= second ? -1 : 1;
}
if (sortDescriptordirection === 'descending') {
cmp *= -1;
}
return cmp;
});
return {
items: sorted
};
}
});
return (
<Flex height="size-2000" flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with client side sorting"
height="100%"
width="100%"
sortDescriptor=listsortDescriptor
onSortChange=listsort>
<TableHeader columns=columns>
(column) => (
<Column
allowsSorting
align=columnkey !== 'name' ? 'end' : 'start'>
columnname
</Column>
)
</TableHeader>
<TableBody
items=listitems
isLoading=listisLoading
onLoadMore=listloadMore>
(item) => (
<Row key=itemname>(key) => <Cell>item[key]</Cell></Row>
)
</TableBody>
</Table>
</Flex>
);
}
function AsyncSortTable() {
let columns = [
{name: 'Name' key: 'name'}
{name: 'Height' key: 'height'}
{name: 'Mass' key: 'mass'}
{name: 'Birth Year' key: 'birth_year'}
];
let list = useAsyncList({
async load({signal cursor}) {
if (cursor) {
cursor = cursorreplace(/^http:\/\//i 'https://');
}
let res = await fetch(
cursor || `https://swapi.dev/api/people/?search`
{signal}
);
let json = await resjson();
return {
items: jsonresults
cursor: jsonnext
};
}
async sort({items sortDescriptor}) {
let sorted = itemssort((a b) => {
let cmp;
let first = a[sortDescriptorcolumn]replace(
'BBY'
''
);
let second = b[sortDescriptorcolumn]replace(
'BBY'
''
);
if (+first || +second) {
cmp = +first < +second ? -1 : 1;
} else {
cmp = first <= second ? -1 : 1;
}
if (sortDescriptordirection === 'descending') {
cmp *= -1;
}
return cmp;
});
return {
items: sorted
};
}
});
return (
<Flex
height="size-2000"
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="Example table with client side sorting"
height="100%"
width="100%"
sortDescriptor=listsortDescriptor
onSortChange=listsort>
<TableHeader columns=columns>
(column) => (
<Column
allowsSorting
align=
columnkey !== 'name' ? 'end' : 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=listitems
isLoading=listisLoading
onLoadMore=listloadMore>
(item) => (
<Row key=itemname>
(key) => <Cell>item[key]</Cell>
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
function AsyncSortTable() {
let columns = [
{
name: 'Name'
key: 'name'
}
{
name: 'Height'
key: 'height'
}
{
name: 'Mass'
key: 'mass'
}
{
name: 'Birth Year'
key: 'birth_year'
}
];
let list = useAsyncList(
{
async load({
signal
cursor
}) {
if (cursor) {
cursor = cursorreplace(
/^http:\/\//i
'https://'
);
}
let res = await fetch(
cursor ||
`https://swapi.dev/api/people/?search`
{signal}
);
let json = await resjson();
return {
items:
jsonresults
cursor:
jsonnext
};
}
async sort({
items
sortDescriptor
}) {
let sorted = itemssort(
(a b) => {
let cmp;
let first = a[
sortDescriptor
column
]replace(
'BBY'
''
);
let second = b[
sortDescriptor
column
]replace(
'BBY'
''
);
if (
+first ||
+second
) {
cmp =
+first <
+second
? -1
: 1;
} else {
cmp =
first <=
second
? -1
: 1;
}
if (
sortDescriptordirection ===
'descending'
) {
cmp *= -1;
}
return cmp;
}
);
return {
items: sorted
};
}
}
);
return (
<Flex
height="size-2000"
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="Example table with client side sorting"
height="100%"
width="100%"
sortDescriptor=
listsortDescriptor
onSortChange=
listsort
>
<TableHeader
columns=
columns
>
(column) => (
<Column
allowsSorting
align=
columnkey !==
'name'
? 'end'
: 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=
listitems
isLoading=
listisLoading
onLoadMore=
listloadMore
>
(item) => (
<Row
key=
itemname
>
(key) => (
<Cell>
item[
key
]
</Cell>
)
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
Props#
Table props#
| Name | Type | Default | Description |
children | ReactElement<TableHeaderProps<T> | TableBodyProps<T>>[] | — | The elements that make up the Table. Includes the TableHeader, TableBody, Columns, and Rows. |
density | 'compact'
| 'regular'
| 'spacious' | 'regular' | Sets the amount of vertical padding within each Table cell. |
overflowMode | 'wrap' | 'truncate' | 'truncate' | Sets the overflow behavior for the Table cell contents. |
isQuiet | boolean | — | Whether the Table should be displayed with a quiet style. |
renderEmptyState | () => JSX.Element | — | Sets what the Table should render when there is no content to display. |
disabledKeys | Iterable<Key> | — | A list of row keys to disable. |
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). |
sortDescriptor | SortDescriptor | — | The current sorted column and direction. |
Events
| Name | Type | Default | Description |
onSelectionChange | (
(keys: Selection
)) => any | — | Handler that is called when the selection changes. |
onSortChange | (
(descriptor: SortDescriptor
)) => any | — | Handler that is called when the sorted column or direction changes. |
Layout
| Name | Type | Default | Description |
flex | string
| number
| boolean | — | When used in a flex layout, specifies how the element will grow or shrink to fit the space available. See MDN. |
flexGrow | number | — | When used in a flex layout, specifies how the element will grow to fit the space available. See MDN. |
flexShrink | number | — | When used in a flex layout, specifies how the element will shrink to fit the space available. See MDN. |
flexBasis | number | string | — | When used in a flex layout, specifies the initial main size of the element. See MDN. |
alignSelf | 'auto'
| 'normal'
| 'start'
| 'end'
| 'center'
| 'flex-start'
| 'flex-end'
| 'self-start'
| 'self-end'
| 'stretch' | — | Overrides the alignItems property of a flex or grid container. See MDN. |
justifySelf | 'auto'
| 'normal'
| 'start'
| 'end'
| 'flex-start'
| 'flex-end'
| 'self-start'
| 'self-end'
| 'center'
| 'left'
| 'right'
| 'stretch' | — | Specifies how the element is justified inside a flex or grid container. See MDN. |
order | number | — | The layout order for the element within a flex or grid container. See MDN. |
gridArea | string | — | When used in a grid layout, specifies the named grid area that the element should be placed in within the grid. See MDN. |
gridColumn | string | — | When used in a grid layout, specifies the column the element should be placed in within the grid. See MDN. |
gridRow | string | — | When used in a grid layout, specifies the row the element should be placed in within the grid. See MDN. |
gridColumnStart | string | — | When used in a grid layout, specifies the starting column to span within the grid. See MDN. |
gridColumnEnd | string | — | When used in a grid layout, specifies the ending column to span within the grid. See MDN. |
gridRowStart | string | — | When used in a grid layout, specifies the starting row to span within the grid. See MDN. |
gridRowEnd | string | — | When used in a grid layout, specifies the ending row to span within the grid. See MDN. |
Spacing
| Name | Type | Default | Description |
margin | DimensionValue | — | The margin for all four sides of the element. See MDN. |
marginTop | DimensionValue | — | The margin for the top side of the element. See MDN. |
marginBottom | DimensionValue | — | The margin for the bottom side of the element. See MDN. |
marginStart | DimensionValue | — | The margin for the logical start side of the element, depending on layout direction. See MDN. |
marginEnd | DimensionValue | — | The margin for the logical end side of an element, depending on layout direction. See MDN. |
marginX | DimensionValue | — | The margin for both the left and right sides of the element. See MDN. |
marginY | DimensionValue | — | The margin for both the top and bottom sides of the element. See MDN. |
Sizing
| Name | Type | Default | Description |
width | DimensionValue | — | The width of the element. See MDN. |
minWidth | DimensionValue | — | The minimum width of the element. See MDN. |
maxWidth | DimensionValue | — | The maximum width of the element. See MDN. |
height | DimensionValue | — | The height of the element. See MDN. |
minHeight | DimensionValue | — | The minimum height of the element. See MDN. |
maxHeight | DimensionValue | — | The maximum height of the element. See MDN. |
Positioning
| Name | Type | Default | Description |
position | 'static'
| 'relative'
| 'absolute'
| 'fixed'
| 'sticky' | — | Specifies how the element is positioned. See MDN. |
top | DimensionValue | — | The top position for the element. See MDN. |
bottom | DimensionValue | — | The bottom position for the element. See MDN. |
left | DimensionValue | — | The left position for the element. See MDN. Consider using start instead for RTL support. |
right | DimensionValue | — | The right position for the element. See MDN. Consider using start instead for RTL support. |
start | DimensionValue | — | The logical start position for the element, depending on layout direction. See MDN. |
end | DimensionValue | — | The logical end position for the element, depending on layout direction. See MDN. |
zIndex | number | — | The stacking order for the element. See MDN. |
isHidden | boolean | — | Hides the element. |
Accessibility
| Name | Type | Default | Description |
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. |
Advanced
| Name | Type | Default | Description |
UNSAFE_className | string | — | Sets the CSS className for the element. Only use as a last resort. Use style props instead. |
UNSAFE_style | CSSProperties | — | Sets inline style for the element. Only use as a last resort. Use style props instead. |
Table header props#
| Name | Type | Default | 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. |
Column props#
| Name | Type | Default | Description |
children | ReactNode
| ColumnElement<T>
| ColumnElement<T>[] | — | Static child columns or content to render as the column header. |
align | 'start'
| 'center'
| 'end' | 'start' | The alignment of the column's contents relative to its allotted width. |
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. |
showDivider | boolean | — | Whether the column should render a divider between it and the next column. |
hideHeader | boolean | — | Whether the column should hide its header text. A tooltip with the column's header text will be displayed when the column header is focused instead. |
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. |
defaultWidth | number | string | — | The default width of the column. |
Sizing
| Name | Type | Default | Description |
width | number | string | — | An accessibility label for the column. The width of the column. |
minWidth | number | string | — | The minimum width of the column. |
maxWidth | number | string | — | The maximum width of the column. |
Table body props#
| Name | Type | Default | 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. |
isLoading | boolean | — | Whether the items are currently loading. |
Events
| Name | Type | Default | Description |
onLoadMore | () => any | — | Handler that is called when more items should be loaded, e.g. while scrolling near the bottom. |
Row props#
| Name | Type | Default | Description |
children | CellElement
| CellElement[]
| CellRenderer | — | Rendered contents of the row or row child items. |
childItems | Iterable<T> | — | A list of child item objects used when dynamically rendering row children. |
hasChildItems | boolean | — | Whether this row has children, even if not loaded yet. |
textValue | string | — | A string representation of the row's contents, used for features like typeahead. |
Cell props#
| Name | Type | Default | Description |
children | ReactNode | — | The contents of the cell. |
textValue | string | — | A string representation of the cell's contents, used for features like typeahead. |
Visual options#
Column alignment#
<Flex flexGrow=1 maxWidth="size-4600">
<Table
aria-label="Example table for column alignment"
width="100%"
height="100%">
<TableHeader>
<Column align="start">Name</Column>
<Column align="center">Type</Column>
<Column align="end">Size</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>2021406_Proposal</Cell>
<Cell>PDF</Cell>
<Cell>86 KB</Cell>
</Row>
<Row>
<Cell>Budget Template</Cell>
<Cell>XLS</Cell>
<Cell>120 KB</Cell>
</Row>
<Row>
<Cell>Onboarding</Cell>
<Cell>PPT</Cell>
<Cell>472 KB</Cell>
</Row>
<Row>
<Cell>Welcome</Cell>
<Cell>TXT</Cell>
<Cell>24 KB</Cell>
</Row>
</TableBody>
</Table>
</Flex><Flex flexGrow=1 maxWidth="size-4600">
<Table
aria-label="Example table for column alignment"
width="100%"
height="100%">
<TableHeader>
<Column align="start">Name</Column>
<Column align="center">Type</Column>
<Column align="end">Size</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>2021406_Proposal</Cell>
<Cell>PDF</Cell>
<Cell>86 KB</Cell>
</Row>
<Row>
<Cell>Budget Template</Cell>
<Cell>XLS</Cell>
<Cell>120 KB</Cell>
</Row>
<Row>
<Cell>Onboarding</Cell>
<Cell>PPT</Cell>
<Cell>472 KB</Cell>
</Row>
<Row>
<Cell>Welcome</Cell>
<Cell>TXT</Cell>
<Cell>24 KB</Cell>
</Row>
</TableBody>
</Table>
</Flex><Flex
flexGrow=1
maxWidth="size-4600">
<Table
aria-label="Example table for column alignment"
width="100%"
height="100%">
<TableHeader>
<Column align="start">
Name
</Column>
<Column align="center">
Type
</Column>
<Column align="end">
Size
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>
2021406_Proposal
</Cell>
<Cell>
PDF
</Cell>
<Cell>
86 KB
</Cell>
</Row>
<Row>
<Cell>
Budget
Template
</Cell>
<Cell>
XLS
</Cell>
<Cell>
120 KB
</Cell>
</Row>
<Row>
<Cell>
Onboarding
</Cell>
<Cell>
PPT
</Cell>
<Cell>
472 KB
</Cell>
</Row>
<Row>
<Cell>
Welcome
</Cell>
<Cell>
TXT
</Cell>
<Cell>
24 KB
</Cell>
</Row>
</TableBody>
</Table>
</Flex>Column widths#
Columns support three different width props: minWidth, width, and maxWidth.
<Flex flexGrow=1 maxWidth="size-4600">
<Table
aria-label="Example table for column widths"
width="100%"
height="100%">
<TableHeader>
<Column maxWidth=300 align="start">
Name
</Column>
<Column width=80>Type</Column>
<Column minWidth=100 align="end">
Size
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>2021406_Proposal</Cell>
<Cell>PDF</Cell>
<Cell>86 KB</Cell>
</Row>
<Row>
<Cell>Budget Template</Cell>
<Cell>XLS</Cell>
<Cell>120 KB</Cell>
</Row>
<Row>
<Cell>Onboarding</Cell>
<Cell>PPT</Cell>
<Cell>472 KB</Cell>
</Row>
<Row>
<Cell>Welcome</Cell>
<Cell>TXT</Cell>
<Cell>24 KB</Cell>
</Row>
</TableBody>
</Table>
</Flex><Flex flexGrow=1 maxWidth="size-4600">
<Table
aria-label="Example table for column widths"
width="100%"
height="100%">
<TableHeader>
<Column maxWidth=300 align="start">
Name
</Column>
<Column width=80>Type</Column>
<Column minWidth=100 align="end">
Size
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>2021406_Proposal</Cell>
<Cell>PDF</Cell>
<Cell>86 KB</Cell>
</Row>
<Row>
<Cell>Budget Template</Cell>
<Cell>XLS</Cell>
<Cell>120 KB</Cell>
</Row>
<Row>
<Cell>Onboarding</Cell>
<Cell>PPT</Cell>
<Cell>472 KB</Cell>
</Row>
<Row>
<Cell>Welcome</Cell>
<Cell>TXT</Cell>
<Cell>24 KB</Cell>
</Row>
</TableBody>
</Table>
</Flex><Flex
flexGrow=1
maxWidth="size-4600">
<Table
aria-label="Example table for column widths"
width="100%"
height="100%">
<TableHeader>
<Column
maxWidth=300
align="start">
Name
</Column>
<Column
width=80>
Type
</Column>
<Column
minWidth=100
align="end">
Size
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>
2021406_Proposal
</Cell>
<Cell>
PDF
</Cell>
<Cell>
86 KB
</Cell>
</Row>
<Row>
<Cell>
Budget
Template
</Cell>
<Cell>
XLS
</Cell>
<Cell>
120 KB
</Cell>
</Row>
<Row>
<Cell>
Onboarding
</Cell>
<Cell>
PPT
</Cell>
<Cell>
472 KB
</Cell>
</Row>
<Row>
<Cell>
Welcome
</Cell>
<Cell>
TXT
</Cell>
<Cell>
24 KB
</Cell>
</Row>
</TableBody>
</Table>
</Flex>Column dividers#
<Flex flexGrow=1 maxWidth="size-4600">
<Table
aria-label="Example table for column dividers"
width="100%"
height="100%">
<TableHeader>
<Column align="start" showDivider>
Name
</Column>
<Column showDivider>Type</Column>
<Column align="end" showDivider>
Size
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>2021406_Proposal</Cell>
<Cell>PDF</Cell>
<Cell>86 KB</Cell>
</Row>
<Row>
<Cell>Budget Template</Cell>
<Cell>XLS</Cell>
<Cell>120 KB</Cell>
</Row>
<Row>
<Cell>Onboarding</Cell>
<Cell>PPT</Cell>
<Cell>472 KB</Cell>
</Row>
<Row>
<Cell>Welcome</Cell>
<Cell>TXT</Cell>
<Cell>24 KB</Cell>
</Row>
</TableBody>
</Table>
</Flex><Flex flexGrow=1 maxWidth="size-4600">
<Table
aria-label="Example table for column dividers"
width="100%"
height="100%">
<TableHeader>
<Column align="start" showDivider>
Name
</Column>
<Column showDivider>Type</Column>
<Column align="end" showDivider>
Size
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>2021406_Proposal</Cell>
<Cell>PDF</Cell>
<Cell>86 KB</Cell>
</Row>
<Row>
<Cell>Budget Template</Cell>
<Cell>XLS</Cell>
<Cell>120 KB</Cell>
</Row>
<Row>
<Cell>Onboarding</Cell>
<Cell>PPT</Cell>
<Cell>472 KB</Cell>
</Row>
<Row>
<Cell>Welcome</Cell>
<Cell>TXT</Cell>
<Cell>24 KB</Cell>
</Row>
</TableBody>
</Table>
</Flex><Flex
flexGrow=1
maxWidth="size-4600">
<Table
aria-label="Example table for column dividers"
width="100%"
height="100%">
<TableHeader>
<Column
align="start"
showDivider>
Name
</Column>
<Column
showDivider>
Type
</Column>
<Column
align="end"
showDivider>
Size
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>
2021406_Proposal
</Cell>
<Cell>
PDF
</Cell>
<Cell>
86 KB
</Cell>
</Row>
<Row>
<Cell>
Budget
Template
</Cell>
<Cell>
XLS
</Cell>
<Cell>
120 KB
</Cell>
</Row>
<Row>
<Cell>
Onboarding
</Cell>
<Cell>
PPT
</Cell>
<Cell>
472 KB
</Cell>
</Row>
<Row>
<Cell>
Welcome
</Cell>
<Cell>
TXT
</Cell>
<Cell>
24 KB
</Cell>
</Row>
</TableBody>
</Table>
</Flex>Nested columns#
Table supports nesting columns, allowing you to render "tiered" column headers.
<Flex flexGrow=1 maxWidth="size-4600">
<Table
aria-label="Example table for nested columns"
width="100%"
height="100%">
<TableHeader>
<Column title="OS (C:)">
<Column align="start">Name</Column>
<Column>Type</Column>
<Column align="end">Size</Column>
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>2021406_Proposal</Cell>
<Cell>PDF</Cell>
<Cell>86 KB</Cell>
</Row>
<Row>
<Cell>Budget Template</Cell>
<Cell>XLS</Cell>
<Cell>120 KB</Cell>
</Row>
<Row>
<Cell>Onboarding</Cell>
<Cell>PPT</Cell>
<Cell>472 KB</Cell>
</Row>
</TableBody>
</Table>
</Flex><Flex flexGrow=1 maxWidth="size-4600">
<Table
aria-label="Example table for nested columns"
width="100%"
height="100%">
<TableHeader>
<Column title="OS (C:)">
<Column align="start">Name</Column>
<Column>Type</Column>
<Column align="end">Size</Column>
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>2021406_Proposal</Cell>
<Cell>PDF</Cell>
<Cell>86 KB</Cell>
</Row>
<Row>
<Cell>Budget Template</Cell>
<Cell>XLS</Cell>
<Cell>120 KB</Cell>
</Row>
<Row>
<Cell>Onboarding</Cell>
<Cell>PPT</Cell>
<Cell>472 KB</Cell>
</Row>
</TableBody>
</Table>
</Flex><Flex
flexGrow=1
maxWidth="size-4600">
<Table
aria-label="Example table for nested columns"
width="100%"
height="100%">
<TableHeader>
<Column title="OS (C:)">
<Column align="start">
Name
</Column>
<Column>
Type
</Column>
<Column align="end">
Size
</Column>
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>
2021406_Proposal
</Cell>
<Cell>
PDF
</Cell>
<Cell>
86 KB
</Cell>
</Row>
<Row>
<Cell>
Budget
Template
</Cell>
<Cell>
XLS
</Cell>
<Cell>
120 KB
</Cell>
</Row>
<Row>
<Cell>
Onboarding
</Cell>
<Cell>
PPT
</Cell>
<Cell>
472 KB
</Cell>
</Row>
</TableBody>
</Table>
</Flex>Focusable cells#
Cells accept any renderable node, allowing you to have focusable children within the table.
import Edit from '@spectrum-icons/workflow/Edit';
import Delete from '@spectrum-icons/workflow/Delete';
<Flex maxWidth="size-3000" flexGrow=1>
<Table
aria-label="Example table for focusable cells"
width="100%"
height="100%">
<TableHeader>
<Column>Name</Column>
<Column hideHeader>Add</Column>
<Column hideHeader>Delete</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Red Panda</Cell>
<Cell>
<ActionButton isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
<Row>
<Cell>Harbor Seal</Cell>
<Cell>
<ActionButton isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
<Row>
<Cell>Groundhog</Cell>
<Cell>
<ActionButton isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
<Row>
<Cell>Otter</Cell>
<Cell>
<ActionButton isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
</TableBody>
</Table>
</Flex>import Edit from '@spectrum-icons/workflow/Edit';
import Delete from '@spectrum-icons/workflow/Delete';
<Flex maxWidth="size-3000" flexGrow=1>
<Table
aria-label="Example table for focusable cells"
width="100%"
height="100%">
<TableHeader>
<Column>Name</Column>
<Column hideHeader>Add</Column>
<Column hideHeader>Delete</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Red Panda</Cell>
<Cell>
<ActionButton isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
<Row>
<Cell>Harbor Seal</Cell>
<Cell>
<ActionButton isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
<Row>
<Cell>Groundhog</Cell>
<Cell>
<ActionButton isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
<Row>
<Cell>Otter</Cell>
<Cell>
<ActionButton isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
</TableBody>
</Table>
</Flex>import Edit from '@spectrum-icons/workflow/Edit';
import Delete from '@spectrum-icons/workflow/Delete';
<Flex
maxWidth="size-3000"
flexGrow=1>
<Table
aria-label="Example table for focusable cells"
width="100%"
height="100%">
<TableHeader>
<Column>
Name
</Column>
<Column
hideHeader>
Add
</Column>
<Column
hideHeader>
Delete
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>
Red Panda
</Cell>
<Cell>
<ActionButton
isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton
isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
<Row>
<Cell>
Harbor Seal
</Cell>
<Cell>
<ActionButton
isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton
isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
<Row>
<Cell>
Groundhog
</Cell>
<Cell>
<ActionButton
isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton
isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
<Row>
<Cell>
Otter
</Cell>
<Cell>
<ActionButton
isQuiet>
<Edit />
</ActionButton>
</Cell>
<Cell>
<ActionButton
isQuiet>
<Delete />
</ActionButton>
</Cell>
</Row>
</TableBody>
</Table>
</Flex>Hide header#
Individual column headers can be hidden by providing the hideHeader prop to the Column. A tooltip is rendered when the column header is focused
to compensate for the lack of a visual title.
function TableExample(props) {
let columns = [
{name: 'First Name' key: 'firstName'}
{name: 'Last Name' key: 'lastName'}
{name: 'Add Info' key: 'addInfo'}
{name: 'Age' key: 'age'}
];
let rows = [
{id: '1' firstName: 'John' lastName: 'Doe' age: '45'}
{id: '2' firstName: 'Jane' lastName: 'Doe' age: '37'}
{id: '3' firstName: 'Joe' lastName: 'Schmoe' age: '67'}
{id: '4' firstName: 'Joe' lastName: 'Bloggs' age: '12'}
{
id: '5'
firstName: 'Longggggggggggg Wrapping'
lastName: 'Name'
age: '56'
}
];
return (
<Flex flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with hidden headers"
width="100%"
height="100%"
...props>
<TableHeader columns=columns>
(column) => (
<Column
hideHeader=columnkey === 'addInfo'
align=columnkey === 'age' ? 'end' : 'start'
showDivider=columnkey === 'addInfo'>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row key=itemid>
(key) =>
key === 'addInfo' ? (
<Cell>
<ActionButton isQuiet>
<Add />
</ActionButton>
</Cell>
) : (
<Cell>item[key]</Cell>
)
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
function TableExample(props) {
let columns = [
{name: 'First Name' key: 'firstName'}
{name: 'Last Name' key: 'lastName'}
{name: 'Add Info' key: 'addInfo'}
{name: 'Age' key: 'age'}
];
let rows = [
{
id: '1'
firstName: 'John'
lastName: 'Doe'
age: '45'
}
{
id: '2'
firstName: 'Jane'
lastName: 'Doe'
age: '37'
}
{
id: '3'
firstName: 'Joe'
lastName: 'Schmoe'
age: '67'
}
{
id: '4'
firstName: 'Joe'
lastName: 'Bloggs'
age: '12'
}
{
id: '5'
firstName: 'Longggggggggggg Wrapping'
lastName: 'Name'
age: '56'
}
];
return (
<Flex flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table with hidden headers"
width="100%"
height="100%"
...props>
<TableHeader columns=columns>
(column) => (
<Column
hideHeader=columnkey === 'addInfo'
align=columnkey === 'age' ? 'end' : 'start'
showDivider=columnkey === 'addInfo'>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row key=itemid>
(key) =>
key === 'addInfo' ? (
<Cell>
<ActionButton isQuiet>
<Add />
</ActionButton>
</Cell>
) : (
<Cell>item[key]</Cell>
)
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
function TableExample(
props
) {
let columns = [
{
name: 'First Name'
key: 'firstName'
}
{
name: 'Last Name'
key: 'lastName'
}
{
name: 'Add Info'
key: 'addInfo'
}
{
name: 'Age'
key: 'age'
}
];
let rows = [
{
id: '1'
firstName: 'John'
lastName: 'Doe'
age: '45'
}
{
id: '2'
firstName: 'Jane'
lastName: 'Doe'
age: '37'
}
{
id: '3'
firstName: 'Joe'
lastName: 'Schmoe'
age: '67'
}
{
id: '4'
firstName: 'Joe'
lastName: 'Bloggs'
age: '12'
}
{
id: '5'
firstName:
'Longggggggggggg Wrapping'
lastName: 'Name'
age: '56'
}
];
return (
<Flex
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="Example table with hidden headers"
width="100%"
height="100%"
...props>
<TableHeader
columns=
columns
>
(column) => (
<Column
hideHeader=
columnkey ===
'addInfo'
align=
columnkey ===
'age'
? 'end'
: 'start'
showDivider=
columnkey ===
'addInfo'
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=rows>
(item) => (
<Row
key=
itemid
>
(key) =>
key ===
'addInfo' ? (
<Cell>
<ActionButton
isQuiet>
<Add />
</ActionButton>
</Cell>
) : (
<Cell>
item[
key
]
</Cell>
)
</Row>
)
</TableBody>
</Table>
</Flex>
);
}
Quiet#
// Using same setup as hide header example
<TableExample isQuiet />// Using same setup as hide header example
<TableExample isQuiet />// Using same setup as hide header example
<TableExample
isQuiet
/>Disabled#
Use the disabledKeys prop to specify which rows to disable selection for in the Table.
// Using same setup as hide header example
<TableExample disabledKeys=['2' '3'] selectionMode="single" />// Using same setup as hide header example
<TableExample
disabledKeys=['2' '3']
selectionMode="single"
/>// Using same setup as hide header example
<TableExample
disabledKeys=[
'2'
'3'
]
selectionMode="single"
/>Density#
The amount of vertical padding that each row contains can be modified by providing the density prop.
// Using same setup as hide header example
<Flex direction="column" gap="size-300">
<TableExample density="compact" />
<TableExample density="spacious" />
</Flex>// Using same setup as hide header example
<Flex direction="column" gap="size-300">
<TableExample density="compact" />
<TableExample density="spacious" />
</Flex>// Using same setup as hide header example
<Flex
direction="column"
gap="size-300">
<TableExample density="compact" />
<TableExample density="spacious" />
</Flex>Overflow mode#
By default, text content that overflows its table cell will be truncated. You can have it wrap instead by passing overflowMode="wrap"
to the Table.
// Using same setup as hide header example
<TableExample overflowMode="wrap" />// Using same setup as hide header example
<TableExample overflowMode="wrap" />// Using same setup as hide header example
<TableExample overflowMode="wrap" />Empty state#
Use the renderEmptyState prop to customize what the Table will display if there are no rows provided.
import {Content} from '@react-spectrum/view';
import {IllustratedMessage} from '@react-spectrum/illustratedmessage';
import {Heading} from '@react-spectrum/text';
function renderEmptyState() {
return (
<IllustratedMessage>
<svg width="150" height="103" viewBox="0 0 150 103">
<path d="M133.7,8.5h-118c-1.9,0-3.5,1.6-3.5,3.5v27c0,0.8,0.7,1.5,1.5,1.5s1.5-0.7,1.5-1.5V23.5h119V92c0,0.3-0.2,0.5-0.5,0.5h-118c-0.3,0-0.5-0.2-0.5-0.5V69c0-0.8-0.7-1.5-1.5-1.5s-1.5,0.7-1.5,1.5v23c0,1.9,1.6,3.5,3.5,3.5h118c1.9,0,3.5-1.6,3.5-3.5V12C137.2,10.1,135.6,8.5,133.7,8.5z M15.2,21.5V12c0-0.3,0.2-0.5,0.5-0.5h118c0.3,0,0.5,0.2,0.5,0.5v9.5H15.2z M32.6,16.5c0,0.6-0.4,1-1,1h-10c-0.6,0-1-0.4-1-1s0.4-1,1-1h10C32.2,15.5,32.6,15.9,32.6,16.5z M13.6,56.1l-8.6,8.5C4.8,65,4.4,65.1,4,65.1c-0.4,0-0.8-0.1-1.1-0.4c-0.6-0.6-0.6-1.5,0-2.1l8.6-8.5l-8.6-8.5c-0.6-0.6-0.6-1.5,0-2.1c0.6-0.6,1.5-0.6,2.1,0l8.6,8.5l8.6-8.5c0.6-0.6,1.5-0.6,2.1,0c0.6,0.6,0.6,1.5,0,2.1L15.8,54l8.6,8.5c0.6,0.6,0.6,1.5,0,2.1c-0.3,0.3-0.7,0.4-1.1,0.4c-0.4,0-0.8-0.1-1.1-0.4L13.6,56.1z" />
</svg>
<Heading>No results</Heading>
<Content>No results found</Content>
</IllustratedMessage>
);
}
<Flex height="size-6000" flexGrow=1 maxWidth="size-6000">
<Table
aria-label="Example table for empty state"
width="100%"
height="100%"
renderEmptyState=renderEmptyState>
<TableHeader>
<Column>Name</Column>
<Column>Type</Column>
<Column>Size</Column>
</TableHeader>
<TableBody>[]</TableBody>
</Table>
</Flex>import {Content} from '@react-spectrum/view';
import {IllustratedMessage} from '@react-spectrum/illustratedmessage';
import {Heading} from '@react-spectrum/text';
function renderEmptyState() {
return (
<IllustratedMessage>
<svg width="150" height="103" viewBox="0 0 150 103">
<path d="M133.7,8.5h-118c-1.9,0-3.5,1.6-3.5,3.5v27c0,0.8,0.7,1.5,1.5,1.5s1.5-0.7,1.5-1.5V23.5h119V92c0,0.3-0.2,0.5-0.5,0.5h-118c-0.3,0-0.5-0.2-0.5-0.5V69c0-0.8-0.7-1.5-1.5-1.5s-1.5,0.7-1.5,1.5v23c0,1.9,1.6,3.5,3.5,3.5h118c1.9,0,3.5-1.6,3.5-3.5V12C137.2,10.1,135.6,8.5,133.7,8.5z M15.2,21.5V12c0-0.3,0.2-0.5,0.5-0.5h118c0.3,0,0.5,0.2,0.5,0.5v9.5H15.2z M32.6,16.5c0,0.6-0.4,1-1,1h-10c-0.6,0-1-0.4-1-1s0.4-1,1-1h10C32.2,15.5,32.6,15.9,32.6,16.5z M13.6,56.1l-8.6,8.5C4.8,65,4.4,65.1,4,65.1c-0.4,0-0.8-0.1-1.1-0.4c-0.6-0.6-0.6-1.5,0-2.1l8.6-8.5l-8.6-8.5c-0.6-0.6-0.6-1.5,0-2.1c0.6-0.6,1.5-0.6,2.1,0l8.6,8.5l8.6-8.5c0.6-0.6,1.5-0.6,2.1,0c0.6,0.6,0.6,1.5,0,2.1L15.8,54l8.6,8.5c0.6,0.6,0.6,1.5,0,2.1c-0.3,0.3-0.7,0.4-1.1,0.4c-0.4,0-0.8-0.1-1.1-0.4L13.6,56.1z" />
</svg>
<Heading>No results</Heading>
<Content>No results found</Content>
</IllustratedMessage>
);
}
<Flex
height="size-6000"
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="Example table for empty state"
width="100%"
height="100%"
renderEmptyState=renderEmptyState>
<TableHeader>
<Column>Name</Column>
<Column>Type</Column>
<Column>Size</Column>
</TableHeader>
<TableBody>[]</TableBody>
</Table>
</Flex>import {Content} from '@react-spectrum/view';
import {IllustratedMessage} from '@react-spectrum/illustratedmessage';
import {Heading} from '@react-spectrum/text';
function renderEmptyState() {
return (
<IllustratedMessage>
<svg
width="150"
height="103"
viewBox="0 0 150 103">
<path d="M133.7,8.5h-118c-1.9,0-3.5,1.6-3.5,3.5v27c0,0.8,0.7,1.5,1.5,1.5s1.5-0.7,1.5-1.5V23.5h119V92c0,0.3-0.2,0.5-0.5,0.5h-118c-0.3,0-0.5-0.2-0.5-0.5V69c0-0.8-0.7-1.5-1.5-1.5s-1.5,0.7-1.5,1.5v23c0,1.9,1.6,3.5,3.5,3.5h118c1.9,0,3.5-1.6,3.5-3.5V12C137.2,10.1,135.6,8.5,133.7,8.5z M15.2,21.5V12c0-0.3,0.2-0.5,0.5-0.5h118c0.3,0,0.5,0.2,0.5,0.5v9.5H15.2z M32.6,16.5c0,0.6-0.4,1-1,1h-10c-0.6,0-1-0.4-1-1s0.4-1,1-1h10C32.2,15.5,32.6,15.9,32.6,16.5z M13.6,56.1l-8.6,8.5C4.8,65,4.4,65.1,4,65.1c-0.4,0-0.8-0.1-1.1-0.4c-0.6-0.6-0.6-1.5,0-2.1l8.6-8.5l-8.6-8.5c-0.6-0.6-0.6-1.5,0-2.1c0.6-0.6,1.5-0.6,2.1,0l8.6,8.5l8.6-8.5c0.6-0.6,1.5-0.6,2.1,0c0.6,0.6,0.6,1.5,0,2.1L15.8,54l8.6,8.5c0.6,0.6,0.6,1.5,0,2.1c-0.3,0.3-0.7,0.4-1.1,0.4c-0.4,0-0.8-0.1-1.1-0.4L13.6,56.1z" />
</svg>
<Heading>
No results
</Heading>
<Content>
No results found
</Content>
</IllustratedMessage>
);
}
<Flex
height="size-6000"
flexGrow=1
maxWidth="size-6000">
<Table
aria-label="Example table for empty state"
width="100%"
height="100%"
renderEmptyState=
renderEmptyState
>
<TableHeader>
<Column>
Name
</Column>
<Column>
Type
</Column>
<Column>
Size
</Column>
</TableHeader>
<TableBody>
[]
</TableBody>
</Table>
</Flex>