The page navigation is complete. You may now navigate the page content as you wish.
Skip to main content

Advanced Table

Updated in v6.5.0

Used to display complex, structured tabular data with advanced features.

The Advanced Table is designed for complex datasets, featuring sticky headers, keyboard navigation, and expandable rows. The Table and Advanced Table features aren't interchangeable.

Usage

When to use

  • For large data sets with many properties that benefit from being viewed in a scrollable container.
  • When expandable rows are needed for hierarchical data.
  • When users would benefit from more efficient keyboard navigation, e.g., when there are many rows or columns.
  • When users would benefit from customizing the view of the data set (column width, order, etc).

When not to use

  • If your dataset requires only basic interactions, such as simple sorting or pagination, and does not require features like nested rows, advanced keyboard navigation, or sticky headers, the standard Table is a more suitable choice.
  • When data visualizations better convey the data.
  • As a layout mechanism for structuring content that isn’t tabular data.
  • To replicate spreadsheet-like functionality with extensive in-cell editing or calculations.

Columns

Sorting

Unsupported feature

Sorting is not supported for nested rows at this time.

  • While multiple columns may offer sorting, only one column can be sorted at a time.
  • In addition to standard sorting methods (like alphabetical or chronological), domain-specific sorting, such as by status severity, can also be useful.
  • Sorting is not relevant for all content and should be applied thoughtfully.

Tooltips

Labels should be concise and straightforward. If more context is necessary, a Tooltip can be used in conjunction with the label, but should be used sparingly and as a last resort.

Some examples where it may be useful to include additional context in a tooltip include:

  • When the label contains a product or i80-specific term.
  • When the label refers to a setting that can be changed elsewhere in the application.

Width

As a best practice, column width should be adjusted to fit the longest data type within the cell.

Placement

Column placement determines the visual styling based on where the column is within the table structure.

For header columns, start placement adds a border radius to the top left corner and a border on the left and right, middle placement has squared corners and a border on the right, end placement has a border radius on the top right corner and a border on the right. For cells, start placement has a border on the left and right, middle and end placement have a border on the right.

Alignment

The content's alignment can impact readability and scannability. The proper alignment method depends on the content type and its relative position in the table.

Do

Use consistent alignment between the header label and the cell content in a column.

Don’t

Avoid misaligned header labels and content.

Left alignment

By default, align content to the left. This lends itself to the default left-to-right reading order of most content types.

Use left alignment for:

  • Strings (unique identifiers or IDs, names and naming conventions, etc).
  • Numerical values that do not contain decimals or floating point numbers.
  • Numerical values that contain periods or other delimiter characters (IP addresses).
  • Nested components that display a string, e.g., a Badge.

Right alignment

Right alignment can be used when expressing numerical values with decimals as this aligns the decimal places vertically.

Common examples of right alignment include:

  • Financial information, currency amounts, or other numbers with decimal values.
  • In a column with a "more options" function.
  • As a means to visually "bookend" the row with content that is of a similar length, e.g., timestamps, TTL (time-to-live) values, dates.

Don’t

Don’t align content of varied lengths to the right. This can make it difficult to read by forcing an unnatural reading pattern.

Other alignment methods

We don’t recommend centered or justified content alignment. These can be difficult to read, especially when the content varies in length.

Don’t

Don’t center header labels or cell content within a table.

Reordering columns

Where reorderable columns are enabled, users can reorder columns either by dragging the column reorder handle with a mouse, or by moving focus to the handle and using the left and right arrow keys.

Actions related to moving columns are displayed in a context menu in the table header. These are not customizable and include:

  • Move column: moves focus to the reordering handle
  • Move column to start/end: moves the column to the first or last position in the table unless the column is already in this position.

Resizing columns

Columns can be resized by dragging the "resize border" with a mouse or by moving focus to it and using the left and right arrow keys.

The interactive resize border in its active state being dragged with a mouse

When resizable columns are enabled, actions related to each function are rendered in a context menu in the table header. These functions are not customizable.

An open context menu in the Advanced Table displays two options; resize column, and reset column width

Minimum and maximum width

To prevent a column from being resized beyond a reasonable amount, every column keeps a minimum width, and a column can be given a minimum and a maximum of its own.

Column and row span

  • Supports combining multiple columns or rows into a single cell.
  • Apply column and row spans carefully to maintain alignment, accessibility, and smooth table interactions.
  • Multi-span cells should use the same alignment for readability.

Rows

Headers

  • Labels in headers should be concise and straightforward.
  • The label should clearly indicate what type of content is contained within the cell (string, number, status, etc).
  • Labels should always use sentence-case.

Expandable rows

Expandable rows let users show or hide more content without navigating away from the table. The expanded content should align with the header labels, even if the parent row includes minimal data.

Don’t

Avoid using expandable rows when data is not structured in parent-child relationships.

Advanced Table where the parent row has cells for name and email, but children rows have cells containing order date and total.

Don’t

Avoid using different density settings for parent and child rows.

Advanced Table with default height parent rows and short density nested rows is something to avoid

Expand/Collapse All Button

The Expand/Collapse All button allows users to expand or collapse all rows, including nested rows. It provides quick access to more content but may impact readability when content is long or detailed.

Interactions

Default state

The Advanced Table supports any combination of expanded or collapsed rows on load. The button reflects the initial state.

The “Expand All” button displays if any rows are currently collapsed

“Collapse All” button displays if all rows are expanded.
Collapsed state

Interacting with the “Expand All” button expands all rows, including nested rows.
Expanded state

Once all rows are expanded, the “Collapse All” button is displayed.
Mixed state

If some rows are expanded and others are collapsed, the “Expand All” button will persist until all rows are expanded.

Striping

Accessibility alert

Ensure that content within striped rows maintains adequate color contrast with the striped background.

Striping enhances readability by alternating row colors, making it easier to scan tabular data.

  • Non-Nested Advanced Tables: Striping starts with the second row, distinguishing it from the header.
  • Nested Advanced Tables: Child rows are automatically striped, while parent rows remain unstriped to visually reinforce hierarchy. This behavior cannot be disabled.

Placement

The cells in the corners of the table carry its rounded corners; the cells between them are square.

Cells

For the user to scan, sort, and filter the table easily, each cell should contain a single piece of data. Having more than one piece of data in a cell makes it harder for users to navigate the relationships between headers and cells.

Density

  • By default, use the medium density for balance and readability.
  • To fit more rows on a page, use the short density. Use this only for text-heavy tables, as it can make them harder to scan.
  • For a smaller dataset, e.g., basic user data, consider using the tall density to provide the content with more breathing room.

Horizontal scrolling

Use horizontal scrolling when the number of columns expands beyond the viewport or container.

Sticky headers and columns

The header and first column can be pinned, helping users navigate large datasets while persisting key values, such as names or IDs.

There are a few things to consider when implementing a sticky header or column:

  • Instances of the Advanced Table with nested rows, expandable rows, or cells that span columns or rows do not support a sticky column, because what counts as the first column then varies from row to row.
  • Setting the first column as sticky in a table with multi-selection will couple the multi-select column and the first column of data together.

Where a sticky first column is enabled, the context menu offers a control to "Pin" and "Unpin" the first column.

With the sticky column option set, the Advanced Table will have a context menu in the column header cell with a single option to pin column

Once pinned, the same context menu in the column header cell will then contain single "Unpin column" option

Multi-Select

Unsupported feature

Multi-select is not supported for nested rows at this time.

Multi-select allows users to select multiple rows to perform bulk actions, such as deleting or exporting data. Selection states are maintained across pagination and filtering.

A multi-select pattern consists of:

A "Select all" checkbox is used in the header row to allow the simultaneous selection or deselection of all child rows.

Individual checkboxes added to each row allow for the selection of that row.

For more details, see the Multi-Select Table Pattern.

Empty state

The Advanced Table supports displaying an empty state using the Application State component to display an informative message and prompt user action. There are several reasons an empty state may occur: the data set is empty, the applied filters return no results, etc.

Show the empty state when there is nothing to list.

How to use this component

The Advanced Table is the Table with the features a large, structured data set needs: nested rows, row selection, a sticky header and resizable columns.

<i80-advanced-table> wraps a real <table>. You — or your server template — write the <thead> and every <tbody> row; the element styles them, adds the buttons and checkboxes the features need, and reads the shape of the table out of the markup. There is no column list to declare and no data model to hand over: the rows on the page are the data.

The styles lay the table out with CSS Grid, and display: grid drops the table semantics a browser derives from the tags, so the element writes those roles back onto the real elements: role="table" on the <table>, role="row" on each <tr>, columnheader, rowheader and cell on the cells. Until the JavaScript has run — or if it never runs — the markup is still an ordinary, readable table.

A basic table

Write the table and give the element a caption. The caption is read by screen readers and not shown; a <caption> you write yourself is kept and hidden the same way.

Artist Album Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
James Taylor Sweet Baby James 1970
Simon and Garfunkel Bridge Over Troubled Waters 1970
<i80-advanced-table caption="Folk albums">
  <table>
    <thead>
      <tr>
        <th>Artist</th>
        <th>Album</th>
        <th>Year</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td>1972</td>
      </tr>
      <tr>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td>1969</td>
      </tr>
      <tr>
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td>1971</td>
      </tr>
      <tr>
        <td>Bob Dylan</td>
        <td>Bringing It All Back Home</td>
        <td>1965</td>
      </tr>
      <tr>
        <td>James Taylor</td>
        <td>Sweet Baby James</td>
        <td>1970</td>
      </tr>
      <tr>
        <td>Simon and Garfunkel</td>
        <td>Bridge Over Troubled Waters</td>
        <td>1970</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Nested rows

For a data set where a row has children, render the children as rows too and say where each one sits with depth: a <tr depth="1"> is a child of the row above it, and is hidden until that row is expanded. A row is a parent because the row after it is deeper — there is nothing else to declare. expanded on a parent row starts it open.

Put expandable on the <th> of the column that should carry the expand button; without it the first column carries it. When that column is not itself sortable, its header cell also gets a button that expands and collapses every row at once.

To keep the table accessible, the cells of a nested row must line up with the columns of its parent. Otherwise the relationship between a parent and its children will not be clear to users.

On a table with nested rows, striped is ignored — the stripes would fight the indentation — and selectable is refused, because a checkbox on a parent row has no clear meaning.

Name Status Description
Policy set 1 Pass
test-advisory-pass.sentinel Pass Sample description for this thing.
test-hard-mandatory-pass.sentinel Pass Sample description for this thing.
Policy set 2 Fail
test-advisory-pass.sentinel Pass Sample description for this thing.
test-soft-mandatory-fail.sentinel Fail Sample description for this thing.
<!-- A row's depth says where it sits in the tree: a <tr depth="1"> is a child of
     the row above it, and is hidden until that row is expanded. -->
<i80-advanced-table caption="Policy sets">
  <table>
    <thead>
      <tr>
        <th expandable>Name</th>
        <th>Status</th>
        <th>Description</th>
      </tr>
    </thead>
    <tbody>
      <tr expanded>
        <th>Policy set 1</th>
        <td><i80-badge color="success" icon="check">Pass</i80-badge></td>
        <td></td>
      </tr>
      <tr depth="1">
        <th>test-advisory-pass.sentinel</th>
        <td><i80-badge color="success" icon="check">Pass</i80-badge></td>
        <td>Sample description for this thing.</td>
      </tr>
      <tr depth="1">
        <th>test-hard-mandatory-pass.sentinel</th>
        <td><i80-badge color="success" icon="check">Pass</i80-badge></td>
        <td>Sample description for this thing.</td>
      </tr>
      <tr>
        <th>Policy set 2</th>
        <td><i80-badge color="critical" icon="x">Fail</i80-badge></td>
        <td></td>
      </tr>
      <tr depth="1">
        <th>test-advisory-pass.sentinel</th>
        <td><i80-badge color="success" icon="check">Pass</i80-badge></td>
        <td>Sample description for this thing.</td>
      </tr>
      <tr depth="1">
        <th>test-soft-mandatory-fail.sentinel</th>
        <td><i80-badge color="critical" icon="x">Fail</i80-badge></td>
        <td>Sample description for this thing.</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Resizable columns

Add resizable-columns and every column but the last gets a drag handle on its right edge. The handle is a real slider: drag it with a mouse, or move focus to it and use the left and right arrow keys, which step the edge 10px at a time.

Artist Album Release Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
James Taylor Sweet Baby James 1970
Simon and Garfunkel Bridge Over Troubled Waters 1970
<!-- Drag a column's right edge, or focus the handle and use the left and right
     arrow keys. -->
<i80-advanced-table caption="Folk albums" resizable-columns>
  <table>
    <thead>
      <tr>
        <th sort>Artist</th>
        <th sort>Album</th>
        <th>Release Year</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td>1972</td>
      </tr>
      <tr>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td>1969</td>
      </tr>
      <tr>
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td>1971</td>
      </tr>
      <tr>
        <td>Bob Dylan</td>
        <td>Bringing It All Back Home</td>
        <td>1965</td>
      </tr>
      <tr>
        <td>James Taylor</td>
        <td>Sweet Baby James</td>
        <td>1970</td>
      </tr>
      <tr>
        <td>Simon and Garfunkel</td>
        <td>Bridge Over Troubled Waters</td>
        <td>1970</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Minimum and maximum column width

width on a <th> sets that column's starting grid track, and min-width and max-width bound how far a resize can take it. A column with no min-width stops at 48px; with no max-width it keeps growing.

Artist Album Release Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
James Taylor Sweet Baby James 1970
Simon and Garfunkel Bridge Over Troubled Waters 1970
<i80-advanced-table caption="Folk albums" resizable-columns>
  <table>
    <thead>
      <tr>
        <th sort>Artist</th>
        <th sort width="300px" min-width="200px" max-width="500px">Album</th>
        <th>Release Year</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td>1972</td>
      </tr>
      <tr>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td>1969</td>
      </tr>
      <tr>
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td>1971</td>
      </tr>
      <tr>
        <td>Bob Dylan</td>
        <td>Bringing It All Back Home</td>
        <td>1965</td>
      </tr>
      <tr>
        <td>James Taylor</td>
        <td>Sweet Baby James</td>
        <td>1970</td>
      </tr>
      <tr>
        <td>Simon and Garfunkel</td>
        <td>Bridge Over Troubled Waters</td>
        <td>1970</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Content wrapping

By default, content within the cells will wrap according to the browser’s natural reflow. This may result in the layout shifting as a column gets narrower.

How the cell content behaves is yours to decide — the element only sizes the column. Truncation with an ellipsis, for example, is a matter of styling the element inside the cell: text-overflow: ellipsis; white-space: nowrap; overflow: hidden;.

Artist Album Release Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
James Taylor Sweet Baby James 1970
Simon and Garfunkel Bridge Over Troubled Waters 1970
<!-- Cell content that should be cut off instead of wrapped when a column is made
     narrower: the truncation is yours to style, the element only sizes the column. -->
<i80-advanced-table caption="Folk albums" resizable-columns>
  <table>
    <thead>
      <tr>
        <th sort>Artist</th>
        <th sort>Album</th>
        <th>Release Year</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><span class="doc-truncate">Nick Drake</span></td>
        <td><span class="doc-truncate">Pink Moon</span></td>
        <td>1972</td>
      </tr>
      <tr>
        <td><span class="doc-truncate">The Beatles</span></td>
        <td><span class="doc-truncate">Abbey Road</span></td>
        <td>1969</td>
      </tr>
      <tr>
        <td><span class="doc-truncate">Melanie</span></td>
        <td><span class="doc-truncate">Candles in the Rain</span></td>
        <td>1971</td>
      </tr>
      <tr>
        <td><span class="doc-truncate">Bob Dylan</span></td>
        <td><span class="doc-truncate">Bringing It All Back Home</span></td>
        <td>1965</td>
      </tr>
      <tr>
        <td><span class="doc-truncate">James Taylor</span></td>
        <td><span class="doc-truncate">Sweet Baby James</span></td>
        <td>1970</td>
      </tr>
      <tr>
        <td><span class="doc-truncate">Simon and Garfunkel</span></td>
        <td><span class="doc-truncate">Bridge Over Troubled Waters</span></td>
        <td>1970</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

<style>
  .doc-truncate {
    width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
</style>

Sortable columns

Add sort to each <th> whose column should be sortable. The header label becomes a sort button, clicking it sorts the rows in the browser, and clicking again reverses the order. Only one column is sorted at a time. A parent row moves together with its children, so the rows of one tree stay together.

Columns whose cells are all numbers are compared as numbers; everything else is compared as text, with digits inside the text read as numbers.

Artist Album Release Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
James Taylor Sweet Baby James 1970
Simon and Garfunkel Bridge Over Troubled Waters 1970
<i80-advanced-table caption="Folk albums">
  <table>
    <thead>
      <tr>
        <th sort>Artist</th>
        <th sort>Album</th>
        <th>Release Year</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td>1972</td>
      </tr>
      <tr>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td>1969</td>
      </tr>
      <tr>
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td>1971</td>
      </tr>
      <tr>
        <td>Bob Dylan</td>
        <td>Bringing It All Back Home</td>
        <td>1965</td>
      </tr>
      <tr>
        <td>James Taylor</td>
        <td>Sweet Baby James</td>
        <td>1970</td>
      </tr>
      <tr>
        <td>Simon and Garfunkel</td>
        <td>Bridge Over Troubled Waters</td>
        <td>1970</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Pre-sorted columns

sort="asc" on a <th> marks that column as the sorted one. It says which column carries the sort indicator, not what order to put the rows in: the rows arrive in that order from the server, and the element does not reorder them on load. The first click on such a column therefore reverses it.

Artist Album Release Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
James Taylor Sweet Baby James 1970
Simon and Garfunkel Bridge Over Troubled Waters 1970
<i80-advanced-table caption="Folk albums">
  <table>
    <thead>
      <tr>
        <th sort="asc">Artist</th>
        <th sort>Album</th>
        <th>Release Year</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td>1972</td>
      </tr>
      <tr>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td>1969</td>
      </tr>
      <tr>
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td>1971</td>
      </tr>
      <tr>
        <td>Bob Dylan</td>
        <td>Bringing It All Back Home</td>
        <td>1965</td>
      </tr>
      <tr>
        <td>James Taylor</td>
        <td>Sweet Baby James</td>
        <td>1970</td>
      </tr>
      <tr>
        <td>Simon and Garfunkel</td>
        <td>Bridge Over Troubled Waters</td>
        <td>1970</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>
Pre-sorting direction

By default, the sort order is set to ascending. To indicate that the column is pre-sorted in descending order, use sort="desc".

Artist Album Release Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
James Taylor Sweet Baby James 1970
Simon and Garfunkel Bridge Over Troubled Waters 1970
<i80-advanced-table caption="Folk albums">
  <table>
    <thead>
      <tr>
        <th sort="desc">Artist</th>
        <th sort>Album</th>
        <th>Release Year</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td>1972</td>
      </tr>
      <tr>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td>1969</td>
      </tr>
      <tr>
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td>1971</td>
      </tr>
      <tr>
        <td>Bob Dylan</td>
        <td>Bringing It All Back Home</td>
        <td>1965</td>
      </tr>
      <tr>
        <td>James Taylor</td>
        <td>Sweet Baby James</td>
        <td>1970</td>
      </tr>
      <tr>
        <td>Simon and Garfunkel</td>
        <td>Bridge Over Troubled Waters</td>
        <td>1970</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Sorting by a value instead of the cell text

Put a sort-value on the cells of a column and the column is sorted by those values instead of by the text in the cell. A sort-key on the <th> names the column in the i80-sort event, where the header's own text is used otherwise.

This is useful for cases where the cell text is not A-Z or 0-9 sortable, e.g., a status, and you’re otherwise unable to influence the shape of the data. In the example below the statuses are sorted by the rank given in sort-value, not alphabetically.

Peer name Status Cluster partition
cluster-2-partition-2 pending cluster-2 / partition-2
cluster-3-partition-3 establishing cluster-3 / partition-3
cluster-4-partition-4 failing cluster-4 / partition-4
cluster-5-partition-5 active cluster-5 / partition-5
cluster-6-partition-2 pending cluster-6 / partition-2
cluster-6-partition-1 failing cluster-6 / partition-1
<!-- The order of the statuses is not alphabetical, so each cell carries the rank
     to sort by. A server that knows the order renders it into sort-value. -->
<i80-advanced-table caption="Cluster peers">
  <table>
    <thead>
      <tr>
        <th sort>Peer name</th>
        <th sort>Status</th>
        <th>Cluster partition</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>cluster-2-partition-2</td>
        <td sort-value="3">pending</td>
        <td>cluster-2 / partition-2</td>
      </tr>
      <tr>
        <td>cluster-3-partition-3</td>
        <td sort-value="2">establishing</td>
        <td>cluster-3 / partition-3</td>
      </tr>
      <tr>
        <td>cluster-4-partition-4</td>
        <td sort-value="0">failing</td>
        <td>cluster-4 / partition-4</td>
      </tr>
      <tr>
        <td>cluster-5-partition-5</td>
        <td sort-value="1">active</td>
        <td>cluster-5 / partition-5</td>
      </tr>
      <tr>
        <td>cluster-6-partition-2</td>
        <td sort-value="3">pending</td>
        <td>cluster-6 / partition-2</td>
      </tr>
      <tr>
        <td>cluster-6-partition-1</td>
        <td sort-value="0">failing</td>
        <td>cluster-6 / partition-1</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Sorting on the server

With sort-mode="server" the element leaves the rows alone: a click only moves the sort indicator and fires i80-sort, so your server can answer with the page sorted, or a script can fetch the sorted rows itself.

Density

To create a condensed or spacious Advanced Table, add density to the element. Note that it only affects the table body, not the table header.

Artist Album Release Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
James Taylor Sweet Baby James 1970
Simon and Garfunkel Bridge Over Troubled Waters 1970
<i80-advanced-table caption="Folk albums" density="short">
  <table>
    <thead>
      <tr>
        <th sort>Artist</th>
        <th sort>Album</th>
        <th>Release Year</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td>1972</td>
      </tr>
      <tr>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td>1969</td>
      </tr>
      <tr>
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td>1971</td>
      </tr>
      <tr>
        <td>Bob Dylan</td>
        <td>Bringing It All Back Home</td>
        <td>1965</td>
      </tr>
      <tr>
        <td>James Taylor</td>
        <td>Sweet Baby James</td>
        <td>1970</td>
      </tr>
      <tr>
        <td>Simon and Garfunkel</td>
        <td>Bridge Over Troubled Waters</td>
        <td>1970</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Horizontal alignment

To create a column that has right-aligned content, set align="right" on both the column’s <th> and its <td>s — the cell’s horizontal content alignment should be the same as the column’s.

Artist Album Actions
Nick Drake Pink Moon Edit Delete
The Beatles Abbey Road Edit Delete
Melanie Candles in the Rain Edit Delete
<i80-advanced-table caption="Folk albums">
  <table>
    <thead>
      <tr>
        <th sort>Artist</th>
        <th sort>Album</th>
        <th align="right">Actions</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td align="right">
          <i80-dropdown text="Actions" size="small" color="secondary" position="bottom-left">
            <i80-dropdown-item href="#edit" icon="edit">Edit</i80-dropdown-item>
            <i80-dropdown-item href="#delete" color="critical" icon="trash">Delete</i80-dropdown-item>
          </i80-dropdown>
        </td>
      </tr>
      <tr>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td align="right">
          <i80-dropdown text="Actions" size="small" color="secondary" position="bottom-left">
            <i80-dropdown-item href="#edit" icon="edit">Edit</i80-dropdown-item>
            <i80-dropdown-item href="#delete" color="critical" icon="trash">Delete</i80-dropdown-item>
          </i80-dropdown>
        </td>
      </tr>
      <tr>
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td align="right">
          <i80-dropdown text="Actions" size="small" color="secondary" position="bottom-left">
            <i80-dropdown-item href="#edit" icon="edit">Edit</i80-dropdown-item>
            <i80-dropdown-item href="#delete" color="critical" icon="trash">Delete</i80-dropdown-item>
          </i80-dropdown>
        </td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Tooltip

Header cells should be clear, concise, and straightforward whenever possible. However, there could be cases where the label is insufficient by itself and extra information is required. In this case, put a tooltip on the <th> to show an info button next to the label:

Artist Album Vinyl Cost (USD)
Nick Drake Pink Moon 29.27
The Beatles Abbey Road 25.99
Melanie Candles in the Rain 46.49
Bob Dylan Bringing It All Back Home 29.00
James Taylor Sweet Baby James 16.00
Simon and Garfunkel Bridge Over Troubled Waters 20.49
<i80-advanced-table caption="Folk albums">
  <table>
    <thead>
      <tr>
        <th>Artist</th>
        <th tooltip="Title of the album (in its first release)">Album</th>
        <th sort align="right" tooltip="Cost of the vinyl (adjusted for inflation)">Vinyl Cost (USD)</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td align="right">29.27</td>
      </tr>
      <tr>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td align="right">25.99</td>
      </tr>
      <tr>
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td align="right">46.49</td>
      </tr>
      <tr>
        <td>Bob Dylan</td>
        <td>Bringing It All Back Home</td>
        <td align="right">29.00</td>
      </tr>
      <tr>
        <td>James Taylor</td>
        <td>Sweet Baby James</td>
        <td align="right">16.00</td>
      </tr>
      <tr>
        <td>Simon and Garfunkel</td>
        <td>Bridge Over Troubled Waters</td>
        <td align="right">20.49</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Scrollable table

Consuming a large amount of data in a tabular format can lead to an intense cognitive load for the user. As a general principle, care should be taken to simplify the information within a table as much as possible.

We recommend using functionalities like pagination, sorting, and filtering to reduce this load.

Vertical scrolling

For situations where the default number of rows visible may be high, it can be difficult for users to track which column is which once they scroll. Give the element a max-height and the body scrolls at that height, with the header sticking to the top while it does, so the column headers persist as the user scrolls.

If you want the max-height but not the sticky header, add sticky-header="false". The header can only stick while something scrolls, so sticky-header on its own does nothing.

ID Name Email Role
1 Burnaby Kuscha 1_bkuscha0@tiny.cc Owner
2 Barton Penley 2_bpenley1@miibeian.gov.cn Admin
3 Norina Emanulsson 3_nemanulsson2@walmart.com Contributor
4 Orbadiah Smales 4_osmales3@amazon.co.jp Contributor
5 Dido Titchener 5_dtitchener4@blogs.com Contributor
6 Trish Horsburgh 6_thorsburgh5@samsung.com Contributor
7 Orion Laverack 7_olaverack6@techcrunch.com Contributor
8 Delly Moulsdale 8_dmoulsdale7@sciencedirect.com Contributor
<!-- max-height makes the body scroll, and the header sticks to the top while it does. -->
<i80-advanced-table caption="Team" max-height="300px">
  <table>
    <thead>
      <tr>
        <th>ID</th>
        <th sort>Name</th>
        <th>Email</th>
        <th sort>Role</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>Burnaby Kuscha</td>
        <td>1_bkuscha0@tiny.cc</td>
        <td>Owner</td>
      </tr>
      <tr>
        <td>2</td>
        <td>Barton Penley</td>
        <td>2_bpenley1@miibeian.gov.cn</td>
        <td>Admin</td>
      </tr>
      <tr>
        <td>3</td>
        <td>Norina Emanulsson</td>
        <td>3_nemanulsson2@walmart.com</td>
        <td>Contributor</td>
      </tr>
      <tr>
        <td>4</td>
        <td>Orbadiah Smales</td>
        <td>4_osmales3@amazon.co.jp</td>
        <td>Contributor</td>
      </tr>
      <tr>
        <td>5</td>
        <td>Dido Titchener</td>
        <td>5_dtitchener4@blogs.com</td>
        <td>Contributor</td>
      </tr>
      <tr>
        <td>6</td>
        <td>Trish Horsburgh</td>
        <td>6_thorsburgh5@samsung.com</td>
        <td>Contributor</td>
      </tr>
      <tr>
        <td>7</td>
        <td>Orion Laverack</td>
        <td>7_olaverack6@techcrunch.com</td>
        <td>Contributor</td>
      </tr>
      <tr>
        <td>8</td>
        <td>Delly Moulsdale</td>
        <td>8_dmoulsdale7@sciencedirect.com</td>
        <td>Contributor</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Empty state

When the <tbody> has no rows, the element shows an empty state saying that there is no data available.

Artist Album Year
<i80-advanced-table caption="Folk albums">
  <table>
    <thead>
      <tr>
        <th>Artist</th>
        <th>Album</th>
        <th>Year</th>
      </tr>
    </thead>
    <tbody>
    </tbody>
  </table>
</i80-advanced-table>

To show something else, put your own element inside the tag with slot="empty". It sits outside the table and is taken out of the document as soon as there are rows. An Application State is the recommended content.

Artist Album Release Year

No results were found with the selected filters. Please clear or update the filters.

Clear filters
<i80-advanced-table caption="Folk albums" density="short">
  <table>
    <thead>
      <tr>
        <th sort>Artist</th>
        <th sort>Album</th>
        <th>Release Year</th>
      </tr>
    </thead>
    <tbody></tbody>
  </table>
  <i80-application-state slot="empty" title="No data to display" align="center">
    <p>No results were found with the selected filters. Please clear or update the filters.</p>
    <i80-button slot="footer" color="secondary">Clear filters</i80-button>
  </i80-application-state>
</i80-advanced-table>

Row selection

A table with row selection includes checkboxes enabling users to select multiple rows for purposes of performing bulk operations. Add selectable and the element adds the checkbox column: one checkbox per row, and a select-all in the header that goes indeterminate while only some rows are ticked. Clicking an indeterminate select-all ticks everything; clicking it again clears everything.

The checkboxes are real inputs. select-name gives them a name and value on a <tr> is what that row's checkbox submits, so inside a <form> the selection posts with the form — one name=value pair per ticked row — and no script is involved. selected on a <tr> starts it ticked, which is how a server renders a selection back. For a page that acts on the selection without a submit, there is the i80-select event.

Accessibility alert

Each checkbox needs a label of its own. Put select-label on every <tr>: the checkbox is then labelled “Select <that text>”. Without it the label falls back to “Select row 3”, which tells a screen reader user nothing about which row it is. select-all-label renames the header checkbox, “Select all rows” by default.

Selecting rows

Artist Album Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
James Taylor Sweet Baby James 1970
Simon and Garfunkel Bridge Over Troubled Waters 1970
Archive selected
<!-- The checkboxes are real inputs with a name, so the selection posts with the
     surrounding form; the element also fires i80-select on every change. -->
<form method="post" action="/albums/archive">
  <i80-advanced-table caption="Folk albums" selectable select-name="album">
    <table>
      <thead>
        <tr>
          <th sort>Artist</th>
          <th sort>Album</th>
          <th sort>Year</th>
        </tr>
      </thead>
      <tbody>
        <tr value="1" select-label="row Nick Drake / Pink Moon">
          <td>Nick Drake</td>
          <td>Pink Moon</td>
          <td>1972</td>
        </tr>
        <tr value="2" select-label="row The Beatles / Abbey Road">
          <td>The Beatles</td>
          <td>Abbey Road</td>
          <td>1969</td>
        </tr>
        <tr value="3" select-label="row Melanie / Candles in the Rain">
          <td>Melanie</td>
          <td>Candles in the Rain</td>
          <td>1971</td>
        </tr>
        <tr value="4" select-label="row Bob Dylan / Bringing It All Back Home">
          <td>Bob Dylan</td>
          <td>Bringing It All Back Home</td>
          <td>1965</td>
        </tr>
        <tr value="5" select-label="row James Taylor / Sweet Baby James">
          <td>James Taylor</td>
          <td>Sweet Baby James</td>
          <td>1970</td>
        </tr>
        <tr value="6" select-label="row Simon and Garfunkel / Bridge Over Troubled Waters">
          <td>Simon and Garfunkel</td>
          <td>Bridge Over Troubled Waters</td>
          <td>1970</td>
        </tr>
      </tbody>
    </table>
  </i80-advanced-table>
  <i80-button color="primary" type="submit">Archive selected</i80-button>
</form>

Sorting by selection state

sort-selected gives the selection column its own sort button, which brings the ticked rows together. In i80-sort that column reports the key selected and the index -1.

Artist Album Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
James Taylor Sweet Baby James 1970
Simon and Garfunkel Bridge Over Troubled Waters 1970
<i80-advanced-table caption="Folk albums" selectable select-name="album" sort-selected>
  <table>
    <thead>
      <tr>
        <th sort>Artist</th>
        <th sort>Album</th>
        <th sort>Year</th>
      </tr>
    </thead>
    <tbody>
      <tr value="1" select-label="row Nick Drake / Pink Moon">
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td>1972</td>
      </tr>
      <tr value="2" select-label="row The Beatles / Abbey Road" selected>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td>1969</td>
      </tr>
      <tr value="3" select-label="row Melanie / Candles in the Rain">
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td>1971</td>
      </tr>
      <tr value="4" select-label="row Bob Dylan / Bringing It All Back Home" selected>
        <td>Bob Dylan</td>
        <td>Bringing It All Back Home</td>
        <td>1965</td>
      </tr>
      <tr value="5" select-label="row James Taylor / Sweet Baby James">
        <td>James Taylor</td>
        <td>Sweet Baby James</td>
        <td>1970</td>
      </tr>
      <tr value="6" select-label="row Simon and Garfunkel / Bridge Over Troubled Waters">
        <td>Simon and Garfunkel</td>
        <td>Bridge Over Troubled Waters</td>
        <td>1970</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Selection with pagination

This is a more complex example, where a table with row selection is associated with a Pagination element (a similar case would be a filter applied to the rows). In this case a subset of rows is on screen.

When a user selects a row and the displayed rows are then replaced with other ones — the “next” button, a different page number — there’s the question of what happens to the previous selection: is it persisted, or is it lost?

Because the checkboxes are part of the page, the answer belongs to whatever renders the page: keep the selected values with the page state and render selected back onto the rows that come round again, and a selection survives paging; render the new page without it, and the selection is gone.

Artist Album Year
Nick Drake Pink Moon 1972
The Beatles Abbey Road 1969
Melanie Candles in the Rain 1971
Bob Dylan Bringing It All Back Home 1965
<i80-advanced-table caption="Folk albums" selectable select-name="album">
  <table>
    <thead>
      <tr>
        <th>Artist</th>
        <th>Album</th>
        <th>Year</th>
      </tr>
    </thead>
    <tbody>
      <tr value="1" select-label="row Nick Drake / Pink Moon">
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td>1972</td>
      </tr>
      <tr value="2" select-label="row The Beatles / Abbey Road">
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td>1969</td>
      </tr>
      <tr value="3" select-label="row Melanie / Candles in the Rain">
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td>1971</td>
      </tr>
      <tr value="4" select-label="row Bob Dylan / Bringing It All Back Home">
        <td>Bob Dylan</td>
        <td>Bringing It All Back Home</td>
        <td>1965</td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>
<i80-pagination total="6" page="1" per-page="4" page-sizes="2,4" show-info show-size-selector
  aria-label="Pagination for multi-select table"></i80-pagination>

Usability and accessibility considerations

Since the “selected” state of a row is communicated with the checkbox selection, there are some important considerations to keep in mind when implementing row selection.

If the selection status of the rows is persisted even when a row is not displayed in the UI, consider what the expectations of the user might be: how are they made aware that the action they are going to perform may involve rows that were previously selected but not displayed in the current view?

Even more complex is the case of the “Select all” checkbox in the table header. While the expected behavior might seem straightforward when all rows are displayed, it may not be obvious what the expected behavior is when the rows are paginated or have been filtered. The element’s own select-all reaches the rows that are on the page, and nothing else.

Consider the experience of a user intending to select all or a subset of all possible rows:

If a user interacts with a “Select all” function or button, is the expectation that only displayed rows are selected (what happens in the example above), or that all of the rows in the data set are selected, even if not displayed in the current view?

In the first scenario, the “Select all” state changes depending on what rows are in view and can be confusing.

In the second scenario it might not be obvious that all of the rows have been selected and may result in the user unintentionally performing a destructive action under the assumption that they have only selected the rows in the current view.

Whatever functionality you decide to implement, be mindful of all these possible subtleties and complexities.

At a bare minimum we recommend clearly communicating to the user if they have selected rows outside of their current view and how many out of the total data set are selected. We're working to document these scenarios as they arise, in the meantime contact the Design Systems Team for assistance.

Visually hidden header labels

Labels within the header cells are intended to provide contextual information about the column’s content to the end user. There may be special cases in which that label is redundant from a visual perspective, because the kind of content can be inferred by looking at it (e.g. a column of menus).

In this example the label of the last column is hidden with hidden-label, so it is read by screen readers only:

Artist Album Year Select an action from the menu
Nick Drake Pink Moon 1972 Delete
The Beatles Abbey Road 1969 Delete
Melanie Candles in the Rain 1971 Delete
<i80-advanced-table caption="Folk albums">
  <table>
    <thead>
      <tr>
        <th sort>Artist</th>
        <th sort>Album</th>
        <th sort>Year</th>
        <th hidden-label width="60px">Select an action from the menu</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Nick Drake</td>
        <td>Pink Moon</td>
        <td>1972</td>
        <td>
          <i80-dropdown text="Actions" size="small" color="secondary" position="bottom-left">
            <i80-dropdown-item href="#delete" color="critical" icon="trash">Delete</i80-dropdown-item>
          </i80-dropdown>
        </td>
      </tr>
      <tr>
        <td>The Beatles</td>
        <td>Abbey Road</td>
        <td>1969</td>
        <td>
          <i80-dropdown text="Actions" size="small" color="secondary" position="bottom-left">
            <i80-dropdown-item href="#delete" color="critical" icon="trash">Delete</i80-dropdown-item>
          </i80-dropdown>
        </td>
      </tr>
      <tr>
        <td>Melanie</td>
        <td>Candles in the Rain</td>
        <td>1971</td>
        <td>
          <i80-dropdown text="Actions" size="small" color="secondary" position="bottom-left">
            <i80-dropdown-item href="#delete" color="critical" icon="trash">Delete</i80-dropdown-item>
          </i80-dropdown>
        </td>
      </tr>
    </tbody>
  </table>
</i80-advanced-table>

Notice: keep hidden labels on non-sortable columns — a sort button with no visible label to sit next to reads as an unexplained control.

Events

Where another framework would take a callback, the element fires an event. All four bubble, and carry a detail:

Event Fired when detail
i80-sort a sort button is clicked { key, order, index } — the column’s sort-key or, without one, its header text; "asc" or "desc"; the column’s position, or -1 for the selection column
i80-expand a row is expanded or collapsed { row, expanded, all } — the <tr>, or null when the header’s expand-all did it; whether it is now open; whether it was the expand-all
i80-select a checkbox changes { key, checked, all, selected } — the row’s value, or "all" for the select-all; the new state; whether it was the select-all; and the value of every ticked row
i80-resize a column’s width changes, by drag or by arrow key { index, width, widths } — the column, its new width in pixels, and the width of every column

A server-rendered page can usually ignore all four. The browser does the sorting and the expanding by itself, and a selection posts with the surrounding form; the events are for a page that has to do something more — send the sort order to the server, enable a bulk-action button, remember a column width.

document.querySelector('i80-advanced-table').addEventListener('i80-select', (event) => {
  // { key: '2', checked: true, all: false, selected: ['1', '2'] }
  archiveButton.disabled = event.detail.selected.length === 0;
});

The element can also be driven from script: selectedRows and selectedValues read the selection, and toggleRow(), expandAll(), collapseAll(), selectAll() and sortRows() do what the buttons do.

What this element does not do

Four features of the original Helios component are deliberately not in <i80-advanced-table>:

  • Column reordering. A column cannot be dragged to a new position.
  • A pinned or sticky first column. max-height makes the header stick; no column stays put while the table scrolls sideways.
  • Filtering. Filtering belongs to the page, not to the table: put a Filter Bar above it and render the rows that match — see filter patterns.
  • Translated strings. The text the element writes is English. The parts worth naming can be set from the markup: caption, select-label, select-all-label, and the whole empty state through slot="empty".

There is also no “reset column width” menu on a header cell: a resized column keeps its width until the page is loaded again.

Attributes

<i80-advanced-table> renders this component. Everything it understands:

Attribute Values Default Notes
density short · medium · tall · default medium Row height.
valign top · middle · baseline top
striped boolean Alternating row background. Ignored on a table with nested rows, where it would fight the indentation.
caption text Read by screen readers, not shown. A <caption> you write yourself is kept.
selectable boolean Adds a checkbox column and a select-all that goes indeterminate. Off on a table with nested rows.
select-name text The name of the row checkboxes, so the selection posts with the surrounding form.
select-all-label text Select all rows The select-all checkbox's aria-label.
sort-selected boolean The selection column gets a sort button that brings the ticked rows together.
max-height CSS length The body scrolls at that height, and the header sticks while it does.
sticky-header false With max-height: keeps the header from sticking.
resizable-columns boolean A drag handle per column, also driven with the left and right arrow keys.
sort-mode client · server client server only fires i80-sort with {key, order, index}.
th sort empty · asc · desc Makes the column sortable and sets the starting order.
th expandable boolean The column that carries the expand button of a row with children, and the expand-all in the header.
th tooltip text An info button next to the column label.
th hidden-label boolean The label is read by screen readers only (a column of buttons).
th width CSS length auto The column's grid track. min-width and max-width bound a resize.
tr depth number 0 A child of the row above it: hidden until that row is expanded.
tr expanded boolean Starts with its children shown.
tr value text What the row's checkbox submits, and what i80-select reports.
tr select-label text row N Named in the row checkbox's aria-label: "Select <this>".
tr selected boolean Starts ticked.
th sort-key / td sort-value text What i80-sort reports, and what to sort by instead of the cell text.
td align / th align left · center · right left
slot="empty" element Shown instead of an empty body; without it, a plain "No data available" state.

The tag keeps any native element you write inside it, so a server-rendered form, link or button works as it is. See Web Components for loading i80.js and the reference for these examples running.

Anatomy

Advanced Table headers

Element Usage
Checkbox Optional, but required when cells contain a checkbox
Label Required
Tooltip button Optional
Sort button Optional, Options: none, ascending, descending
Context menu Shown where resizable columns are enabled.
Container Required

Context menu

Element Usage
Context menu and actions Optional, conditionally renders when a column has context menu actions available for interactions such as resizing, reordering, or pinning.

Advanced Table cells

Expandable cell

Element Usage
Expand Optional
Cell content Required
Icon Optional
Container Required

Nested cell

Element Usage
Nested Required
Cell content Required
Icon Optional
Container Required

Selection cell

Element Usage
Checkbox Optional, but required when the header contains a checkbox
Cell content Required
Icon Optional
Container Required

States

Advanced Table cells

Cells have a default and focused state. The focused state provides visual feedback for keyboard navigation.

Header column state example

Resize border

To indicate interactivity, the border on a resizable column has visual interactive states.

Resize border states

Drag handle

The drag handle is displayed either when hovering on a table header with a mouse, or when focus is moved to it from the context menu.

Drag handle states

Overflow

The Advanced Table can overflow on the X and Y axes (horizontally and vertically). When the number of columns no longer fits within the viewport or container, a subtle shadow indicates where the overflow occurs. When paired with a sticky header or column, the “edge” where the overflow occurs uses the same subtle shadow.

An Advanced Table showcasing the visual language and subtle shadow that indicates overflow on the horizontal axis

For general content recommendations, refer to our Tables content guidelines documentation.

Conformance rating

Conformant

When used as recommended, there should not be any WCAG conformance issues with this component.

<i80-advanced-table> styles the <table> you (or your server) wrote. The i80 layout uses CSS grid, which would otherwise take the table semantics away from the browser, so the element writes them back onto the real elements — role="table", rowgroup, row, columnheader, rowheader and cell. A screen reader therefore announces rows, columns and header cells as it would for any other table.

Best practices

Interactive rows

A table row cannot receive interactions, meaning actions cannot be attached directly to a <tr>. If you need an interactive element, put it inside a cell in that row, e.g. <td><a href="somelink.html">Some link</a></td>.

Focus in Advanced Tables

Cells are not focusable, and there is no separate navigation mode: focus moves through the interactive elements in the table in document order, the same as anywhere else on the page. Everything the element adds is a real control with a name:

  • the sort button in a <th sort> header cell, which sets aria-sort on the cell,
  • the expand button of a <th expandable> header cell or a parent row, with aria-expanded,
  • the row checkbox and the select-all checkbox, which are real <input type="checkbox"> elements — the select-all one goes indeterminate when only some rows are chosen,
  • the resize handle of a resizable column, which is a slider (see below),
  • and whatever you put in the cells yourself.

Keyboard navigation

Tab

Tabbing into the table moves focus to the first control in it — for a sortable table, the sort button in the first header cell.

Keyboard focus on the sortable 'Artist' column header with sorting controls active.

Spacebar
Enter

Sort by that column, or expand and collapse the related rows. Both are buttons, so both keys work.

Keyboard focus on the sortable 'Artist' column header with sorting controls active.

Tab

Keep tabbing to reach the links, buttons and checkboxes inside the cells.

Keyboard focus on 'The Beatles' link inside the table.

A header cell can carry a Tooltip. Its toggle is in the tab order too, and the hint appears on focus as well as on hover.

Tooltip appears on focus for the info icon in the 'Artist' column header.

Resizing columns

Tab

With resizable-columns, each header cell ends with a resize handle. It is a role="slider" with tabindex="0", named after its column (“Resize Artist column”), and it reports its current width through aria-valuenow and aria-valuetext, so a screen reader reads the width out as it changes.

Keyboard focus on the resize border for the column.

With the handle focused, the right and left arrow keys increase or decrease the width of the column in 10px steps, between the column’s minimum and maximum.

Row selection

You should clearly communicate to the user how many rows are selected and how many rows there are total outside of the Advanced Table. For additional considerations, read the Multi-select usability and accessibility considerations.

Applicable WCAG Success Criteria

This section is for reference only. This component intends to conform to the following WCAG Success Criteria:

  • 1.3.1 Info and Relationships (Level A):
    Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.
  • 1.3.2 Meaningful Sequence (Level A):
    When the sequence in which content is presented affects its meaning, a correct reading sequence can be programmatically determined.
  • 1.4.1 Use of Color (Level A):
    Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.
  • 1.4.10 Reflow (Level AA):
    Content can be presented without loss of information or functionality, and without requiring scrolling in two dimensions.
  • 1.4.11 Non-text Contrast (Level AA):
    The visual presentation of the following have a contrast ratio of at least 3:1 against adjacent color(s): user interface components; graphical objects.
  • 1.4.12 Text Spacing (Level AA):
    No loss of content or functionality occurs by setting all of the following and by changing no other style property: line height set to 1.5; spacing following paragraphs set to at least 2x the font size; letter-spacing set at least 0.12x of the font size, word spacing set to at least 0.16 times the font size.
  • 1.4.13 Content on Hover or Focus (Level AA):
    Where receiving and then removing pointer hover or keyboard focus triggers additional content to become visible and then hidden, the following are true: dismissible, hoverable, persistent (see link).
  • 1.4.3 Minimum Contrast (Level AA):
    The visual presentation of text and images of text has a contrast ratio of at least 4.5:1
  • 1.4.4 Resize Text (Level AA):
    Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality.
  • 2.1.1 Keyboard (Level A):
    All functionality of the content is operable through a keyboard interface.
  • 2.1.2 No Keyboard Trap (Level A):
    If keyboard focus can be moved to a component of the page using a keyboard interface, then focus can be moved away from that component using only a keyboard interface.
  • 2.1.4 Character Key Shortcuts (Level A):
    If a keyboard shortcut is implemented in content using only letter (including upper- and lower-case letters), punctuation, number, or symbol characters, then it should be able to be turned off, remapped, or active only on focus.
  • 2.4.3 Focus Order (Level A):
    If a Web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability.
  • 2.4.7 Focus Visible (Level AA):
    Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible.
  • 4.1.2 Name, Role, Value (Level A):
    For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies.

Support

If any accessibility issues have been found within this component, let us know by submitting an issue.

0.1.0

Første i80-version (fork af upstream, se core/UPSTREAM.md).


Related