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

Used to display organized, two-dimensional tabular data.

The Hds::Table component should be used for displaying tabular data; it renders an HTML table element.

This component takes advantage of the sort-by helper provided in ember-composable-helpers. While some of the examples provided on this page use some of the other helpers provided in this addon, they are not required to use the design system. Read the configuration information provided by the addon if you wish to customize which helpers are included in your own app.

Usage

When to use

  • To display and organize tabular data.
  • When comparing, sorting, and filtering multi-dimensional data and objects.

When not to use

  • As a layout mechanism.
  • As a replacement for a spreadsheet or similar application.

Column placement

Start Middle End

Column placement determines the visual styling based on where the column is placed relatively to other columns in a table header.

The column placement property is only relevant within Figma design tooling and doesn’t exist as a property within the production code component.

Alignment

Start End

Alignment Best practices

  • Alignment of the header column should remain consistent within the cell (see best practices for alignment within the cell).
  • Columns in the end position frequently use end-alignment when displaying non-string/text content.

Sorting

Header column sorting

Sorting Best practices

  • Sorting is not relevant for all content, therefore the default sort variant of the header column is none.
    • Whether a column is sortable is left up to the consumer building the table and should be addressed on a case-by-case basis.
  • Columns that do contain a sortable data type are interactive and therefore have corresponding hover, active, and focus states.
  • A table may only be sorted by a single value at a time.

Header column pattern

Intentionally not defined as a component with HDS Figma tooling, the header column component is intended to be assembled into a larger table header pattern consisting of multiple columns.

Full name Projects Email address Status Created by Options

Column width

Within Figma, column width is determined manually by resizing the header column and cells. As a best practice, column width should be adjusted to fit the longest data type within the cell.

Content

Label

  • Labels should be clear, concise, and straightforward.
  • The label should infer clearly what type (string, number, status, etc) of content is contained within the cell.
  • Labels should use sentence-case capitalization, not all-caps.

How to use this component

Static Table (non-sortable)

If you have your own content and don’t want to use a model, you can still benefit from the components themselves. Here is an example of such an invocation in a template:

<!-- app/templates/components/table.hbs -->

<Hds::Table>
  <:head as |H|>
    <H.Tr>
      <H.Th>Artist</H.Th>
      <H.Th>Album</H.Th>
      <H.Th>Release Year</H.Th>
    </H.Tr>
  </:head>
  <:body as |B|>
    <B.Tr>
      <B.Td>Custom Cell Content</B.Td>
      <B.Td>{{t 'translated-cell-content-string'}}</B.Td>
      <B.Td>Some other custom cell content</B.Td>
    </B.Tr>
  </:body>
</Hds::Table>

Simple Table with model defined (non-sortable)

In this invocation of the table component, you would define the data model and insert your own content into the :head and :body blocks. Here is an example of such an invocation in a template:

<!-- app/templates/components/table.hbs -->

<Hds::Table @model={{this.model}}>
  <:head as |H|>
    <H.Tr>
      <H.Th>Artist</H.Th>
      <H.Th>Album</H.Th>
      <H.Th>Release Year</H.Th>
    </H.Tr>
  </:head>
  <:body as |B|>
    <B.Tr>
      <B.Td>{{B.data.artist}}</B.Td>
      <B.Td>{{B.data.album}}</B.Td>
      <B.Td>{{B.data.year}}</B.Td>
    </B.Tr>
  </:body>
</Hds::Table>

For documentation purposes, we imitated fetching data from an API and working with that as our data model.

import Route from '@ember/routing/route';

export default class ComponentsTableRoute extends Route {
  async model() {
    let response = await fetch('/api/folk.json');
    let { data } = await response.json();

    return data.map((model) => {
      let { attributes } = model;
      return { ...attributes };
    });
  }
}

Sortable Table

For the sortable table, the invocation and use is a little bit different:

1. Shape the data model for use; in this example we've placed it in the page's route. In this example, we're identifying the column headers (keys) and also capitalizing them. Each column object has two pieces: a key-- used for the model, the sortingKeys and sortBy; and the label-- used in the table header cells.

// app/routes/components/table.js

import Route from '@ember/routing/route';
import { capitalize } from '@ember/string';

export default class ComponentsTableRoute extends Route {
  async model() {
    let response = await fetch('/api/folk.json');
    let { data } = await response.json();

    // make sure the variable is declared outside of the loop
    // so we can return it in the model response
    let columns;
    let dataResponse = data.map((model) => {
      let { id, attributes } = model;
      columns = Object.keys(attributes);
      return { id, ...attributes };
    });
    columns = columns.map((column) => {
      return { key: column, label: capitalize(column) };
    });
    return { data: dataResponse, columns };
  }
}

2. Invoke the Hds::Table component in your template file.

<!-- app/templates/components/table.hbs -->

<Hds::Table
  @model={{this.model.data}}
  @columns={{this.model.columns}}
>
  <:body as |B|>
    <B.Tr>
      <B.Td>{{B.data.artist}}</B.Td>
      <B.Td>{{B.data.album}}</B.Td>
      <B.Td>{{B.data.year}}</B.Td>
    </B.Tr>
  </:body>
</Hds::Table>

If you want, you can indicate that only specific columns should be sortable.

<!-- app/templates/components/table.hbs -->

<Hds::Table
  @model={{this.model.data}}
  @columns={{this.model.columns}}
  @sortingKeys={{array 'artist' 'album'}}
>
  <:body as |B|>
    <B.Tr>
      <B.Td>{{B.data.artist}}</B.Td>
      <B.Td>{{B.data.album}}</B.Td>
      <B.Td>{{B.data.year}}</B.Td>
    </B.Tr>
  </:body>
</Hds::Table>

You can also indicate that a specific column should be pre-sorted.

<!-- app/templates/components/table.hbs -->

<Hds::Table
  @model={{this.model.data}}
  @columns={{this.model.columns}}
  @sortingKeys={{array 'artist' 'album'}}
  @sortBy='artist'
>
  <:body as |B|>
    <B.Tr>
      <B.Td>{{B.data.artist}}</B.Td>
      <B.Td>{{B.data.album}}</B.Td>
      <B.Td>{{B.data.year}}</B.Td>
    </B.Tr>
  </:body>
</Hds::Table>

You can also indicate that a specific column should be pre-sorted in a specific direction.

<!-- app/templates/components/table.hbs -->

<Hds::Table
  @model={{this.model.data}}
  @columns={{this.model.columns}}
  @sortingKeys={{array 'artist' 'album'}}
  @sortBy='artist'
  @sortOrder='desc'
>
  <:body as |B|>
    <B.Tr>
      <B.Td>{{B.data.artist}}</B.Td>
      <B.Td>{{B.data.album}}</B.Td>
      <B.Td>{{B.data.year}}</B.Td>
    </B.Tr>
  </:body>
</Hds::Table>

Here's a table implementation that uses an array hash with localized strings for the column headers, indicates which columns should be sortable, and adds an overflow menu.

<!-- app/templates/components/table.hbs -->

<Hds::Table
  @model={{this.model.data}}
  @columns={{array
      (hash key='artist' label=(t 'components.table.headers.artist'))
      (hash key='album' label=(t 'components.table.headers.album'))
      (hash key='year' label=(t 'components.table.headers.year'))
      (hash key='other' label=(t 'global.titles.other'))
    }}
  @sortingKeys={{array 'artist' 'album' 'year'}}
>
  <:body as |B|>
    <B.Tr>
      <B.Td>{{B.data.artist}}</B.Td>
      <B.Td>{{B.data.album}}</B.Td>
      <B.Td>{{B.data.year}}</B.Td>
      <B.Td>
          <Hds::Dropdown as |dd|>
            <dd.ToggleIcon
              @icon='more-horizontal'
              @text='Overflow Options'
              @hasChevron={{false}}
              @size='small'
            />
            <dd.Interactive @route='components.table' @text='Create' />
            <dd.Interactive @route='components.table' @text='Read' />
            <dd.Interactive @route='components.table' @text='Update' />
            <dd.Separator />
            <dd.Interactive
              @route='components.table'
              @text='Delete'
              @color='critical'
              @icon='trash'
            />
          </Hds::Dropdown>
        </B.Td>
    </B.Tr>
  </:body>
</Hds::Table>

Component API

The Table component itself is where most of options will be applied. However, the child components can also be used if a custom implementation is desired.

  • The Hds::Table::Tr component is a template-only component. It supports use of ...attributes but is not eligible to receive interactions (e.g., it cannot have an onClick event handler attached directly to it). It can contain Hds::Table::Th or Hds::Table::Td components.
  • The Hds::Table::Th component is a template-only component. It supports use of ...attributes but is not eligible to receive interactions itself, although it can contain interactive elements. However, it is not likely that you will need to add interactive elements to this component as the sorting is already otherwise provided for.
  • The Hds::Table::Td component is a template-only component. It supports use of ...attributes but is not eligible to receive interactions itself (e.g., it cannot have an onClick event handler attached directly to it); however, it can contain interactive elements (e.g., <td><a href="user-info.html">User info</a></td>)

Name
<:head>
Type
named block
Description
This is a named block where the content for the table head (<thead>) is rendered.
Name
<:body>
Type
named block
Description
This is a named block where the content for the table body (<tbody>) is rendered.
Name
model
Type
array
Description
If defined, sets the data source that gets yielded by the :body named block.
Name
columns
Type
array
Description
Use an array hash to define your table columns. While key and label are required, other options include isSortable, align (for text-alignment), and width.
Name
sortBy
Type
string
Description
If defined, indicates which column should be pre-sorted when the table is rendered.
Name
sortOrder
Type
string
Values
  • asc (default)
  • desc
Description
Use in conjunction with sortBy. If defined, indicates which direction the column should be pre-sorted in. If not defined, asc is applied by default.
Name
onSort
Type
function
Description
Callback function invoked (if provided) when one of the sortable table headers is clicked, and a sorting is triggered. The function receives the values of sortBy and sortOrder as arguments.
Name
isStriped
Type
boolean
Values
  • false (default)
  • true
Description
Define on the table invocation. If set to true, row striping ("zebra striping") will be applied to the table.
Name
isFixed
Type
boolean
Values
  • false (default)
  • true
Description
If set to true, the table-display(CSS) property will be set to fixed, which will automatically distribute columns equally based on the total width of the table.
Name
density
Type
enum
Values
  • short
  • medium (default)
  • tall
Description
If set, determines the density, or height, of the table’s rows.
Name
valign
Type
enum
Values
  • top (default)
  • middle
Description
If set, determines the vertical alignment for all cell (td) content in a table. Does not apply to table headers (th).
Name
caption
Type
string
Description
Adds a (non-visible) caption for users with assistive technology. If set on a sortable table, the provided table caption is paired with the automatically generated sorted message text.
Name
…attributes
Description
Supported for the Hds::Table component.

Anatomy

Table header anatomy

Element Usage
Label Required
Sort Direction Options: none, ascending, descending

Table cell anatomy

Element Usage
Cell content Required
Icon Optional
Container Required

State

Header column state example

Only header columns that contain a sortable data type have state variants. Non-sortable header columns are not interactive and therefore do not have interactive states.

Conformance rating

Conformant

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

Best practices

For Designers

Can I use tooltips within the Header Column to provide additional information and/or context about a column?

Since columns within the table header can control sorting within the table, the header column is not eligible to receive additional interactive elements such as tooltip/toggletip or other components that rely on interactivity to display content (nested interactive elements).

If you feel you need to use a tooltip, then there probably isn’t enough contextual information about the table or the label within the header isn’t clear enough.

Example of a nested tooltip within a table header

How is focus handled within the table header?

Focus is only relevant for columns that you define as sortable which is not a predefined or default property. While sorting by numerical and string-based values is helpful, sorting doesn’t make sense for all data types within a table.

Example of focus around a table header column

For Developers

There are a few critical items for developers to note:

  • The table row element (tr) is not eligible to receive interactions. That is, actions cannot be attached to a table row. If an interactive element is desired, place it within a table cell element (td) within that row (i.e., <td><a href="somelink.html">Some link</a></td>).
  • When providing additional or alternative styles to the table element, do not change the display property in the CSS. This alters how the table is presented to the user with assistive technology; they will no longer be presented with a table.

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.1 Parsing (Level A):
    In content implemented using markup languages, elements have complete start and end tags, elements are nested according to their specifications, elements do not contain duplicate attributes, and any IDs are unique.
  • 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, please let us know by submitting an issue.