TableView
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-beta.0 |
| usage | import {Cell, Column, Row, TableView, TableBody, TableHeader} from '@react-spectrum/table' |
Example#
<Flex flexGrow=1 maxWidth="size-6000">
<TableView
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>
</TableView>
</Flex><Flex flexGrow=1 maxWidth="size-6000">
<TableView
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>
</TableView>
</Flex><Flex
flexGrow=1
maxWidth="size-6000">
<TableView
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>
</TableView>
</Flex>Content#
TableView 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 TableView, 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 TableView 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 TableView 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.
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'}
];
<TableView
aria-label="Example table with dynamic content"
height="size-3000"
maxWidth="size-6000">
<TableHeader columns=columns>
(column) => (
<Column
key=columnuid
align=columnuid === 'date' ? 'end' : 'start'>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => <Row>(columnKey) => <Cell>item[columnKey]</Cell></Row>
</TableBody>
</TableView>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'
}
];
<TableView
aria-label="Example table with dynamic content"
height="size-3000"
maxWidth="size-6000">
<TableHeader columns=columns>
(column) => (
<Column
key=columnuid
align=columnuid === 'date' ? 'end' : 'start'>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row>
(columnKey) => <Cell>item[columnKey]</Cell>
</Row>
)
</TableBody>
</TableView>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'
}
];
<TableView
aria-label="Example table with dynamic content"
height="size-3000"
maxWidth="size-6000">
<TableHeader
columns=columns>
(column) => (
<Column
key=
columnuid
align=
columnuid ===
'date'
? 'end'
: 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=rows>
(item) => (
<Row>
(
columnKey
) => (
<Cell>
item[
columnKey
]
</Cell>
)
</Row>
)
</TableBody>
</TableView>Internationalization#
To internationalize a TableView, all text content within the TableView should be replaced with localized strings. This includes the aria-label provided to the TableView if any.
For languages that are read right-to-left (e.g. Hebrew and Arabic), the layout of TableView is automatically flipped.
Labeling#
Accessibility#
An aria-label must be provided to the TableView for accessibility. If the TableView 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 TableView 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 TableView.
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">
<TableView
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>
</TableView>
</Flex><Flex flexGrow=1 maxWidth="size-3000">
<TableView
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>
</TableView>
</Flex><Flex
flexGrow=1
maxWidth="size-3000">
<TableView
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>
</TableView>
</Flex>Asynchronous loading#
TableView supports loading data asynchronously, and will display a progress circle reflecting the current load state,
set by the loadingState 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">
<TableView
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
loadingState=listloadingState
onLoadMore=listloadMore>
(item) => (
<Row key=itemname>(key) => <Cell>item[key]</Cell></Row>
)
</TableBody>
</TableView>
</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">
<TableView
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
loadingState=listloadingState
onLoadMore=listloadMore>
(item) => (
<Row key=itemname>
(key) => <Cell>item[key]</Cell>
</Row>
)
</TableBody>
</TableView>
</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">
<TableView
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
loadingState=
listloadingState
onLoadMore=
listloadMore
>
(item) => (
<Row
key=
itemname
>
(key) => (
<Cell>
item[
key
]
</Cell>
)
</Row>
)
</TableBody>
</TableView>
</Flex>
);
}
Selection#
By default, TableView 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 multiple selection mode, and uses defaultSelectedKeys to select the rows with keys "2" and "4".
<TableView
aria-label="Example table with multiple selection"
maxWidth="size-6000"
height="size-3000"
selectionMode="multiple"
defaultSelectedKeys=['2' '4']>
<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>
</TableView><TableView
aria-label="Example table with multiple selection"
maxWidth="size-6000"
height="size-3000"
selectionMode="multiple"
defaultSelectedKeys=['2' '4']>
<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>
</TableView><TableView
aria-label="Example table with multiple selection"
maxWidth="size-6000"
height="size-3000"
selectionMode="multiple"
defaultSelectedKeys=[
'2'
'4'
]>
<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>
</TableView>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.
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 [selectedKeys setSelectedKeys] = ReactuseState(new Set([2]));
return (
<TableView
aria-label="Table with controlled selection"
maxWidth="size-6000"
height="size-3000"
selectionMode="multiple"
selectedKeys=selectedKeys
onSelectionChange=setSelectedKeys
...props>
<TableHeader columns=columns>
(column) => (
<Column
key=columnuid
align=columnuid === 'level' ? 'end' : 'start'>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => <Row>(columnKey) => <Cell>item[columnKey]</Cell></Row>
</TableBody>
</TableView>
);
}
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 (
<TableView
aria-label="Table with controlled selection"
maxWidth="size-6000"
height="size-3000"
selectionMode="multiple"
selectedKeys=selectedKeys
onSelectionChange=setSelectedKeys
...props>
<TableHeader columns=columns>
(column) => (
<Column
key=columnuid
align=
columnuid === 'level' ? 'end' : 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row>
(columnKey) => <Cell>item[columnKey]</Cell>
</Row>
)
</TableBody>
</TableView>
);
}
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 (
<TableView
aria-label="Table with controlled selection"
maxWidth="size-6000"
height="size-3000"
selectionMode="multiple"
selectedKeys=
selectedKeys
onSelectionChange=
setSelectedKeys
...props>
<TableHeader
columns=
columns
>
(column) => (
<Column
key=
columnuid
align=
columnuid ===
'level'
? 'end'
: 'start'
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=rows>
(item) => (
<Row>
(
columnKey
) => (
<Cell>
item[
columnKey
]
</Cell>
)
</Row>
)
</TableBody>
</TableView>
);
}
Single selection#
To limit users to selecting only a single item at a time, selectionMode can be set to single.
// Using the same table as above
<PokemonTable selectionMode="single" />// Using the same table as above
<PokemonTable selectionMode="single" />// Using the same table as above
<PokemonTable selectionMode="single" />Disallow empty selection#
TableView also supports a disallowEmptySelection prop which forces the user to have at least one row in the TableView 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 same table as above
<PokemonTable disallowEmptySelection />// Using the same table as above
<PokemonTable disallowEmptySelection />// Using the same table as above
<PokemonTable
disallowEmptySelection
/>Disabled rows#
You can disable specific rows by providing an array of keys to TableView via the disabledKeys prop. This will prevent rows from being
selectable as shown in the example below.
// 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#
TableView 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 TableView 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.
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 (
<TableView
aria-label="Example table with client side sorting"
height="size-3000"
maxWidth="size-6000"
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 loadingState=listloadingState>
(item) => (
<Row key=itemname>
(columnKey) => <Cell>item[columnKey]</Cell>
</Row>
)
</TableBody>
</TableView>
);
}
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 (
<TableView
aria-label="Example table with client side sorting"
height="size-3000"
maxWidth="size-6000"
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
loadingState=listloadingState>
(item) => (
<Row key=itemname>
(columnKey) => <Cell>item[columnKey]</Cell>
</Row>
)
</TableBody>
</TableView>
);
}
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 (
<TableView
aria-label="Example table with client side sorting"
height="size-3000"
maxWidth="size-6000"
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
loadingState=
listloadingState
>
(item) => (
<Row
key=
itemname
>
(
columnKey
) => (
<Cell>
item[
columnKey
]
</Cell>
)
</Row>
)
</TableBody>
</TableView>
);
}
Props#
TableView props#
| Name | Type | Default | Description |
children | [
ReactElement<TableHeaderProps<T>>,
ReactElement<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 cell. |
overflowMode | 'wrap' | 'truncate' | 'truncate' | Sets the overflow behavior for the cell contents. |
isQuiet | boolean | — | Whether the TableView should be displayed with a quiet style. |
renderEmptyState | () => JSX.Element | — | Sets what the TableView 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 | Responsive<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 | Responsive<number> | — | When used in a flex layout, specifies how the element will grow to fit the space available. See MDN. |
flexShrink | Responsive<number> | — | When used in a flex layout, specifies how the element will shrink to fit the space available. See MDN. |
flexBasis | Responsive<number | string> | — | When used in a flex layout, specifies the initial main size of the element. See MDN. |
alignSelf | Responsive<'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 | Responsive<'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 | Responsive<number> | — | The layout order for the element within a flex or grid container. See MDN. |
gridArea | Responsive<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 | Responsive<string> | — | When used in a grid layout, specifies the column the element should be placed in within the grid. See MDN. |
gridRow | Responsive<string> | — | When used in a grid layout, specifies the row the element should be placed in within the grid. See MDN. |
gridColumnStart | Responsive<string> | — | When used in a grid layout, specifies the starting column to span within the grid. See MDN. |
gridColumnEnd | Responsive<string> | — | When used in a grid layout, specifies the ending column to span within the grid. See MDN. |
gridRowStart | Responsive<string> | — | When used in a grid layout, specifies the starting row to span within the grid. See MDN. |
gridRowEnd | Responsive<string> | — | When used in a grid layout, specifies the ending row to span within the grid. See MDN. |
Spacing
| Name | Type | Default | Description |
margin | Responsive<DimensionValue> | — | The margin for all four sides of the element. See MDN. |
marginTop | Responsive<DimensionValue> | — | The margin for the top side of the element. See MDN. |
marginBottom | Responsive<DimensionValue> | — | The margin for the bottom side of the element. See MDN. |
marginStart | Responsive<DimensionValue> | — | The margin for the logical start side of the element, depending on layout direction. See MDN. |
marginEnd | Responsive<DimensionValue> | — | The margin for the logical end side of an element, depending on layout direction. See MDN. |
marginX | Responsive<DimensionValue> | — | The margin for both the left and right sides of the element. See MDN. |
marginY | Responsive<DimensionValue> | — | The margin for both the top and bottom sides of the element. See MDN. |
Sizing
| Name | Type | Default | Description |
width | Responsive<DimensionValue> | — | The width of the element. See MDN. |
minWidth | Responsive<DimensionValue> | — | The minimum width of the element. See MDN. |
maxWidth | Responsive<DimensionValue> | — | The maximum width of the element. See MDN. |
height | Responsive<DimensionValue> | — | The height of the element. See MDN. |
minHeight | Responsive<DimensionValue> | — | The minimum height of the element. See MDN. |
maxHeight | Responsive<DimensionValue> | — | The maximum height of the element. See MDN. |
Positioning
| Name | Type | Default | Description |
position | Responsive<'static'
| 'relative'
| 'absolute'
| 'fixed'
| 'sticky'> | — | Specifies how the element is positioned. See MDN. |
top | Responsive<DimensionValue> | — | The top position for the element. See MDN. |
bottom | Responsive<DimensionValue> | — | The bottom position for the element. See MDN. |
left | Responsive<DimensionValue> | — | The left position for the element. See MDN. Consider using start instead for RTL support. |
right | Responsive<DimensionValue> | — | The right position for the element. See MDN. Consider using start instead for RTL support. |
start | Responsive<DimensionValue> | — | The logical start position for the element, depending on layout direction. See MDN. |
end | Responsive<DimensionValue> | — | The logical end position for the element, depending on layout direction. See MDN. |
zIndex | Responsive<number> | — | The stacking order for the element. See MDN. |
isHidden | Responsive<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. |
TableHeader 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. |
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. Note that this prop is specifically for columns that contain ActionButtons in place of text content. |
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. |
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. |
TableBody 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. |
loadingState | LoadingState | — | The current loading state of the table. |
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. |
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">
<TableView
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>
</TableView>
</Flex><Flex flexGrow=1 maxWidth="size-4600">
<TableView
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>
</TableView>
</Flex><Flex
flexGrow=1
maxWidth="size-4600">
<TableView
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>
</TableView>
</Flex>Column widths#
Columns support three different width props: minWidth, width, and maxWidth.
<Flex flexGrow=1 maxWidth="size-4600">
<TableView
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>
</TableView>
</Flex><Flex flexGrow=1 maxWidth="size-4600">
<TableView
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>
</TableView>
</Flex><Flex
flexGrow=1
maxWidth="size-4600">
<TableView
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>
</TableView>
</Flex>Column dividers#
<Flex flexGrow=1 maxWidth="size-4600">
<TableView
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>
</TableView>
</Flex><Flex flexGrow=1 maxWidth="size-4600">
<TableView
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>
</TableView>
</Flex><Flex
flexGrow=1
maxWidth="size-4600">
<TableView
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>
</TableView>
</Flex>Focusable cells#
Cells accept any renderable node, allowing you to have focusable children within the TableView.
import Edit from '@spectrum-icons/workflow/Edit';
import Delete from '@spectrum-icons/workflow/Delete';
<Flex maxWidth="size-3000" flexGrow=1>
<TableView
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>
</TableView>
</Flex>import Edit from '@spectrum-icons/workflow/Edit';
import Delete from '@spectrum-icons/workflow/Delete';
<Flex maxWidth="size-3000" flexGrow=1>
<TableView
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>
</TableView>
</Flex>import Edit from '@spectrum-icons/workflow/Edit';
import Delete from '@spectrum-icons/workflow/Delete';
<Flex
maxWidth="size-3000"
flexGrow=1>
<TableView
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>
</TableView>
</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. Note that the hideHeader prop is specifically intended for columns that contain ActionButtons instead
of text content.
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">
<TableView
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>
</TableView>
</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">
<TableView
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>
</TableView>
</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">
<TableView
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>
</TableView>
</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
/>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 TableView.
// 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 TableView 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">
<TableView
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>
</TableView>
</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">
<TableView
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>
</TableView>
</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">
<TableView
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>
</TableView>
</Flex>Nested columns#
TableView supports nesting columns, allowing you to create column groups, or "tiered" column headers.
<TableView
aria-label="Example table for nested columns"
maxWidth="size-6000"
height="size-3000">
<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>
</TableView><TableView
aria-label="Example table for nested columns"
maxWidth="size-6000"
height="size-3000">
<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>
</TableView><TableView
aria-label="Example table for nested columns"
maxWidth="size-6000"
height="size-3000">
<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>
</TableView>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'}
];
<TableView
aria-label="Example table for nested columns"
maxWidth="size-6000"
height="size-3000">
<TableHeader columns=columns>
(column) => (
<Column isRowHeader=columnisRowHeader childColumns=columnchildren>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => <Row>(columnKey) => <Cell>item[columnKey]</Cell></Row>
</TableBody>
</TableView>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'
}
];
<TableView
aria-label="Example table for nested columns"
maxWidth="size-6000"
height="size-3000">
<TableHeader columns=columns>
(column) => (
<Column
isRowHeader=columnisRowHeader
childColumns=columnchildren>
columnname
</Column>
)
</TableHeader>
<TableBody items=rows>
(item) => (
<Row>
(columnKey) => <Cell>item[columnKey]</Cell>
</Row>
)
</TableBody>
</TableView>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'
}
];
<TableView
aria-label="Example table for nested columns"
maxWidth="size-6000"
height="size-3000">
<TableHeader
columns=columns>
(column) => (
<Column
isRowHeader=
columnisRowHeader
childColumns=
columnchildren
>
columnname
</Column>
)
</TableHeader>
<TableBody
items=rows>
(item) => (
<Row>
(
columnKey
) => (
<Cell>
item[
columnKey
]
</Cell>
)
</Row>
)
</TableBody>
</TableView>