alpha

Table

Tables are containers for displaying information. They allow users to quickly scan, sort, compare, and take action on large amounts of data.

installyarn add @react-spectrum/table
version3.0.0-alpha.11
usageimport {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={column.uid}
              align={column.uid === 'date' ? 'end' : 'start'}>
              {column.name}
            </Column>
          )}
        </TableHeader>
        <TableBody items={rows}>
          {(item) => (
            <Row key={item.id}>
              {/* 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={column.uid}
              align={
                column.uid === 'date' ? 'end' : 'start'
              }>
              {column.name}
            </Column>
          )}
        </TableHeader>
        <TableBody items={rows}>
          {(item) => (
            <Row key={item.id}>
              {/* 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={
                column.uid
              }
              align={
                column.uid ===
                'date'
                  ? 'end'
                  : 'start'
              }>
              {
                column.name
              }
            </Column>
          )}
        </TableHeader>
        <TableBody
          items={rows}>
          {(item) => (
            <Row
              key={
                item.id
              }>
              {/* 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 = cursor.replace(/^http:\/\//i, 'https://');
      }

      let res = await fetch(cursor || `https://swapi.dev/api/people/?search=`, {
        signal
      });
      let json = await res.json();

      return {
        items: json.results,
        cursor: json.next
      };
    }
  });

  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={column.key !== 'name' ? 'end' : 'start'}>
              {column.name}
            </Column>
          )}
        </TableHeader>
        <TableBody
          items={list.items}
          isLoading={list.isLoading}
          onLoadMore={list.loadMore}>
          {(item) => (
            <Row key={item.name}>{(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 = cursor.replace(/^http:\/\//i, 'https://');
      }

      let res = await fetch(
        cursor || `https://swapi.dev/api/people/?search=`,
        {signal}
      );
      let json = await res.json();

      return {
        items: json.results,
        cursor: json.next
      };
    }
  });

  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={
                column.key !== 'name' ? 'end' : 'start'
              }>
              {column.name}
            </Column>
          )}
        </TableHeader>
        <TableBody
          items={list.items}
          isLoading={list.isLoading}
          onLoadMore={list.loadMore}>
          {(item) => (
            <Row key={item.name}>
              {(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 = cursor.replace(
            /^http:\/\//i,
            'https://'
          );
        }

        let res = await fetch(
          cursor ||
            `https://swapi.dev/api/people/?search=`,
          {signal}
        );
        let json = await res.json();

        return {
          items:
            json.results,
          cursor:
            json.next
        };
      }
    }
  );

  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={
                column.key !==
                'name'
                  ? 'end'
                  : 'start'
              }>
              {
                column.name
              }
            </Column>
          )}
        </TableHeader>
        <TableBody
          items={
            list.items
          }
          isLoading={
            list.isLoading
          }
          onLoadMore={
            list.loadMore
          }>
          {(item) => (
            <Row
              key={
                item.name
              }>
              {(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] = React.useState(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={column.uid}
              align={column.uid === 'level' ? 'end' : 'start'}>
              {column.name}
            </Column>
          )}
        </TableHeader>
        <TableBody items={rows}>
          {(item) => (
            <Row key={item.id}>{(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] = React.useState(
    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={column.uid}
              align={
                column.uid === 'level' ? 'end' : 'start'
              }>
              {column.name}
            </Column>
          )}
        </TableHeader>
        <TableBody items={rows}>
          {(item) => (
            <Row key={item.id}>
              {(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
  ] = React.useState(
    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={
                column.uid
              }
              align={
                column.uid ===
                'level'
                  ? 'end'
                  : 'start'
              }>
              {
                column.name
              }
            </Column>
          )}
        </TableHeader>
        <TableBody
          items={rows}>
          {(item) => (
            <Row
              key={
                item.id
              }>
              {(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 = cursor.replace(/^http:\/\//i, 'https://');
      }

      let res = await fetch(cursor || `https://swapi.dev/api/people/?search`, {
        signal
      });
      let json = await res.json();

      return {
        items: json.results,
        cursor: json.next
      };
    },
    async sort({items, sortDescriptor}) {
      let sorted = items.sort((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 (sortDescriptor.direction === '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={list.sortDescriptor}
        onSortChange={list.sort}>
        <TableHeader columns={columns}>
          {(column) => (
            <Column
              allowsSorting
              align={column.key !== 'name' ? 'end' : 'start'}>
              {column.name}
            </Column>
          )}
        </TableHeader>
        <TableBody
          items={list.items}
          isLoading={list.isLoading}
          onLoadMore={list.loadMore}>
          {(item) => (
            <Row key={item.name}>{(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 = cursor.replace(/^http:\/\//i, 'https://');
      }

      let res = await fetch(
        cursor || `https://swapi.dev/api/people/?search`,
        {signal}
      );
      let json = await res.json();

      return {
        items: json.results,
        cursor: json.next
      };
    },
    async sort({items, sortDescriptor}) {
      let sorted = items.sort((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 (sortDescriptor.direction === '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={list.sortDescriptor}
        onSortChange={list.sort}>
        <TableHeader columns={columns}>
          {(column) => (
            <Column
              allowsSorting
              align={
                column.key !== 'name' ? 'end' : 'start'
              }>
              {column.name}
            </Column>
          )}
        </TableHeader>
        <TableBody
          items={list.items}
          isLoading={list.isLoading}
          onLoadMore={list.loadMore}>
          {(item) => (
            <Row key={item.name}>
              {(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 = cursor.replace(
            /^http:\/\//i,
            'https://'
          );
        }

        let res = await fetch(
          cursor ||
            `https://swapi.dev/api/people/?search`,
          {signal}
        );
        let json = await res.json();

        return {
          items:
            json.results,
          cursor:
            json.next
        };
      },
      async sort({
        items,
        sortDescriptor
      }) {
        let sorted = items.sort(
          (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 (
              sortDescriptor.direction ===
              '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={
          list.sortDescriptor
        }
        onSortChange={
          list.sort
        }>
        <TableHeader
          columns={
            columns
          }>
          {(column) => (
            <Column
              allowsSorting
              align={
                column.key !==
                'name'
                  ? 'end'
                  : 'start'
              }>
              {
                column.name
              }
            </Column>
          )}
        </TableHeader>
        <TableBody
          items={
            list.items
          }
          isLoading={
            list.isLoading
          }
          onLoadMore={
            list.loadMore
          }>
          {(item) => (
            <Row
              key={
                item.name
              }>
              {(key) => (
                <Cell>
                  {
                    item[
                      key
                    ]
                  }
                </Cell>
              )}
            </Row>
          )}
        </TableBody>
      </Table>
    </Flex>
  );
}

Props#


Table props#

NameTypeDefaultDescription
childrenReactElement<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.
isQuietbooleanWhether the Table should be displayed with a quiet style.
renderEmptyState() => JSX.ElementSets what the Table should render when there is no content to display.
disabledKeysIterable<Key>A list of row keys to disable.
selectionModeSelectionModeThe type of selection that is allowed in the collection.
disallowEmptySelectionbooleanWhether 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).
sortDescriptorSortDescriptorThe current sorted column and direction.
Events
NameTypeDefaultDescription
onSelectionChange( (keys: Selection )) => anyHandler that is called when the selection changes.
onSortChange( (descriptor: SortDescriptor )) => anyHandler that is called when the sorted column or direction changes.
Layout
NameTypeDefaultDescription
flexstringnumberbooleanWhen used in a flex layout, specifies how the element will grow or shrink to fit the space available. See MDN.
flexGrownumberWhen used in a flex layout, specifies how the element will grow to fit the space available. See MDN.
flexShrinknumberWhen used in a flex layout, specifies how the element will shrink to fit the space available. See MDN.
flexBasisnumberstringWhen 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.
ordernumberThe layout order for the element within a flex or grid container. See MDN.
gridAreastringWhen used in a grid layout, specifies the named grid area that the element should be placed in within the grid. See MDN.
gridColumnstringWhen used in a grid layout, specifies the column the element should be placed in within the grid. See MDN.
gridRowstringWhen used in a grid layout, specifies the row the element should be placed in within the grid. See MDN.
gridColumnStartstringWhen used in a grid layout, specifies the starting column to span within the grid. See MDN.
gridColumnEndstringWhen used in a grid layout, specifies the ending column to span within the grid. See MDN.
gridRowStartstringWhen used in a grid layout, specifies the starting row to span within the grid. See MDN.
gridRowEndstringWhen used in a grid layout, specifies the ending row to span within the grid. See MDN.
Spacing
NameTypeDefaultDescription
marginDimensionValueThe margin for all four sides of the element. See MDN.
marginTopDimensionValueThe margin for the top side of the element. See MDN.
marginBottomDimensionValueThe margin for the bottom side of the element. See MDN.
marginStartDimensionValueThe margin for the logical start side of the element, depending on layout direction. See MDN.
marginEndDimensionValueThe margin for the logical end side of an element, depending on layout direction. See MDN.
marginXDimensionValueThe margin for both the left and right sides of the element. See MDN.
marginYDimensionValueThe margin for both the top and bottom sides of the element. See MDN.
Sizing
NameTypeDefaultDescription
widthDimensionValueThe width of the element. See MDN.
minWidthDimensionValueThe minimum width of the element. See MDN.
maxWidthDimensionValueThe maximum width of the element. See MDN.
heightDimensionValueThe height of the element. See MDN.
minHeightDimensionValueThe minimum height of the element. See MDN.
maxHeightDimensionValueThe maximum height of the element. See MDN.
Positioning
NameTypeDefaultDescription
position'static''relative''absolute''fixed''sticky'Specifies how the element is positioned. See MDN.
topDimensionValueThe top position for the element. See MDN.
bottomDimensionValueThe bottom position for the element. See MDN.
leftDimensionValueThe left position for the element. See MDN. Consider using start instead for RTL support.
rightDimensionValueThe right position for the element. See MDN. Consider using start instead for RTL support.
startDimensionValueThe logical start position for the element, depending on layout direction. See MDN.
endDimensionValueThe logical end position for the element, depending on layout direction. See MDN.
zIndexnumberThe stacking order for the element. See MDN.
isHiddenbooleanHides the element.
Accessibility
NameTypeDefaultDescription
idstringThe element's unique identifier. See MDN.
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.
Advanced
NameTypeDefaultDescription
UNSAFE_classNamestringSets the CSS className for the element. Only use as a last resort. Use style props instead.
UNSAFE_styleCSSPropertiesSets inline style for the element. Only use as a last resort. Use style props instead.

Table header props#

NameTypeDefaultDescription
childrenColumnElement<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.
columnsT[]A list of Table columns.

Column props#

NameTypeDefaultDescription
childrenReactNodeColumnElement<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.
allowsSortingbooleanWhether the column allows sorting.
isRowHeaderbooleanWhether a column is a row header and should be announced by assistive technology during row navigation.
showDividerbooleanWhether the column should render a divider between it and the next column.
hideHeaderbooleanWhether 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.
titleReactNodeRendered contents of the column if children contains child columns.
childColumnsT[]A list of child columns used when dynamically rendering nested child columns.
defaultWidthnumberstringThe default width of the column.
Sizing
NameTypeDefaultDescription
widthnumberstringAn accessibility label for the column. The width of the column.
minWidthnumberstringThe minimum width of the column.
maxWidthnumberstringThe maximum width of the column.

Table body props#

NameTypeDefaultDescription
childrenCollectionChildren<T>The contents of the table body. Supports static items or a function for dynamic rendering.
itemsIterable<T>A list of row objects in the table body used when dynamically rendering rows.
isLoadingbooleanWhether the items are currently loading.
Events
NameTypeDefaultDescription
onLoadMore() => anyHandler that is called when more items should be loaded, e.g. while scrolling near the bottom.

Row props#

NameTypeDefaultDescription
childrenCellElementCellElement[]CellRendererRendered contents of the row or row child items.
childItemsIterable<T>A list of child item objects used when dynamically rendering row children.
hasChildItemsbooleanWhether this row has children, even if not loaded yet.
textValuestringA string representation of the row's contents, used for features like typeahead.

Cell props#

NameTypeDefaultDescription
childrenReactNodeThe contents of the cell.
textValuestringA string representation of the cell's contents, used for features like typeahead.

Visual options#


Column alignment#

View guidelines

<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#

View guidelines

<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={column.key === 'addInfo'}
              align={column.key === 'age' ? 'end' : 'start'}
              showDivider={column.key === 'addInfo'}>
              {column.name}
            </Column>
          )}
        </TableHeader>
        <TableBody items={rows}>
          {(item) => (
            <Row key={item.id}>
              {(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={column.key === 'addInfo'}
              align={column.key === 'age' ? 'end' : 'start'}
              showDivider={column.key === 'addInfo'}>
              {column.name}
            </Column>
          )}
        </TableHeader>
        <TableBody items={rows}>
          {(item) => (
            <Row key={item.id}>
              {(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={
                column.key ===
                'addInfo'
              }
              align={
                column.key ===
                'age'
                  ? 'end'
                  : 'start'
              }
              showDivider={
                column.key ===
                'addInfo'
              }>
              {
                column.name
              }
            </Column>
          )}
        </TableHeader>
        <TableBody
          items={rows}>
          {(item) => (
            <Row
              key={
                item.id
              }>
              {(key) =>
                key ===
                'addInfo' ? (
                  <Cell>
                    <ActionButton
                      isQuiet>
                      <Add />
                    </ActionButton>
                  </Cell>
                ) : (
                  <Cell>
                    {
                      item[
                        key
                      ]
                    }
                  </Cell>
                )
              }
            </Row>
          )}
        </TableBody>
      </Table>
    </Flex>
  );
}

Quiet#

View guidelines

// 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>