EDS4 logo
@eds/table
v4.5.15

Table

Primitive components to build data tables.

Tables are used to organise and display data efficiently. They allow users to quickly scan, sort, compare and make actions to items within the table.

The @eds/table package provides primitive components that help build an EDS-styled HTML table.

For examples of composing tables with other components, see the Tables pattern.

The @eds/table package provides primitive components for building a table that match a HTML table. The table components are basic components for laying out and styling a table, and make no assumptions on how the table's data is fetched, paged or what type of content is in the cells.

As well as providing EDS styles to the table, it also helps implement basic table functionality such as sorting.

Use the <Table />, <TableHead />, <TableBody />, <TableFoot />, <TableRow />, <TableHeaderCell /> and <TableCell /> components to build your table.

There are three ways to apply settings to the table:

  • Table settings - set as props on the <Table /> component, applies the setting to the whole table.
  • Column settings - set in the columnSettings prop on the Table component, applies to all cells in a column. Requires columns to have a columnName prop.
  • Cell settings - set as props directly on the <TableCell /> component.

There are a few accessibility props that must be considered when building a table.

  • ariaLabel - When the context of the table isn't clear, a label must be provided to give screen readers context on the table contents.
  • ariaRowCount - If only a subset of rows are shown due to paging or virtualisation, the total row count must be provided for screen readers. TableRow components also must have an ariaRowIndex prop provided.
  • ariaRowIndex - If only a subset of rows are shown due to paging or virtualisation, the row's index must be provided for screen readers. Table components must also have an ariaRowCount prop provided.

The fillContainer prop makes the table expand to the full width of its container by setting min-width: 100%. Its default value is true.

It is available on the <Table /> component.

NOTE: If the minWidth prop is provided to the Table component, width: 100% is set to fill the container.

The fillContainer prop defaults to setting the minWidth rather than the width, as setting width: 100% without a minWidth causes the table to become unusable on smaller viewports.

The <Table /> component has three width props available, width, minWidth and maxWidth.

Column widths can be set either via columnSettings on the <Table /> component, or directly on <TableCell /> and TableHeaderCell components.

By default the browser will render the columns with widths to match their content.

To achieve even columns set a width on the table. If setting the width to 100% make sure to also provide a minWidth to ensure that the table doesn't get too small on mobile viewports.

The tableBorder prop adds a border around the table, along with rounded corners.

It is available on the <Table /> component.

The divider prop adds a right border on cells.

It is available on the <Table /> component to apply to all cells, via columnSettings to apply it to a column, or directly on the <TableCell /> component.

Combining the tableBorder and divider props on the Table component is an easy way to get a grid-styled table.

The truncate prop prevents text in table cells from wrapping, and applies ellipsis to overflowing text.

It requires either the <Table /> to have a width or maxWidth set, or for truncated columns to have a width set.

If setting the Table width to 100%, always ensure you have set a minWidth for smaller viewport compatibility.

It is available on the <Table /> component to apply to all cells, via columnSettings to apply it to a column, or directly on the <TableCell /> component.

The density prop controls the padding around the table cells, and is available on the <Table /> component.

Three options are available: spacious, regular and slim. It defaults to regular.

The horizontalAlign prop controls alignment across the x-axis.

Three options are available: left, middle and right. Defaults to left.

It is available on the <Table /> component to apply to all cells, via columnSettings to apply it to a column, or directly on the <TableCell /> component.

The verticalAlign prop controls alignment across the y-axis.

Three options are available: top, middle and bottom. Defaults to middle.

It is available on the <Table /> component to apply to all cells, via columnSettings to apply it to a column, or directly on the <TableCell /> component.

The rowSpan and colSpan props allow cells to span over multiple rows or columns.

They are available via columnSettings to apply it to a column, or directly on the <TableCell /> component.

Columns can be sorted in ascending or descending order. When sorting is enabled on a column, a ‘sort by’ icon will be placed next to the label. The order of sorting when clicking the header is ascending, descending, then default.

Columns are made sortable by providing an onSortChange callback function and sort value to the <TableHeaderCell />, and giving each TableHeaderCell a columnName.

When the user clicks a sortable header, the onSortChange callback will be called with the columnName and sortDirection: ascending, descending, or default.

As the state is controlled by the app, you can manually update the state when loading sort state from a URL or other user-action. The app can then apply its own sorting logic to update the table results.

NOTE: Unless instant, it's good practice to indicate the sorting progress with a loading spinner. Refer to the Tables' Loading pattern for more information.

If there is a case where you do not wish to control the state of the column sorting, the onSort callback can be provided to enable sorting on a column.

The onSort callback will be fired when the user clicks a sortable column's name or icon with the columnName and sortDirection.

By default the table will attempt to fit inside its container. If a table's width, column settings or content causes the table to grow wider than its container, the table becomes scrollable and overflow styles are applied.

Additionally, if you set a maxHeight on the <Table /> or wrap the table in a flexbox container with a fixed height, e.g. <Flex flexDirection="column" height="15rem" />, then it becomes scrollable on the vertical axis when the table's content is taller.

The overflow styles are provided by the ScrollWrap component.

A column can be made "sticky" to the left-hand side of the table when a table is likely to overflow on the horizontal axis, and the user needs to keep a reference to a column whilst scrolling horizontally.

Simply define sticky: true on the column via columnSettings on the <Table /> component and offsets will be calculated automatically. Stickiness will automatically be de-activated if the table container's width is smaller than the sticky columns' total width + 200 (px).

It’s recommended that no more than 2 columns are sticky so that the table view is as lean and unobtrusive as possible.

NOTE: This functionality relies on the TableHeaderCell and TableCell components being used and having columnName defined.

NOTE: If you are using Selectable rows props then the injected column containing a checkbox or radio will automatically become sticky, so it always remains visible on horizontal scroll.

Use the stickyHeader prop on the <Table /> component when you need the table header to stick to the top when the user scrolls on the vertical axis.

NOTE: This functionality uses position: "sticky", and due to limited stacking context flexibility it only works if:

  • the table is wrapped in a container element with a fixed height, or
  • the <Table /> component has a maxHeight set.

EDS supports both single- and multi-selection of table rows, controlled via props outlined below.

When adding selectable rows to a table, you'll need to handle the selection state yourself because selections may span across multiple pages, whereas the table data may only be aware of the current page. Selection state is typically kept separate from table data as table data usually comes from the server, where as selection state is usually a client-side concern.

On the <Table /> component, use the following props:

  • selectType: Use "checkbox" for multiple rows selection.
  • selectedValues: An array list of strings representing the currently selected values, often the ID of the row's record.
  • onSelectedValuesChange: Callback to run when the table row checkbox is clicked.

On the <TableRow /> components inside TableBody:

  • selectLabel: Always provide an accessible label for the checkbox input (visually hidden).
  • selectValue: Pass a unique string value to each <TableRow /> that you wish to be selectable.
  • selectDisabled: Pass true if you need to disable the table row checkbox.

Alongside multi-selection, it's common to implement a "Select all" checkbox in your table header to let the user easily select all of the rows. This state also needs to be controlled because you may want it to select all of the rows on the current page, or sometimes to select all rows across multiple pages.

On the <TableRow /> component that must be nested inside the TableHead:

  • selectAll: State of the "Select all" checkbox; true, false or "indeterminate".
  • onSelectAllChange: Callback to run when the table header's "Select all" checkbox is clicked.

To make the "indeterminate" state easier to implement, we have a useSelectAll hook. All you need to do is pass in these three arguments:

  • selectAllValues: An array list of all the values that should be selected or unselected when onSelectAllChange is called.
  • selectedValues: An array list of the currently selected values, i.e. your selected rows state.
  • setSelectedValues: Callback to run when onSelectAllChange is called, i.e. the state handler you use to manage the selected rows.

The hook then returns two variables, selectAll and onSelectAllChange that you can simply pass via props to the TableRow inside TableHead.

NOTE: selectValue and selectAll can't be used together on the same <TableRow />.

If you need more control of how "Select all" works you can write your own implementation as follows:

On the <Table /> component, use the following props:

  • selectType: Use "radio".
  • selectedValues: An array with only one string representing the currently selected value, often the ID of the row's record.
  • onSelectedValuesChange: Callback to run when the table row radio is clicked.

On the <TableRow /> components inside TableBody:

  • selectLabel: Always provide an accessible label for the table row radio input (visually hidden).
  • selectValue: Pass a unique string value to each <TableRow /> that you wish to be selectable.
  • selectDisabled: Pass true boolean if you need to disable the table row radio.

NOTE: "Select all" functionality and props will have no effect if used with <Table selectType="radio" />.

Clickable table rows can be used when the only interaction of a row is to navigate the user to a detail page. Pass the onClick prop to each <TableRow /> component inside the table body to enable this functionality.

It is recommended to implement a <ChevronRightIcon /> in the last table cell of each clickable row as a visual cue that the user will be directed to another page.

Inline actions in a table refer to the ability to perform certain actions on a specific table row without the need to navigate to a separate page or interface. Each row is accompanied by actions placed at the end of the table, containing actions specific to that particular row.

Prefer using icon-only buttons for data-heavy tables or when there is limited space. Ensure the icons are universal and easily understood on their own, otherwise use a text label to clarify the action.

Text-only buttons can be used in instances where the action is unique to the product. For text-only buttons, it is recommended to use a Neutral style to differentiate the buttons from the other cells in the table.

Supporting text with an icon can also help convey meaning to functions that are specific to the product.

A button can be paired with an overflow that can be used to highlight an action and simplify the table.

In 2022 the best way to display a table of data is still with a HTML table.

When building this component other options were explored.

Flex - using flexbox to build a table can work layout-wise but ultimately flex is one-dimensional which makes which makes certain features such as column/row spans difficult to implement without a lot of client-side processing, which has performance issues on large tables.

CSS grid - CSS grid is very powerful and has the potential to replace HTML tables in the future. The one drawback with CSS grid is that every cell of the table needs to be a direct descendant of the container, so you cannot have table row elements for styling or functionality.

The new CSS grid feature subgrid will allow grids to be built with table row components, but as of this writing subgrid doesn't have much browser support.

There is a workaround by adding display: contents to the table row element in a grid layout so that it is ignored by the grid, but due to bugs in Firefox and Safari, anything inside a display: contents container is inaccessible to assistive technologies.