Components

Table

Table displays structured data with sortable columns and selectable rows.

Examples #

Import Syntax #

import Table from 'mineral-ui/Table';

Note

Unless otherwise declared, the examples below use the same data structure as shown in the basic example.

Basic Usage #

Pass Table an array of row objects, where each property key will be used as a column header. It's also recommended to pass a rowKey, which is the row property key used to uniquely identify each row.

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonsFiddleheadsQuinoaMilbenkaseSpirulina
() => {
  const data = [
    {
      Fruits: 'Pomello',
      Vegetables: 'Bok Choi',
      Grains: 'Chia',
      Dairy: 'Pule',
      Protein: 'Crickets'
    },
    {
      Fruits: 'Starfruit',
      Vegetables: 'Romanesco',
      Grains: 'Sorghum',
      Dairy: 'Casu marzu',
      Protein: 'Barnacles'
    },
    {
      Fruits: 'Durian',
      Vegetables: 'Ramps',
      Grains: 'Teff',
      Dairy: 'Vieux Lille',
      Protein: 'Inca nuts'
    },
    {
      Fruits: 'Persimmons',
      Vegetables: 'Fiddleheads',
      Grains: 'Quinoa',
      Dairy: 'Milbenkase',
      Protein: 'Spirulina'
    }
  ];

  return <Table
           data={data}
           rowKey="Fruits"
           title="Foods of the World"
           hideTitle />;
}

Column Definition #

In addition to data, you can pass an array of column definition objects, detailed in the API.

Foods of the World

Fresh FruitsVeritable VegetablesGood GrainsDelectable DairyPowerful Protein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
() => {
  const columns = [
    { content: 'Fresh Fruits', key: 'Fruits' },
    { content: 'Veritable Vegetables', key: 'Vegetables' },
    { content: 'Good Grains', key: 'Grains' },
    { content: 'Delectable Dairy', key: 'Dairy' },
    { content: 'Powerful Protein', key: 'Protein' }
  ];

  return (
    <Table
      columns={columns}
      data={data}
      rowKey="Fruits"
      title="Foods of the World"
      hideTitle />
  );
}

Pagination #

Table can be used in conjunction with Pagination by passing sliced data to Table, calculated from the current page and page size.

Minerals

MineralColorLusterCrystal SystemCrystal Habit
Malachitegreenadamantinemonoclinicbotryoidal, stalactitic
Fluoritecolorlessvitreousisometriccoarse, granular
Magnetiteblackmetallicisometricoctahedral
() => {
  const pageSize = 3;

  class PaginatedTable extends Component {
    constructor(props) {
      super(props);

      this.state = {
        currentPage: 1,
      };

      this.handlePageChange = this.handlePageChange.bind(this);
    }

    handlePageChange(currentPage) {
      this.setState({ currentPage });
    }

    render () {
      const { currentPage } = this.state;
      const firstRow = (currentPage - 1) * pageSize;
      const lastRow = (currentPage - 1) * pageSize + pageSize;
      const slicedData = this.props.data.slice(firstRow, lastRow);

      return (
        <DemoLayout>
          <Table
            data={slicedData}
            rowKey="name"
            columns={columns}
            title="Minerals"
            hideTitle
          />
          <Pagination
            currentPage={currentPage}
            onPageChange={this.handlePageChange}
            pageSize={pageSize}
            totalCount={data.length}
          />
        </DemoLayout>
      );
    }
  }

  return <PaginatedTable data={data}/>;
}

Sortable Columns #

Users can sort the rows in Table by column, enabled via the sortable prop. If the default sort comparator, below, is insufficient for your needs, you can supply your own with the sortComparator prop. You can set the initially sorted column & direction with the defaultSort prop. An onSort callback is also available. Note that the sortable & sortComparator properties can be applied to Table via props or to individual columns via column definition.

// Coerce null & undefined values to an empty string and normalize letter casing
const normalizedValue = (value) =>
  value === null || value === undefined
    ? ''
    : typeof value === 'string' ? value.toUpperCase() : value;

const defaultSortComparator: SortComparator = (a, b, key) => {
  const valueA = normalizedValue(a[key]);
  const valueB = normalizedValue(b[key]);

  return valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
};

This is available as a named export from the component:

import Table, { defaultSortComparator } from 'mineral-ui/Table';

In the example below, sorting is enabled for all columns except Protein, and the Dairy column sorts by length of the string rather than alphabetically.

Foods of the World

Protein
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
() => {
  const sortByLength = (a, b, key) => {
    const lengthA = a[key].length;
    const lengthB = b[key].length;
    return lengthA < lengthB ? -1 : lengthA > lengthB ? 1 : 0;
  };

  const columns = [
    { content: 'Fruits', key: 'Fruits' },
    { content: 'Vegetables', key: 'Vegetables' },
    { content: 'Grains', key: 'Grains' },
    { content: 'Dairy', key: 'Dairy', sortComparator: sortByLength },
    { content: 'Protein', key: 'Protein', sortable: false }
  ];

  return (
    <Table
      columns={columns}
      data={sharedData}
      rowKey="Fruits"
      defaultSort={{ key: 'Fruits' }}
      sortable
      title="Foods of the World"
      hideTitle
    />
  );
}

Sortable Columns — Controlled #

Table controls its own sort state by default. It can optionally be managed by the application as a controlled component via the control prop, sort. Two details of note:

  1. defaultSortComparator is available from Table.
  2. Because a controlled sortable Table does no sorting on its own, it also does not use any sortComparators defined in either props or column definitions. The example below illustrates one way to define and use custom sort comparator functions, with a comparators object.

Foods of the World

DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
() => {
  const evenRows = data.filter((row, index) => (index + 1)%2 === 0);
  const oddRows = data.filter((row, index) => (index + 1)%2 !== 0);

  const comparators = {
    // See the 'Sortable Columns' example for sortByLength implementation
    Dairy: sortByLength
  }

  const sortFn = (data, sort) =>
    sort
      ? data.slice(0).sort((a, b) => {
          const comparator = comparators[sort.key] || defaultSortComparator;
          const asc = comparator(a, b, sort.key);
          const desc = asc * -1;
          return sort.descending ? desc : asc;
        })
      : data;

  const initialState = {
    sort: { key: 'Fruits' }
  };

  class MyTable extends Component {
    constructor(props) {
      super(props);

      this.state = initialState;

      this.handleSort = this.handleSort.bind(this);
      this.reset = this.reset.bind(this);
      this.sortDairyAscending = this.sortDairyAscending.bind(this);
      this.sortDairyDescending = this.sortDairyDescending.bind(this);

      this.sort = sortFn;
    }

    handleSort(sort) {
      this.setState({ sort });
    }

    sortDairyAscending() {
      this.setState({
        sort: {
          key: 'Dairy',
          descending: false
        }
      });
    }

    sortDairyDescending() {
      this.setState({
        sort: {
          key: 'Dairy',
          descending: true
        }
      });
    }

    reset() {
      this.setState(initialState);
    }

    render() {
      const { data } = this.props;
      const { sort } = this.state;
      const sortedData = this.sort(data, sort);

      return (
        <div>
          <Flex wrap>
            <FlexItem marginBottom="md">
              <Button onClick={this.sortDairyAscending} size="medium">Sort Dairy ascending</Button>
            </FlexItem>
            <FlexItem marginBottom="md" marginRight="auto">
              <Button onClick={this.sortDairyDescending} size="medium">Sort Dairy descending</Button>
            </FlexItem>
            <FlexItem marginBottom="md">
              <Button onClick={this.reset} size="medium">Reset</Button>
            </FlexItem>
          </Flex>
          <Table
            data={sortedData}
            rowKey="Fruits"
            sortable
            sort={sort}
            onSort={this.handleSort}
            title="Foods of the World"
            hideTitle
          />
        </div>
      );
    }
  }

  return <MyTable data={data} />;
}

Sortable Columns — Paginated #

Implement sorting for paginated data with Pagination by passing a controlled Table sliced data calculated from the active sort, current page, and page size.

Minerals

Aragonitewhite, var.vitreousorthorhombicpseudohexagonal, columnar
Azuriteazurevitreousmonoclinicprismatic, stalactitic
Celestitecolorless, var.vitreousorthorhombicfibrous, lamellar
() => {
  class PaginatedTable extends Component {
    constructor(props) {
      super(props);

      this.state = {
        currentPage: 1,
        sort: { key: 'name' }
      };

      this.handleSort = this.handleSort.bind(this);
      this.handlePageChange = this.handlePageChange.bind(this);
      this.sort = this.sort.bind(this);
    }

    handleSort(sort) {
      this.setState(
        { sort },
        () => { this.handlePageChange(1) }
      );
    }

    handlePageChange(currentPage) {
      this.setState({ currentPage })
    }

    // See the 'Sortable Columns — Controlled' example for more implementation details
    sort(data, sort) {
      return sort
        ? data.slice(0).sort((a, b) => {
            const asc = defaultSortComparator(a, b, sort.key);
            const desc = asc * -1;
            return sort.descending ? desc : asc;
          })
        : data;
    }

    render() {
      const { data } = this.props;
      const { currentPage, sort } = this.state;
      const pageSize = 3;
      const firstRow = (currentPage - 1) * pageSize;
      const lastRow = (currentPage - 1) * pageSize + pageSize;
      const slicedData = this.sort(data, sort).slice(firstRow, lastRow);

      return (
        <DemoLayout>
          <Table
            sortable
            sort={sort}
            onSort={this.handleSort}
            data={slicedData}
            columns={columns}
            title="Minerals"
            hideTitle
            rowKey="Name"
          />
          <Pagination
            currentPage={currentPage}
            onPageChange={this.handlePageChange}
            pageSize={pageSize}
            totalCount={data.length}
          />
        </DemoLayout>
      );
    }
  }

  return <PaginatedTable data={data} />
}

Selectable Rows #

Allow users to select rows with the selectable prop. Rows with a disabled property set to true will render a disabled checkbox. You can set the initially selected rows with the defaultSelectedRows prop. onToggleRow and onToggleAllRows callbacks are also available.

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
() => {
  const data = [
    sharedData[0],
    sharedData[1],
    { ...sharedData[2], disabled: true }
  ];

  return (
    <Table
      selectable
      defaultSelectedRows={[data[1]]}
      data={data}
      rowKey="Fruits"
      title="Foods of the World"
      hideTitle />
  );
}

Selectable Rows — Controlled #

Table controls its own selected rows state by default. It can optionally be managed by the application as a controlled component via the control prop, selectedRows.

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
() => {
  const evenRows = data.filter((row, index) => (index + 1)%2 === 0);
  const oddRows = data.filter((row, index) => (index + 1)%2 !== 0);

  const initialState = {
    selected: []
  };

  class MyTable extends Component {
    constructor(props) {
      super(props);

      this.state = initialState;

      this.handleToggleRow = this.handleToggleRow.bind(this);
      this.handleToggleAllRows = this.handleToggleAllRows.bind(this);
      this.selectEvenRows = this.selectEvenRows.bind(this);
      this.selectOddRows = this.selectOddRows.bind(this);
      this.reset = this.reset.bind(this);
    }

    handleToggleRow(row) {
      this.setState((prevState) => {
        const selected = prevState.selected.slice(0);
        const index = selected.indexOf(row);
        const hasRow = index !== -1;
        hasRow ? selected.splice(index, 1) : selected.push(row);
        return { selected };
      });
    }

    handleToggleAllRows(rows) {
      this.setState({ selected: rows });
    }

    selectEvenRows() {
      this.setState({
        selected: evenRows
      })
    }

    selectOddRows() {
      this.setState({
        selected: oddRows
      })
    }

    reset() {
      this.setState(initialState)
    }

    render() {
      return (
        <div>
          <Flex wrap>
            <FlexItem marginBottom="md">
              <Button onClick={this.selectEvenRows} size="medium">Select even rows</Button>
            </FlexItem>
            <FlexItem marginBottom="md" marginRight="auto">
              <Button onClick={this.selectOddRows} size="medium">Select odd rows</Button>
            </FlexItem>
            <FlexItem marginBottom="md">
              <Button onClick={this.reset} size="medium">Reset</Button>
            </FlexItem>
          </Flex>
          <Table
            data={data}
            rowKey="Fruits"
            selectable
            selectedRows={this.state.selected}
            onToggleRow={this.handleToggleRow}
            onToggleAllRows={this.handleToggleAllRows}
            title="Foods of the World"
            hideTitle />
        </div>
      );
    }
  }

  return <MyTable />;
}

Selectable Rows — Paginated #

Implement selection for paginated data with Pagination by passing sliced selected rows calculated from the overall selected rows, current page, and page size.

Minerals

MineralColorLusterCrystal SystemCrystal Habit
Malachitegreenadamantinemonoclinicbotryoidal, stalactitic
Fluoritecolorlessvitreousisometriccoarse, granular
Magnetiteblackmetallicisometricoctahedral
() => {
  class PaginatedTable extends Component {
    constructor(props) {
      super(props);

      this.state = {
        currentPage: 1,
        selected: []
      };

      this.handleToggleRow = this.handleToggleRow.bind(this);
      this.handleToggleAllRows = this.handleToggleAllRows.bind(this);
      this.pageSize = 3;
      this.handlePageChange = this.handlePageChange.bind(this);
    }

    handlePageChange(currentPage) {
      this.setState({ currentPage });
    }

    handleToggleRow(row) {
      this.setState((prevState) => {
        const selected = prevState.selected.slice(0);
        const index = selected.indexOf(row);
        const hasRow = index !== -1;
        hasRow ? selected.splice(index, 1) : selected.push(row);
        return { selected };
      });
    }

    handleToggleAllRows(_, none) {
      const slicedData = this.getSlicedData();
      this.setState(({ selected }) => ({
        selected: none
          ? selected.concat(slicedData)
          : selected.filter((row) => slicedData.indexOf(row) === -1)
      }));
    }

    getSlicedData() {
      const { currentPage } = this.state;
      const firstRow = (currentPage - 1) * this.pageSize;
      const lastRow = (currentPage - 1) * this.pageSize + this.pageSize;
      return this.props.data.slice(firstRow, lastRow);
    }

    getSlicedSelected() {
      const { selected } = this.state;
      return this.getSlicedData().reduce((acc, row) => {
        if(selected.indexOf(row) !== -1) {
          acc.push(row);
        }
        return acc;
      }, []);
    }

    render () {
      const { currentPage } = this.state;

      return (
        <DemoLayout>
          <Table
            selectable
            selectedRows={this.getSlicedSelected()}
            onToggleRow={this.handleToggleRow}
            onToggleAllRows={this.handleToggleAllRows}
            data={this.getSlicedData()}
            rowKey="name"
            columns={columns}
            title="Minerals"
            hideTitle
          />
          <Pagination
            currentPage={currentPage}
            onPageChange={this.handlePageChange}
            pageSize={this.pageSize}
            totalCount={data.length}
          />
        </DemoLayout>
      );
    }
  }

  return <PaginatedTable data={data}/>;
}

Title #

Display a title for your Table with the title prop. You can adjust the appearance (titleAppearance) and the rendered HTML element (titleAs). Use the hideTitle prop to hide the title visually, while maintaining accessibility.

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
<Grid alignItems="end" breakpoints={['57em']}>
  <GridItem span={[12, 6]} marginBottom={['lg', 0]}>
    <Table
      title="Foods of the World"
      data={data}
      rowKey="Fruits"/>
  </GridItem>
  <GridItem span={[12, 6]}>
    <Table
      title="Foods of the World"
      titleAppearance="h2"
      titleAs="h3"
      data={data}
      rowKey="Fruits"/>
  </GridItem>
</Grid>

Primary Column #

It's recommended to identify a column as the primary column (typically the first in the columns array) with the primary column definition property. This will render cells in that column as <th scope="row">, which can provide helpful context to users of some assistive technologies.

Fruits

FruitFamilyEtymologyColorTaste
PomelloRutaceaebig citruslimemild, sweet grapefruit
StarfruitOxalidaceaefruit of actionsdark yellowsweet or sour
DurianMalvaceaethornbrownunique
PersimmonEbenaceaedivine fruitred-orangesweet
() => {
  const data = [
    {
      Fruit: 'Pomello',
      Etymology: 'big citrus',
      Family: 'Rutaceae',
      Color: 'lime',
      Taste: 'mild, sweet grapefruit'
    },
    {
      Fruit: 'Starfruit',
      Etymology: 'fruit of actions',
      Family: 'Oxalidaceae',
      Color: 'dark yellow',
      Taste: 'sweet or sour'
    },
    {
      Fruit: 'Durian',
      Etymology: 'thorn',
      Family: 'Malvaceae',
      Color: 'brown',
      Taste: 'unique'
    },
    {
      Fruit: 'Persimmon',
      Etymology: 'divine fruit',
      Family: 'Ebenaceae',
      Color: 'red-orange',
      Taste: 'sweet'
    }
  ];

  const columns = [
    { content: 'Fruit', key: 'Fruit', primary: true },
    { content: 'Family', key: 'Family' },
    { content: 'Etymology', key: 'Etymology' },
    { content: 'Color', key: 'Color' },
    { content: 'Taste', key: 'Taste' }
  ];

  return (
    <Table
      columns={columns}
      data={data}
      title="Fruits"
      hideTitle />
  );
}

Column Alignment #

Align the text of both the column header and the cells under it with the textAlign column definition property.

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
() => {
  const columns = [
    { content: 'Fruits', key: 'Fruits' },
    { content: 'Vegetables', key: 'Vegetables', textAlign: 'end' },
    { content: 'Grains', key: 'Grains', textAlign: 'center' },
    { content: 'Dairy', key: 'Dairy',  textAlign: 'justify' },
    { content: 'Protein', key: 'Protein' }
  ];

  return (
    <Table
      columns={columns}
      data={data}
      title="Foods of the World"
      hideTitle />
  );
}

Density #

You can render Table with a more spacious appearance.

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
<Table
  density="spacious"
  data={data}
  rowKey="Fruits"
  title="Foods of the World"
  hideTitle />

Striped Rows #

You can render Table with alternately-striped rows.

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
<Table
  striped
  data={data}
  rowKey="Fruits"
  title="Foods of the World"
  hideTitle />

High Contrast #

You can render Table with a high contrast appearance.

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
<Table
  highContrast
  data={data}
  rowKey="Fruits"
  title="Foods of the World"
  hideTitle />

Scrollable #

Table will allow horizontal scrolling by default when its width is greater than that of its container. You can disable this behavior with scrollable={false}.

Foods of the World

Fresh FruitsVeritable VegetablesGood GrainsDelectable DairyPowerful Protein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
<Box width="50%">
  <Table
    columns={columns}
    data={data}
    rowKey="Fruits"
    title="Foods of the World"
    hideTitle />
</Box>

Bidirectionality #

Table reverses its alignment when the direction theme variable is set to rtl (right-to-left). You can use the messages prop, as in the example below, to set the various messages announced by assistive technologies within the component.

الأطعمة اللذيذة

خضرواتبقولياتبروتين
Pomelloبوك تشويشياPuleالصراصير
فاكهة النجمةRomanescoالذرةCasu marzuالنظارات
دوريانRampsالتفVieux LilleInca nuts
() => {
  const columns = [
    { content: 'ثمار', key: 'Fruits', sortable: true },
    { content: 'خضروات', key: 'Vegetables', textAlign: 'center' },
    { content: 'بقوليات', key: 'Grains' },
    { content: 'الألبان', key: 'Dairy', sortable: true, textAlign: 'end' },
    { content: 'بروتين', key: 'Protein' }
  ];

  const data = [
    {
      Fruits: 'Pomello',
      Vegetables: 'بوك تشوي',
      Grains: 'شيا',
      Dairy: 'Pule',
      Protein: 'الصراصير'
    },
    {
      Fruits: 'فاكهة النجمة',
      Vegetables: 'Romanesco',
      Grains: 'الذرة',
      Dairy: 'Casu marzu',
      Protein: 'النظارات'
    },
    {
      Fruits: 'دوريان',
      Vegetables: 'Ramps',
      Grains: 'التف',
      Dairy: 'Vieux Lille',
      Protein: 'Inca nuts'
    }
  ];

  const messages = {
    deselectAllRows: 'قم بإلغاء تحديد جميع الصفوف',
    deselectRow: 'إلغاء الصف',
    selectAllRows: 'حدد جميع الصفوف',
    selectedRows: 'الصفوف المختارة',
    selectRow: 'حدد الصف',
    sortColumnAscending: 'ترتيب العمود في تصاعدي الطلب',
    sortColumnDescending: 'ترتيب العمود في تنازلي الطلب'
  };

  return (
    <div dir="rtl">
      <ThemeProvider theme={{ direction: 'rtl' }}>
        <Table
          columns={columns}
          data={data}
          rowKey="Fruits"
          title="الأطعمة اللذيذة"
          messages={messages} />
      </ThemeProvider>
    </div>
  )
}

Custom Cell #

Use the cell render prop in a column definiton to provide custom rendering control of all cells in that column. See our Render Props Guide for additional information, including important considerations and examples.

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
() => {
   const Root = styled('td')(({ theme }) => ({
     padding: theme.space_stack_sm + ' ' + theme.space_inline_md,

     'tr:hover > &': {
       backgroundColor: palette.green_10
     }
   }));

   const Emoji = withProps({
      'aria-hidden': true,
      role: 'img'
    })(styled('span')(
     ({ theme }) => ({
       display: 'inline-block',
       marginRight: theme.space_inline_sm
     })
   ));

   const Content = styled('span')(({ theme }) => ({
     fontSize: theme.fontSize_ui,
     textAlign: 'left'
   }));

   class CustomCell extends React.PureComponent {
     render() {
       return (
         <Root {...this.props}>
           <Flex as="span">
             <Emoji>🌿</Emoji>
             <Content>{this.props.children}</Content>
           </Flex>
         </Root>
       );
     }
   }

  const cell = ({ props }) => <CustomCell {...props} />;

  const columns = [
    { content: 'Fruits', key: 'Fruits' },
    { content: 'Vegetables', key: 'Vegetables', cell },
    { content: 'Grains', key: 'Grains' },
    { content: 'Dairy', key: 'Dairy' },
    { content: 'Protein', key: 'Protein' }
  ];

  return (
    <Table
      columns={columns}
      data={data}
      rowKey="Fruits"
      title="Foods of the World"
      hideTitle />
  );
}

Custom Header Cell #

Use the headerCell render prop in a column definiton to provide custom rendering control of all table header cells in that column. See our Render Props Guide for additional information, including important considerations and examples.

Refer to the custom sortable header cell if your data is sortable.

Delicious Foods

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
() => {
  const Root = styled('th')(({ theme }) => ({
    padding: 0,
    verticalAlign: 'bottom',

    '&:not(:first-of-type)': {
      borderLeft: '1px dotted ' + theme.borderColor
    }
  }));

  const Inner = styled('span')(({theme}) => ({
    alignItems: 'flex-end',
    display: 'flex',
    padding: pxToEm(12) + ' ' + theme.space_inline_md,
    whiteSpace: 'nowrap'
  }));

  const Content = styled('span')(({ theme }) => ({
    fontSize: theme.fontSize_ui,
    fontWeight: theme.fontWeight_bold,
    textAlign: 'left'
  }));

  const Emoji = withProps({
      'aria-hidden': true,
      role: 'img'
    })(styled('span')(({ theme }) => ({
      display: 'inline-block',
      marginRight: theme.space_inline_sm
    })
  ));

  class CustomHeaderCell extends React.PureComponent {
    render() {
      const { children, emoji } = this.props;

      return (
        <Root {...this.props}>
          <Inner>
            <Content>
              <Emoji>{emoji}</Emoji>
              {children}
            </Content>
          </Inner>
        </Root>
      );
    }
  }

  const headerCell = ({ props }) => <CustomHeaderCell {...props} />;

  const columns = [
    { content: 'Fruits', key: 'Fruits', emoji: '🍎', headerCell },
    { content: 'Vegetables', key: 'Vegetables', emoji: '🥗', headerCell },
    { content: 'Grains', key: 'Grains', emoji: '🌾', headerCell },
    { content: 'Dairy', key: 'Dairy', emoji: '🥚', headerCell },
    { content: 'Protein', key: 'Protein', emoji: '🍗', headerCell }
  ];

  return (
    <Table
      columns={columns}
      data={data}
      rowKey="Fruits"
      title="Delicious Foods"
      hideTitle />
  );
}

Custom Sortable Header Cell #

Use the headerCell render prop in a column definiton to provide custom rendering control of all table header cells in that column. See our Render Props Guide for additional information, including important considerations and examples.

Delicious Foods

DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles
() => {
  const ThemedSortableTableHeaderCell = themed(TableSortableHeaderCell)(
    ({ theme }) => ({
      TableSortableHeaderCell_border_focus: '1px dotted ' + theme.color_black,
      TableSortableHeaderCell_color_focus: theme.color_black
    })
  );

  const StyledSortableTableHeaderCell = styled(ThemedSortableTableHeaderCell)(
    ({ direction, isSorted, theme }) => ({
      boxShadow: isSorted
        ? direction === 'descending'
          ? 'inset 0 -3px ' + theme.color_black
          : 'inset 0 3px ' + theme.color_black
        : null,

      '& [role="img"]': {
        display: 'none'
      }
    })
  );

  class CustomSortableTableHeaderCell extends React.PureComponent {
    render() {
      const { props, state } = this.props;
      const styledSortableTableHeaderCellProps = {
        ...props,
        ...state
      };

      return (
        <StyledSortableTableHeaderCell {...styledSortableTableHeaderCellProps} />
      );
    }
  }

  const headerCell = (props) => <CustomSortableTableHeaderCell {...props} />;

  const columns = [
    { content: 'Fruits', key: 'Fruits', headerCell },
    { content: 'Vegetables', key: 'Vegetables', headerCell },
    { content: 'Grains', key: 'Grains', headerCell },
    { content: 'Dairy', key: 'Dairy', headerCell },
    { content: 'Protein', key: 'Protein', headerCell }
  ];

  return (
    <Table
      data={data}
      columns={columns}
      sortable
      defaultSort={{ key: 'Fruits' }}
      rowKey="Fruits"
      title="Delicious Foods"
      hideTitle />
  );
}

Custom Row #

Use the row render prop as a row property in your data to provide custom rendering control of the table row. See our Render Props Guide for additional information, including important considerations and examples.

Foods of the World

FruitsVegetablesGrainsDairyProtein
PomelloBok ChoiChiaPuleCrickets
StarfruitRomanescoSorghumCasu marzuBarnacles

DurianRampsTeffVieux LilleInca nuts
PersimmonFiddleheadsQuinoaMilbenkaseSpirulina
() => {
  const Root = styled('tr')(({ theme }) => ({
    backgroundColor: theme.well_backgroundColor_warning
  }));

  const Cell = styled('td')(({ theme }) => ({
    padding: theme.space_stack_sm + ' ' + theme.space_inline_md
  }));

  const Divider = styled('hr')(({ theme }) => ({
    backgroundColor: theme.color_warning,
    border: 0,
    height: 1
  }));

  class CustomRow extends React.PureComponent {
    render() {
      const { isSelectable } = this.props;
      const cellCount = Object.keys(data[0]).length + (isSelectable ? 1 : 0);

      return (
        <Root {...this.props}>
          <Cell colSpan={cellCount}>
            <Divider />
          </Cell>
        </Root>
      );
    }
  }

  const row = ({ props }) => <CustomRow {...props} />;

  const data = [
    sharedData[0],
    sharedData[1],
    { row },
    sharedData[2],
    sharedData[3]
  ];

  return (
    <Table
      data={data}
      rowKey="Fruits"
      title="Foods of the World"
      hideTitle />
  );
}

API & Theme #

Table Props #

The Table component takes the following React props.

NameTypeDefaultDescription
columnsArray<Column>

Column definitions (see Column type for details)

dataArray<Row>required
defaultSelectedRowsArray<Row>

Initially selected rows when selectable = true. Primarily for use with uncontrolled components.

defaultSort

Initially sorted column & direction. Primarily for use with uncontrolled components.

density'compact' | 'spacious''compact'

Amount of vertical space in Table's cells

hideHeaderboolean

Visually hide Table's header, but keep available for assistive technologies

hideTitleboolean

Visually hide Table's title, but keep available for assistive technologies

highContrastboolean

Render Table with high-contrast styles

messages

Various messages and labels used by Table (see example for more details)

onSort

Called when data is sorted

onToggleAllRows

Called when all rows are selected/deselected

onToggleRow

Called when a single row is selected/deselected

rowKeystring

Specifies a key in the row data that gives a row its unique identity. See the React docs.

scrollableboolean

Determines the scrolling behavior when Table's width exceeds that of its container

selectableboolean

Enable the user to select rows. Prepends a column for checkboxes to your Table.

selectedRowsArray<Row>

Selected rows when selectable = true. Primarily for use with controlled components.

sort

Sorted column & direction

sortComparator

The sort comparator function used by sortable columns

sortableboolean

Enable the user to sort all columns

stripedboolean

Renders Table with alternating row stripes

titleReact$Noderequired

Title for Table

titleAppearance

Available title styles (see Text)

titleAs'h4'

Available title elements (see Text)

Undocumented properties, including as and css, will be applied to the root element.

Column Type #

Definition for each column in Table. See example.

NameTypeDefaultDescription
cell

Provides custom rendering control. See the custom cell example and our render props guide.

contentReact$Noderequired

Rendered content of the column header

headerCell

Provides custom rendering control. See the custom header cell example and our render props guide.

keystringrequired

Used to look up the value for a column in your row data

labelstring

If a column's content is not a string, label must be provided for accessibility purposes.

maxWidthnumber | string

Maximum width of the column. See width for more details.

minWidthnumber | string

Minimum width of the column. See width for more details.

primaryboolean

Render cells in the column as <th scope="row" /> (see example)

sortComparator

Define a custom comparator function for the column (see example)

sortableboolean

Enable user to sort the column (see example)

textAlign'start' | 'end' | 'center' | 'justify'

Align the text of both the column header and the cells (see example)

widthnumber | string

Width of the column. minWidth takes precedence over maxWidth which takes precedence over width. If no width-related properties are defined, columns will use the width decided by the rendered <table> element.

Table Theme Variables #

These variables can be used as hooks to override this component's style at either a local or global level. The theme referenced below is whatever theme is available from props to the instance of this component.

VariableValue
Table_outline_focus1px solid borderColor_theme_focus
TableCell_borderVertical
TableCell_borderVertical_highContrast
TableCell_fontSizetheme.fontSize_ui
TableCell_paddingHorizontaltheme.space_inline_md
TableCell_paddingVerticaltheme.space_stack_sm
TableCell_paddingVertical_spacious0.75em
TableCell_verticalAligntop
TableHeaderCell_borderVertical1px dotted borderColor
TableHeaderCell_borderVertical_highContrast1px dotted color_gray_80
TableHeaderCell_fontSizetheme.fontSize_ui
TableHeaderCell_paddingHorizontaltheme.space_inline_md
TableHeaderCell_paddingVertical0.75em
TableHeaderCell_paddingVertical_spacioustheme.space_stack_md
TableHeaderCell_verticalAlignbottom
TableHeaderCell_fontWeighttheme.fontWeight_bold
TableHeader_borderBottom2px solid borderColor
TableHeader_borderBottom_highContrast2px solid color_gray_80
TableHeader_borderTop1px solid borderColor
TableHeader_borderTop_highContrast1px solid color_gray_80
TableRow_backgroundColor_highContrast_selectedtheme.color_theme_20
TableRow_backgroundColor_highContrast_selectedHovertheme.color_theme_30
TableRow_backgroundColor_hovertheme.color_gray_20
TableRow_backgroundColor_selectedtheme.color_theme_10
TableRow_backgroundColor_selectedHovertheme.color_theme_20
TableRow_backgroundColor_stripedtheme.color_gray_10
TableRow_borderHorizontal1px solid color_white
TableRow_borderHorizontal_highContrast1px solid color_gray_60
TableTitle_colortheme.h4_color
TableTitle_fontSizetheme.h4_fontSize
TableTitle_fontWeighttheme.h4_fontWeight
TableTitle_marginBottomtheme.space_stack_sm

Usage #

When/How to Use #

Table is best suited to data in which a user will need to compare data points or investigate relationships. For simpler data, consider a list structure; for more complex data or user needs, consider data visualization, possibly in addition to Table. Don't use Table for data sets with a blend of text, images, and data visualizations, or content with mixed formatting; use Card instead.

For Tables with many columns, striped rows can enhance readability. Tables that do not have enough columns to fill the width can be hard to read and should be displayed at a more appropriate width (perhaps using Box or other layout components).

Table is designed to optimize performance and minimize excess operations, but rendering more than 100 rows is still not recommended.

Best Practices #

If one or more columns in your table are sortable, make sure one of those columns has a default sort applied, so that its clear the table can be sorted.

Example data

Fruit Basket$123.45
Muffin Basket$98.76

Avoid a mix of sortable and non-sortable columns. If one column is sortable, users will expect all columns to be sortable.

Example data

Price
Fruit Basket$123.45
Muffin Basket$98.76

Designate a column as primary in your column definitions, unless your data has no column to serve that purpose.

Example data

ProductPrice
Fruit Basket$123.45
Muffin Basket$98.76

Align numerical content (currency, numerical dates, etc...) to the end (right for LTR languages) for ease of comparison. If using decimals, numbers in a column should have a consistent precision.

Example data

ProductPrice
Fruit Basket$123.45
Muffin Basket$98.76

Place the units for a column in the header cell, rather than repeating them in each data cell.

Example data

NameAge (years)
Allison42
Sam65

Avoid displaying your own title for Table. Instead, use the title* prop(s) to display an accessibly-connected title with the desired element and appearance.

Non-accessible Title

Example data

ProductPrice
Fruit Basket$123.45
Muffin Basket$98.76