Skip to content

Data Tables13.10.5

Data tables present raw data sets and lend meaning to the data, while maintaining that the data is readable, scannable, and easily comparable.


$ npm i @if-design-system/data-tables@13.10.5

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Edit this section

Overview

Data tables display information in a way that’s easy to scan, so that users can look for patterns and insights. They can be embedded in primary content, such as cards, blocks or panels.

When to use

  • To organize and display data.
  • If your user must navigate to a specific piece of data to complete a task.
  • Displaying all of a user’s resources.

When not to use

  • When a more complex display of the data or interactions are required.
  • As a replacement for a spreadsheet application.

Principles

Header row

Use meaningful text to label the table data and provide clarity to the content below.

Sortable

All columns should by default be sortable

Number of columns

For large sets of data, it is preferable to use rows instead of columns in order to guide the eye across the page. It is quicker and easier to scan down a single row rather than scan across multiple columns of related data.

Batch actions

Batch actions are functions that may be performed on multiple items within a table. Use a menu to present batch actions to the user. The menu appears when the user clicks on the icon.

Inline actions

Inline actions are functions that may be performed on a specific table item. Each row is accompanied by a menu or a button that contains actions related specifically to that table row.

Expandable rows

Expandable rows show minimal information at a high-level and a more detailed view nested within that row.

Striped table

Use striped table when you have text on more than one row, or large datasets.

Hover on row

Not needed for small tables, preferably on larger tables (datasets, wider) and denser tables.

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Edit this section

Variations

Type Purpose
Default The default data table comes with a base style with only the title, header, and table elements. rows.
With selection Batch actions are functions that may be performed on multiple items within a table. This type of table enables the user to select individual rows and apply an action. A batch action toolbar appears when table rows are selected.
With expansion The expandable data table is useful for presenting large amounts of data in a small space. Rows are collapsed and can be expanded to reveal extra information.
Edit this section

Usage

Sizing

The data table is available in two different row sizes: default and condensed.

Add the .condensed class to table to minimize the spacing in cells.

Name Age Position
John 38 Hitman
John 38 Hitman
John 38 Hitman
John 38 Hitman
Name Age Position
John 38 Hitman
John 38 Hitman
John 38 Hitman
John 38 Hitman
<table class="if table condensed"></table>

Placement

Data tables should be placed in a page's main content area and given plenty of space to display data without truncation. Avoid placing data tables inside modals or smaller containers where the information can feel cramped or needs truncation.

The data table can be placed on the grid following the four different grid modes outlined in the layout section. Although, the data table can share horizontal space with other components and content, consider giving your data table the most width on the page to help your user view dense data.

These four examples show the data table on the default, wide, fluid and across grid modes.

Default

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000

Sed accumsan dapibus ligula et maximus. Integer vel suscipit nisl. Nulla facilisis cursus nisi, sed pretium justo tincidunt eget. In pharetra augue tellus, sed pellentesque diam malesuada vitae. Aenean at diam quis nunc maximus elementum sit amet at quam. Vivamus tincidunt et ex eget mollis. Suspendisse rhoncus ultricies dolor, nec finibus dui pellentesque ut. Vestibulum luctus ut lectus et hendrerit. Morbi lobortis justo at sem sagittis, in euismod metus pellentesque. In in efficitur justo. Pellentesque fringilla odio posuere tempus ullamcorper.

Wide

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000

Sed accumsan dapibus ligula et maximus. Integer vel suscipit nisl. Nulla facilisis cursus nisi, sed pretium justo tincidunt eget. In pharetra augue tellus, sed pellentesque diam malesuada vitae. Aenean at diam quis nunc maximus elementum sit amet at quam. Vivamus tincidunt et ex eget mollis. Suspendisse rhoncus ultricies dolor, nec finibus dui pellentesque ut. Vestibulum luctus ut lectus et hendrerit. Morbi lobortis justo at sem sagittis, in euismod metus pellentesque. In in efficitur justo. Pellentesque fringilla odio posuere tempus ullamcorper.

Fluid

Name Age Position
John Wicker 38 Hitman
John Wicker 38 Hitman
John Wicker 38 Hitman
John Wicker 38 Hitman

Sed accumsan dapibus ligula et maximus. Integer vel suscipit nisl. Nulla facilisis cursus nisi, sed pretium justo tincidunt eget. In pharetra augue tellus, sed pellentesque diam malesuada vitae. Aenean at diam quis nunc maximus elementum sit amet at quam. Vivamus tincidunt et ex eget mollis. Suspendisse rhoncus ultricies dolor, nec finibus dui pellentesque ut. Vestibulum luctus ut lectus et hendrerit. Morbi lobortis justo at sem sagittis, in euismod metus pellentesque. In in efficitur justo. Pellentesque fringilla odio posuere tempus ullamcorper.

Across

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000

Sed accumsan dapibus ligula et maximus. Integer vel suscipit nisl. Nulla facilisis cursus nisi, sed pretium justo tincidunt eget. In pharetra augue tellus, sed pellentesque diam malesuada vitae. Aenean at diam quis nunc maximus elementum sit amet at quam. Vivamus tincidunt et ex eget mollis. Suspendisse rhoncus ultricies dolor, nec finibus dui pellentesque ut. Vestibulum luctus ut lectus et hendrerit. Morbi lobortis justo at sem sagittis, in euismod metus pellentesque. In in efficitur justo. Pellentesque fringilla odio posuere tempus ullamcorper.

<div class="if block">
  <div class="if grid [wide|fluid|across]">
    <table ></table>
  </div>
</div>

Alignment

Registravimo data Paslauga Įstaigos pavadinimas Sveikatos išlaidų grupė Statusas Paslaugos kaina, EUR
By default, the table cell is vertically aligned to top 38 Hitman London 01.01.1971 833.000 €
Default text alignment is always to the left 38 Hitman London 01.01.1971 4.123 €
Numbers should always be aligned to the right 38 Hitman London 01.01.1971 833.000 €

Principles

  • By default, the table cell is vertically aligned to top
  • Default text alignment is always to the left
  • Numbers should always be aligned to the right, but there can be exceptions
Edit this section

Behaviours

Interactions

Pointer

A row can be selected:

Name Age Position Office Salary
John Wicker 38 Hitman London 833 000
John Wicker 38 Hitman London 833 000
Expandable 38 Hitman London 833 000
John Wicker 38 Hitman London 83 312 000
Selected row

The color used for the outline is BL 1, BLUE. The color used for the fill is BL 1, BLUE 5%

  1. Selected row
  2. Checkbox
<tr class="if is-selected">
  <td class="if">
    <div class="if" style="position:relative;">
      <input
        id="behaviour-selected-row-4"
        type="checkbox"
        checked
        class="if checkbox standalone"
        aria-label="Select this row"
      />
      <label for="behaviour-selected-row-4"></label>
    </div>
  </td></tr>

Hover

Row hover state:

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000

See the documentation for adding hoverable feature.

Focus and focus-within

Focused row, or focus within row state:

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Focused/focused within row

Remember to use tabindex="0" (or any preferred index) so it is accessible.

  1. The color used for the focus ring is CB 3, LIGHT BLUE

Sorting

Columns can be sorted in ascending or descending order. Sorting controls are located in the column headers and indicated with an arrow icon on hover and when a column has been sorted.

A sorted data table has three states: unsorted (no arrows), ascending (arrow up) or descending (arrow down). The icon indicates the current sorted state and is only shown if sorting is activated. Only the column being sorted should display an icon, and unsorted icons are only visible on hover.

Name Age
C 38
B 38
A 38

NOTE! Table columns should be sortable by default

Name Age Birthdate
C 38 1983-11-10
B 38 1983-11-10
A 38 1983-11-10
Sort controls
  1. On hover
  2. Descending sort
  3. Ascending sort
Style
  1. The color used for the hovered arrow is BR 1, BROWN
  2. The color used for the hovered arrow is LB 1, LIGHT BROWN
  3. The color used for the hovered arrow is LB 1, LIGHT BROWN
Markup

This is the generated markup for sorting.

<table class="if table">
  <thead class="if">
    <tr class="if ">
      <th tabindex="0" scope="col" class="if is-hovered">
        Name<span class="if inline-nowrap" title="Sort column" aria-label="Sort column">
          <span class="if sort"></span
        ></span>
      </th>
      <th tabindex="0" scope="col" class="if">
        Age<span class="if inline-nowrap" title="Sort column" aria-label="Sort column">
          <span class="if sort"></span
        ></span>
      </th>
    </tr>
  </thead>
  <tbody class="if"></tbody>
</table>

If you want to implement this via a framework, e.g. React, you need to add this markup to the column headers:

<span class="if inline-nowrap" title="Sort column" aria-label="Sort column">&#xfeff;<span class="if sort"></span></span>

And add a clickHandler to the th-tags, that toggles the classes required to dictate sorting; is-ascending and is-descending.

Name Age Birthdate
C 38 1983-11-10
B 38 1983-11-10
A 38 1983-11-10
Not sorted
<th tabindex="0" scope="col" class="if">
  Age<span class="if inline-nowrap" title="Sort column" aria-label="Sort column"> <span class="if sort"></span></span>
</th>
Name Age Birthdate
C 38 1983-11-10
B 39 1983-11-10
A 58 1983-11-10
Ascending
<th tabindex="0" scope="col" class="if is-ascending">
  Age<span class="if inline-nowrap" title="Sort column" aria-label="Sort column"> <span class="if sort"></span></span>
</th>
Name Age Birthdate
C 58 1983-11-10
B 39 1983-11-10
A 28 1983-11-10
Descending
<th tabindex="0" scope="col" class="if is-descending">
  Age<span class="if inline-nowrap" title="Sort column" aria-label="Sort column"> <span class="if sort"></span></span>
</th>
Example JavaScript implementation code
const addClass = (el, className) => {
  if (className && className.indexOf(' ') !== -1) {
    className.split(' ').forEach(cl => el.classList.add(cl));
  } else {
    el.classList.add(className);
  }
};

const createSortElement = (label, className = 'if sort', tag = 'span') => {
  const fragment = document.createDocumentFragment();
  const el = document.createElement(tag);
  const wrapper = document.createElement('span');

  wrapper.classList.add('if');
  wrapper.classList.add('inline-nowrap');

  wrapper.innerHTML = '&#xfeff;';
  wrapper.appendChild(el);
  wrapper.setAttribute('title', label);
  wrapper.setAttribute('aria-label', label);
  addClass(el, className);

  fragment.appendChild(wrapper);

  return fragment;
};

const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;

const comparer = (idx, asc) => (a, b) =>
  ((v1, v2) => (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)))(
    getCellValue(asc ? a : b, idx),
    getCellValue(asc ? b : a, idx)
  );

const handleTHClick = function(e) {
  const th = e.target;
  const table = th.closest('table');
  table.querySelectorAll('th.is-ascending').forEach(el => {
    if (!th.isSameNode(el)) {
      el.classList.remove('is-ascending');
    }
  });
  table.querySelectorAll('th.is-descending').forEach(el => {
    if (!th.isSameNode(el)) {
      el.classList.remove('is-descending');
    }
  });
  if (!th.classList.contains('is-ascending') && !th.classList.contains('is-descending')) {
    th.classList.add('is-descending');
    th.classList.remove('is-ascending');
  } else if (th.classList.contains('is-descending')) {
    th.classList.add('is-ascending');
    th.classList.remove('is-descending');
  } else {
    th.classList.add('is-descending');
    th.classList.remove('is-ascending');
  }
  const tbody = table.querySelector('tbody');
  Array.from(tbody.querySelectorAll('tr:not(.is-expanded)'))
    .sort(comparer(Array.from(th.parentNode.children).indexOf(th), (this.asc = !this.asc)))
    .forEach(tr => tbody.appendChild(tr));
};

const initTableSorter = table => {
  if (
    table.classList.contains('col-left') ||
    table.classList.contains('expandable') ||
    table.classList.contains('calendar')
  )
    return;
  const headers = table.querySelectorAll('thead > tr > th');
  headers.forEach(th => {
    if (th.textContent === '') return false;
    if (th.querySelector('.if.standalone')) return false;
    th.setAttribute('tabindex', 0);
    if (th.querySelector('.if.sort')) {
      th.removeEventListener('click', handleTHClick);
      th.removeEventListener('keydown', e => {
        if (e.key === 'Enter') {
          handleTHClick(e);
        }
      });
    } else {
      th.appendChild(createSortElement('Sort column'));
    }

    th.addEventListener('click', handleTHClick);
    th.addEventListener('keydown', e => {
      if (e.key === 'Enter') {
        handleTHClick(e);
      }
    });
  });
};

Pagination

When there is a lot of content to display, using pagination will help us to add navigation links to reveal more content on the page.

Primarily, put pagination on the bottom of the table. If the table does not fit on the screen, use pagination above the table aswell.

See the pagination component for more information.

Default pagination
Minimal pagination
Default pagination with disabled arrows

Disabled buttons is shown when the data set is at the end, or beginning of the data set.

Expandable

Expandable table

The color for the expanded bar is BR 1, BROWN 50% WHITE. The fill for the expanded row is BE 4, LIGHTER BEIGE

  1. Expanded row
  2. Expanded content
Implementation code for expandable table

Always remember to use the correct aria-*-attributes to make it accessible

<table class="if table expandable">
  <thead class="if"></thead>
  <tbody class="if">
    <tr class="if expandable">
      <td class="if" scope="row" tabindex="0" aria-controls="controlled-expandable-content-1" aria-expanded="false">        John Wicker
      </td></tr>
    <tr class="if" id="controlled-expandable-content-1" aria-hidden="true">      <td class="if" colspan="6"></td>
    </tr></tbody>
</table>
const initExandableTables = table => {
  if (!table.classList.contains('expandable')) return;

  const handleToggleExpandableRow = e => {
    const td = e.target;
    const row = td.parentElement;
    const expandableControl = row.querySelector('td:first-of-type');
    const isExpanded = row.classList.contains('is-expanded');
    const expandedRows = table.querySelectorAll('tr.expandable.is-expanded');
    const expandableContent = row.nextSibling;
    if (isExpanded) {
      row.classList.remove('is-expanded');
      expandableControl.setAttribute('aria-expanded', false);
      expandableContent.setAttribute('aria-hidden', true);
    } else {
      // Close other expanded rows
      expandedRows.forEach(tr => {
        tr.classList.remove('is-expanded');
        tr.querySelector('td:first-of-type').setAttribute('aria-expanded', false);
        tr.nextSibling.setAttribute('aria-hidden', true);
      });
      row.classList.add('is-expanded');
      expandableControl.setAttribute('aria-expanded', true);
      expandableContent.setAttribute('aria-hidden', false);
    }
  };

  table.querySelectorAll('tr.expandable > td:first-child').forEach(td => {
    td.addEventListener('keydown', e => {
      if (e.key === 'Enter') {
        handleToggleExpandableRow(e);
      }
    });
    td.addEventListener('click', handleToggleExpandableRow);
  });
};

Global actions

If needed, global actions can be added to the table. Basically it is buttons with relevant actions to the whole table, like export or download. Actions for selected rows can also utilize this, i.e. in a form.

Name Age Position Office Salary Availability
John Wicker 38 Hitman London 833 000 Available
John Wicker 38 Hitman London 833 000 Available
Expandable 38 Hitman London 833 000 Available
John Wicker 38 Hitman London 83312 000 Available
Global actions
  1. Table global actions
Name Age Position Office Salary Availability
John Wicker 38 Hitman London 833 000 Available
John Wicker 38 Hitman London 833 000 Available
Expandable 38 Hitman London 833 000 Available
John Wicker 38 Hitman London 83312 000 Available
Global actions with form
  1. Table global actions

Toolbar

The toolbar contains search and filters.

Filters
Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Toolbar
  1. Search
  2. Filters
Implementation code for table toolbar
<div class="if table-toolbar">
  <div class="if filter-search-holder search-field">
    <input type="search" class="if" placeholder="Search table" aria-label="Search/Filter by" />
    <button type="button" class="if reset" aria-label="Reset search"></button>
  </div>
  <div class="if filter-tags-holder"></div>
  <div class="if table-filters" aria-describedby="behaviours-toolbar-table-fiters-title-1">
    <span class="if title" id="behaviours-toolbar-table-fiters-title-1">Filters<span aria-hidden="true">:</span></span>
    <ul class="if dropdown-filter-groups">
      <li class="if">
        <button
          type="button"
          aria-controls="behaviours-toolbar-office-filters-1"
          aria-haspopup="true"
          role="combobox"
          aria-owns="behaviours-toolbar-office-filters-1"
          aria-expanded="true"
          class="if dropdown-filter-button"
        >
          Office
        </button>
        <div class="if dropdown-filter center" id="behaviours-toolbar-office-filters-1" role="listbox">
          <ul class="if" aria-activedescendant="behaviours-toolbar-office-filter-2">
            <li class="if">
              <input
                id="behaviours-toolbar-office-filter-1"
                type="checkbox"
                role="option"
                aria-selected="false"
                data-rel="London"
                class="if checkbox"
              />
              <label class="if" for="behaviours-toolbar-office-filter-1">London</label>
            </li>
            <li class="if">
              <input
                id="behaviours-toolbar-office-filter-2"
                type="checkbox"
                role="option"
                aria-selected="false"
                data-rel="Moscow"
                class="if checkbox"
              />
              <label class="if" for="behaviours-toolbar-office-filter-2">Moscow</label>
            </li>
            <li class="if">
              <input
                id="behaviours-toolbar-office-filter-3"
                type="checkbox"
                role="option"
                aria-selected="false"
                data-rel="Oslo"
                class="if checkbox"
              />
              <label class="if" for="behaviours-toolbar-office-filter-3">Oslo</label>
            </li>
            <li class="if">
              <input
                id="behaviours-toolbar-office-filter-4"
                type="checkbox"
                role="option"
                aria-selected="false"
                data-rel="Helsinki"
                class="if checkbox"
              />
              <label class="if" for="behaviours-toolbar-office-filter-4">Helsinki</label>
            </li>
            <li class="if">
              <input
                id="behaviours-toolbar-office-filter-5"
                type="checkbox"
                role="option"
                aria-selected="false"
                data-rel="Paris"
                class="if checkbox"
              />
              <label class="if" for="behaviours-toolbar-office-filter-5">Paris</label>
            </li>
          </ul>
        </div>
      </li>
      <li class="if">
        <button
          type="button"
          aria-controls="behaviours-toolbar-availability-filters-1"
          aria-haspopup="true"
          role="combobox"
          aria-owns="behaviours-toolbar-availability-filters-1"
          aria-expanded="true"
          class="if dropdown-filter-button"
        >
          Availability
        </button>
        <div class="if dropdown-filter center" id="behaviours-toolbar-availability-filters-1" role="listbox">
          <ul class="if" aria-activedescendant="behaviours-toolbar-availability-filter-2">
            <li class="if">
              <input
                id="behaviours-toolbar-availability-filter-1"
                type="checkbox"
                role="option"
                aria-selected="false"
                data-rel="Available"
                class="if checkbox"
              />
              <label class="if" for="behaviours-toolbar-availability-filter-1">Available</label>
            </li>
            <li class="if">
              <input
                id="behaviours-toolbar-availability-filter-2"
                type="checkbox"
                role="option"
                aria-selected="false"
                data-rel="Retired"
                class="if checkbox"
              />
              <label class="if" for="behaviours-toolbar-availability-filter-2">Retired</label>
            </li>
            <li class="if">
              <input
                id="behaviours-toolbar-availability-filter-3"
                type="checkbox"
                role="option"
                aria-selected="false"
                data-rel="Dead"
                class="if checkbox"
              />
              <label class="if" for="behaviours-toolbar-availability-filter-3">Dead</label>
            </li>
            <li class="if">
              <input
                id="behaviours-toolbar-availability-filter-4"
                type="checkbox"
                role="option"
                aria-selected="false"
                data-rel="On mission"
                class="if checkbox"
              />
              <label class="if" for="behaviours-toolbar-availability-filter-4">On mission</label>
            </li>
          </ul>
        </div>
      </li>
    </ul>
  </div>
</div>

Bulk actions

Batch actions are functions that may be performed on multiple items within a table. Once the user selects at least one row from the table, the batch action bar appears at the top of the table, presenting the user with actions they can take. To exit or escape “batch action mode”, the user can cancel out or deselect the items.

Performing actions on 3 items
Name Age Position Office Salary Availability
John Wicker 38 Hitman London 833 000 Available
John Wicker 38 Hitman London 833 000 Available
Expandable 38 Hitman London 833 000 Available
John Wicker 38 Hitman London 83312 000 Available
Bulk actions

The color used for the fill is BL 1, BLUE. The color used for the text is BE 5, LIGHTEST BEIGE. The hover state fill for the actions is BL 1 DARK, DARK BLUE

  1. Table bulk actions
  2. Hovered action
  3. Action
  4. Indicator on how many items are to be used in the action
Implementation code for bulk actions
<div class="if table-bulk-actions">
  <button type="button" class="if" data-title="Hire">Hire</button>
  <button type="button" class="if" data-title="Send message">Send message</button>
  <button type="button" class="if" data-title="Retire">Retire</button>
  <button type="button" class="if js-table-bulk-cancel" data-title="Cancel">Cancel</button>
  <span class="if description"
    >Performing actions on <strong class="if js-table-bulk-select-selected-items">3 items</strong></span
  >
</div>
<table class="if table …"></table>
const getPreviousSibling = (el, selector) => {
  // Get the next sibling element
  let sibling = el.previousElementSibling;

  // If there's no selector, return the first sibling
  if (!selector) return sibling;

  // If the sibling matches our selector, use it
  // If not, jump to the next sibling and continue the loop
  while (sibling) {
    if (sibling.matches(selector)) return sibling;
    sibling = sibling.previousElementSibling;
  }
};

const getNumberOfCheckedBoxes = table => table.querySelectorAll('tr > td input[type="checkbox"]:checked').length;

const toggleBulkActions = (table, checkbox) => {
  const bulkActions = getPreviousSibling(table, '.if.table-bulk-actions');
  if (!checkbox || !bulkActions) return;
  const selectedSumElement = bulkActions.querySelector('.js-table-bulk-select-selected-items');
  const numberOfCheckedBoxes = getNumberOfCheckedBoxes(table);

  if (!checkbox.checked && !checkbox.indeterminate) {
    bulkActions.classList.remove('is-open');
  } else if (checkbox.checked || checkbox.indeterminate || !bulkActions.classList.contains('is-open')) {
    selectedSumElement.textContent = `${numberOfCheckedBoxes} item(s)`;
    bulkActions.classList.add('is-open');
  }
};

const handleMasterCheckboxChange = e => {
  const masterCheckbox = e.target;
  const masterRow = masterCheckbox.closest('tr');
  const isSelected = masterRow.classList.contains('is-selected');
  const table = e.target.closest('table');
  const checkboxes = table.querySelectorAll('tr > td input[type="checkbox"]');

  if (masterCheckbox.checked) {
    masterCheckbox.indeterminate = false;
  }

  if (isSelected) {
    masterRow.classList.remove('is-selected');
  } else {
    masterRow.classList.add('is-selected');
  }

  checkboxes.forEach(checkbox => {
    const row = checkbox.closest('tr');
    if (masterCheckbox.checked) {
      row.classList.add('is-selected');
      checkbox.checked = true;
    } else {
      row.classList.remove('is-selected');
      checkbox.checked = false;
    }

    toggleBulkActions(table, masterCheckbox);
  });
};

const setCheckboxIndeterminate = (table, checkbox) => {
  const numberOfBoxes = table.querySelectorAll('tr > td input[type="checkbox"]').length;
  const numberOfCheckedBoxes = getNumberOfCheckedBoxes(table);

  if (numberOfCheckedBoxes != numberOfBoxes && numberOfCheckedBoxes !== 0) {
    checkbox.indeterminate = true;
  } else {
    checkbox.indeterminate = false;
  }
};

const toggleMasterCheckbox = (table, checkbox) => {
  const numberOfBoxes = table.querySelectorAll('tr > td input[type="checkbox"]').length;
  const numberOfCheckedBoxes = getNumberOfCheckedBoxes(table);

  if (numberOfCheckedBoxes === numberOfBoxes) {
    checkbox.checked = true;
  } else {
    checkbox.checked = false;
  }
};

const initMasterCheckboxChangeEvent = masterCheckbox => {
  masterCheckbox.addEventListener('change', handleMasterCheckboxChange);
};

const initCheckboxChangeEvents = (table, masterCheckbox) => {
  const checkboxes = table.querySelectorAll('tr > td input[type="checkbox"]');

  checkboxes.forEach(checkbox => {
    checkbox.addEventListener('change', function(e) {
      if (e.target.closest('tr').classList.contains('is-selected')) {
        e.target.closest('tr').classList.remove('is-selected');
      } else {
        e.target.closest('tr').classList.add('is-selected');
      }
      toggleMasterCheckbox(table, masterCheckbox);
      setCheckboxIndeterminate(table, masterCheckbox);
      toggleBulkActions(table, masterCheckbox);
    });
  });
};

const initBulkActionTables = table => {
  const masterCheckbox = table.querySelector('.js-table-bulk-select');
  if (!masterCheckbox) return;
  if (masterCheckbox.checked) {
    masterCheckbox.indeterminate = false;
  }

  setCheckboxIndeterminate(table, masterCheckbox);
  toggleBulkActions(table, masterCheckbox);
  initMasterCheckboxChangeEvent(masterCheckbox);
  initCheckboxChangeEvents(table, masterCheckbox);
};

Inline actions

Försäkring Betalningssätt Action
Villaförsäkring, LYXVILLAN PÅ SOLSIDAN Helårsvis
Hemförsäkring, Testgatan 1 Helårsvis
Olycksfallsförsäkring, TESTTVÅ, MYPAGES Helårsvis
Inline actions with text controls
  1. Text control
Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Expandable 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 83312 000
Inline actions with icon controls
  1. Icon control
Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Expandable 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 83312 000
Inline actions with multiple actions
  1. Menu control
Implementation code for inline actions
<button class="if button control text" type="button">
  <span class="if  icon ui clock blue"></span>Ändra betalningssätt
</button>
<button type="button" class="if button control delete" aria-label="Delete row"></button>
<button type="button" class="if contextual-menu-button js-table-row-menu" aria-label="Open row menu"></button>
<nav class="if contextual-menu">
  <ul class="if">
    <li class="if">
      <button class="if" type="button">
        Add to watch list
      </button>
    </li>
    <li class="if separator"></li>
    <li class="if">
      <button class="if" type="button">
        <span class="if icon ui trashcan"></span>
        Delete
      </button>
    </li>
  </ul>
</nav>
Example JavaScript implementation for inline actions

To open the menu, add a clickHandler to the button, that toggles the .is-open-class on .if.menu.

const initRowActions = () => {
  const rowActions = document.querySelectorAll('.js-table-row-menu');

  const handleClick = e => {
    const el = e.target;
    const menu = el.parentElement.querySelector('.if[class*="menu"]:not(button)');
    menu.classList.toggle('is-open');
  };

  rowActions.forEach(el => {
    const menu = el.parentElement.querySelector('.if[class*="menu"]:not(button)');
    const elRect = el.getBoundingClientRect();
    const parentRect = el.parentElement.getBoundingClientRect();
    const top = elRect.top - parentRect.top + elRect.height;
    menu.style.top = `${top}px`;
    menu.style.right = 0;
    el.removeEventListener('click', handleClick);
    el.addEventListener('click', handleClick);
  });
};

Modifiers

Embedded

Tables can be embedded in different content, like cards, panels and modals.

Striped

Add the .striped class to the table element to get a striped table.

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Striped rows
  1. The color used for the alternating background is LB 1, LIGHT BROWN 10%
<table class="if table striped"></table>

Highlight row

A row can be highlighted. This is useful if you want to highlight a row/several rows for reference.

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Highlighted row

The color used for the outline is BL 1, BLUE. The color used for the fill is BL 1, BLUE 5%

  1. Selected row
<tr class="if is-selected"></tr>

Unread row

A row can be marked as unread:

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Unread
  1. The color used for the unread bar is BL 1, BLUE
<tr class="if is-unread"></tr>

Interactive row

A row can be interactive:

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Interactive rows

Add is-interactive to the row you want to have a pointer cursor on hover to indicate that it is interactive. Remember to use tabindex="0" (or any preferred index) so it is accessible.

  1. The color used for the focus ring is CB 3, LIGHT BLUE
<tr class="if is-interactive" tabindex="0"></tr>

Hoverable

Add the class .is-hoverable to the table element to let each row be highlighted on hover. Not needed for small tables, preferably on larger tables (datasets, wider) and denser tables.

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Hoverable table rows
  1. The color used for hover is LB 1, LIGHT BROWN 20%

Filled headers

Add .filled to the thead element to get a background on the horizontal table headers.

<thead class="if filled"></thead>
Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Filled horizontal headers
  1. The color as background is BE 1, DARK BEIGE
  2. The column header text color is BR 1, BROWN

Add .filled to the table element to get a background on the vertical table headers.

<table class="if table col-left filled"></table>
Name John Wicker John Wicker John Wicker John Wicker John Wicker John Wicker
Age 38 38 38 38 38 38
Position Hitman Hitman Hitman Hitman Hitman Hitman
Office London London London London London London
Start date 01.01.1971 01.01.1971 01.01.1971 01.01.1971 01.01.1971 01.01.1971
Salary 833 000 833 000 833 000 833 000 83312 000 833 000
Filled vertical headers
  1. The color as background is BE 1, DARK BEIGE

Sticky

A table can have sticky headers. This might make the table a bit responsive in some cases:

position: sticky can't be used in Chrome/Edge/Opera on <thead> tags, only <th> tags: https://caniuse.com/css-sticky, it's a bug in chrome and edge: https://bugs.chromium.org/p/chromium/issues/detail?id=702927 https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/16765952/.

So a workaround is required using a mix of position: absolute and position: fixed on scroll.

Name Age Position Office Start date Salary Office Start date Salary Name Age
John Wicker 01.01.1971 833 000 John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 01.01.1971 833 000 John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 01.01.1971 833 000 John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 01.01.1971 833 000 John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 32 Hitman 01.01.1971 833 000 John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 32 Hitman 01.01.1971 833 000 John Wicker 38 Hitman London 01.01.1971 833 000
123 123 123
Sticky headers

When using sticky headers, the table headers always get a dark background.

Name John Wicker John Wicker John Wicker John Wicker John Wicker John Wicker John Wicker John Wicker John Wicker John Wicker John Wicker John Wicker
Age 38 38 38 38 38 38 38 38 38 38 38 38
Position Hitman Hitman Hitman Hitman Hitman Hitman Hitman Hitman Hitman Hitman Hitman Hitman
Office London London London London London London London London London London London London
Start date 01.01.1971 01.01.1971 01.01.1971 01.01.1971 01.01.1971 01.01.1971 01.01.1971 01.01.1971 01.01.1971 01.01.1971 01.01.1971 01.01.1971
Salary 833 000 833 000 833 000 833 000 83312 000 833 000 833 000 833 000 833 000 833 000 83312 000 833 000
Sticky headers

When using sticky headers, the table headers always get a dark background.

<div class="if table-container" style="max-height: 20rem; overflow-y: scroll;">
  <table class="if table sticky"></table>
</div>
<div class="if table-container" style="max-width: 100%; overflow-x: scroll;">
  <table class="if table col-left sticky"></table>
</div>

Status

Different statuses can be applied to table elements:

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Row statuses

A status could be: Error, Warning or OK

  1. The color used is CG 3, LIGHT GREEN
  2. The color used is CY 3, LIGHT YELLOW
  3. The color used is CR 3, LIGHT RED

The background colors used is blended with mix-blend-mode: multiply; and opacity: 0.5;

<tr class="if [is-ok|is-warning|is-error]"></tr>

If you want to add statuses to every row, use the Status Indicator:

Name Age Position Office Salary Availability
John Wicker 38 Hitman London 833 000 Available
John Wicker 38 Hitman London 833 000 Available
Expandable 38 Hitman London 833 000 Retired
John Wicker 38 Hitman London 83312 000 Available
Wicked Witch 62 Beheader Moscow 622 430 Away
Black Widow 34 Undercover Agent Copenhagen 1 351 400 Dead
Deadpool ?? Pain in the ass ?? Free Available
Spawn 43 Demon New York Free Away
<td class="if"><span class="if status-indicator [active|in-progress|error|success|pause">Away</span></td>

A cell can have different statuses:

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Cell statuses

A status could be: Error, Warning or OK

  1. On hover
  2. Descending sort
  3. Ascending sort
<td class="if [is-ok|is-warning|is-error]"></td>

Table container

A table is to be placed inside a table container, <div class="if table-container"> when applicable. It is essentially a wrapper with no extra styling.

However, you can use this wrapper to emphasize a table, by using it as a box, with the if.table-container.box-classes.

Table title

Additional description if needed

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Edit this section

Responsive

If you want to display the table in a narrower vieport, i.e. mobile or table, you need to add some more sugaring to the component. Here are 4 examples on how you can present the table to the user.

Use which ever method that suits the table best.

Name Age Position Office Start date Salary Office Start date Salary Name Age
John Wicker 01.01.1971 833 000 John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 32 Hitman 01.01.1971 833 000 John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 32 Hitman 01.01.1971 833 000 John Wicker 38 Hitman London 01.01.1971 833 000
123 123 123

Mobile table version 1

This table is displayed as separate cards with a table inside, headings to the left, content to the right, in mobile view

Mobile table version 1

<div class="if table-container">
  <table
    class="if table responsive-data"
    aria-describedby="table-responsive-data-description-1"
    aria-labelledby="table-responsive-data-title-1"
  >
    <thead class="if filled">
      <tr class="if">
        <th scope="col" class="if">Name</th>
        <th scope="col" class="if">Age</th>
        <th scope="col" class="if">Position</th>
        <th scope="col" class="if">Office</th>
        <th scope="col" class="if">Start date</th>
        <th scope="col" class="if">Salary</th>
        <th scope="col" class="if">Office</th>
        <th scope="col" class="if">Start date</th>
        <th scope="col" class="if">Salary</th>
        <th scope="col" class="if">Name</th>
        <th scope="col" class="if">Age</th>
      </tr>
    </thead>
    <tbody class="if">
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">32</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">32</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
    </tbody>
    <tfoot class="if">
      <tr class="if">
        <td class="if" colspan="9"></td>
        <td class="if number" colspan="2">123 123 123</td>
      </tr>
    </tfoot>
  </table>
</div>

Name Age Position Office Start date Salary Office Start date Salary Name Age
John Wicker 01.01.1971 833 000 John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 32 Hitman 01.01.1971 833 000 John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 32 Hitman 01.01.1971 833 000 John Wicker 38 Hitman London 01.01.1971 833 000
123 123 123

Mobile table version 2

This table is displayed as a scrollable table in mobile view

Mobile table version 2

<div class="if table-container responsive md">
  <table
    class="if table responsive-scroll"
    aria-describedby="table-responsive-scroll-description-1"
    aria-labelledby="table-responsive-scroll-title-1"
  >
    <thead class="if filled">
      <tr class="if">
        <th scope="col" class="if">Name</th>
        <th scope="col" class="if">Age</th>
        <th scope="col" class="if">Position</th>
        <th scope="col" class="if">Office</th>
        <th scope="col" class="if">Start date</th>
        <th scope="col" class="if">Salary</th>
        <th scope="col" class="if">Office</th>
        <th scope="col" class="if">Start date</th>
        <th scope="col" class="if">Salary</th>
        <th scope="col" class="if">Name</th>
        <th scope="col" class="if">Age</th>
      </tr>
    </thead>
    <tbody class="if">
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">32</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">32</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
    </tbody>
    <tfoot class="if">
      <tr class="if">
        <td class="if" colspan="9"></td>
        <td class="if number" colspan="2">123 123 123</td>
      </tr>
    </tfoot>
  </table>
</div>

Name Age Position Office Start date Salary Office Start date Salary Name Age
John Wicker 01.01.1971 833 000 John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman John Wicker 38 Hitman 38 Hitman London 01.01.1971 833 000
John Wicker 32 Hitman 01.01.1971 833 000 John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 32 Hitman 01.01.1971 833 000 John Wicker 38 Hitman London 01.01.1971 833 000
123 123 123

The table is too large for this view. Please download or open in new tab.

Download as PDF

Mobile table version 3

The table is hidden and export methods are shown in mobile view. If the usage is to modify, download, if it is to preview, open in new tab

Mobile table version 3

<div class="if table-container responsive sm export">
  <table
    class="if table responsive-export"
    aria-describedby="table-responsive-export-description-1"
    aria-labelledby="table-responsive-export-title-1"
  >
    <thead class="if filled">
      <tr class="if">
        <th scope="col" class="if">Name</th>
        <th scope="col" class="if">Age</th>
        <th scope="col" class="if">Position</th>
        <th scope="col" class="if">Office</th>
        <th scope="col" class="if">Start date</th>
        <th scope="col" class="if">Salary</th>
        <th scope="col" class="if">Office</th>
        <th scope="col" class="if">Start date</th>
        <th scope="col" class="if">Salary</th>
        <th scope="col" class="if">Name</th>
        <th scope="col" class="if">Age</th>
      </tr>
    </thead>
    <tbody class="if">
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">32</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">32</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if">Hitman</td>
        <td data-col="Office" class="if">London</td>
        <td data-col="Start date" class="if">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
    </tbody>
    <tfoot class="if">
      <tr class="if">
        <td class="if" colspan="9"></td>
        <td class="if number" colspan="2">123 123 123</td>
      </tr>
    </tfoot>
  </table>
  <div class="if export" style="flex-direction: column;">
    <p class="if text body">The table is too large for this view. Please download or open in new tab.</p>
    <a
      href="https://github.com/simonbengtsson/jsPDF-AutoTable"
      target="_blank"
      class="if text button export js-tab-export-pdf"
      style=" margin-bottom: 0;width: auto; min-width: 0;"
      ><span class="if icon ui document-pdf blue"></span>Download as PDF</a
    >
    <button
      type="button"
      class="if text button export js-tab-export-csv"
      style=" margin-bottom: 0;width: auto; min-width: 0;"
    >
      <span class="if icon ui document-xlsx blue"></span>Download as Spreadsheet
    </button>
    <button
      type="button"
      class="if text button export js-tab-export-csv"
      style=" margin-bottom: 0;width: auto; min-width: 0;"
    >
      <span class="if icon ui share blue"></span>Open in new tab
    </button>
  </div>
</div>

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000

Mobile table version 4

The preselected rows are hidden in mobile view. This method should either be done manually with css, i.e. setting the cells(columns) to be hidden, or by JavaScript

Mobile table version 4

<div class="if table-container">
  <table
    class="if table"
    aria-describedby="table-responsive-selective-description-1"
    aria-labelledby="table-responsive-selective-title-1"
  >
    <thead class="if">
      <tr class="if">
        <th scope="col" class="if">Name</th>
        <th scope="col" class="if">Age</th>
        <th scope="col" class="if u-hidden-down--md">Position</th>
        <th scope="col" class="if u-hidden-down--md">Office</th>
        <th scope="col" class="if u-hidden-down--md">Start date</th>
        <th scope="col" class="if">Salary</th>
      </tr>
    </thead>
    <tbody class="if">
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if u-hidden-down--md">Hitman</td>
        <td data-col="Office" class="if u-hidden-down--md">London</td>
        <td data-col="Start date" class="if u-hidden-down--md">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if u-hidden-down--md">Hitman</td>
        <td data-col="Office" class="if u-hidden-down--md">London</td>
        <td data-col="Start date" class="if u-hidden-down--md">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if u-hidden-down--md">Hitman</td>
        <td data-col="Office" class="if u-hidden-down--md">London</td>
        <td data-col="Start date" class="if u-hidden-down--md">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
      <tr class="if">
        <td data-col="Name" class="if">John Wicker</td>
        <td data-col="Age" class="if">38</td>
        <td data-col="Position" class="if u-hidden-down--md">Hitman</td>
        <td data-col="Office" class="if u-hidden-down--md">London</td>
        <td data-col="Start date" class="if u-hidden-down--md">01.01.1971</td>
        <td data-col="Salary" class="if number">833 000</td>
      </tr>
    </tbody>
  </table>
</div>

Type Purpose
Default The default data table comes with a base style with only the title, header, and table elements. rows.
With selection Batch actions are functions that may be performed on multiple items within a table. This type of table enables the user to select individual rows and apply an action. A batch action toolbar appears when table rows are selected.
With expansion The expandable data table is useful for presenting large amounts of data in a small space. Rows are collapsed and can be expanded to reveal extra information.

Mobile table version 5

This table is displayed like a definition list with key-value pairs.

Mobile table version 5

<div class="if table-container responsive">
  <table
    class="if table responsive-key-value"
    aria-describedby="table-responsive-key-value-description-2"
    aria-labelledby="table-responsive-key-value-title-2"
  >
    <thead class="if filled">
      <tr class="if">
        <th class="if" style="width: 10rem;">
          Type
        </th>
        <th class="if">
          Purpose
        </th>
      </tr>
    </thead>
    <tbody class="if">
      <tr class="if">
        <td class="if">Default</td>
        <td class="if">
          The default data table comes with a base style with only the title, header, and table elements. rows.
        </td>
      </tr>
      <tr class="if">
        <td class="if">With selection</td>
        <td class="if">
          Batch actions are functions that may be performed on multiple items within a table. This type of table enables
          the user to select individual rows and apply an action. A batch action toolbar appears when table rows are
          selected.
        </td>
      </tr>
      <tr class="if">
        <td class="if">With expansion</td>
        <td class="if">
          The expandable data table is useful for presenting large amounts of data in a small space. Rows are collapsed
          and can be expanded to reveal extra information.
        </td>
      </tr>
    </tbody>
  </table>
</div>
Edit this section

Anatomy

Table title

Additional description if needed

Filters
Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
123 123 123
Anatomy
  1. Table title
  2. Description if needed
  3. Toolbar
  4. Column header
  5. Table row
  6. Table Footer
  7. Pagination
  8. Table global actions
Edit this section

Specs

Name Age Position Office Start date Salary
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
John Wicker 38 Hitman London 01.01.1971 833 000
Spacing tokens used
Token Rem Pixel
size-spacing-8 0.5rem 8px
size-spacing-16 1rem 16px
size-spacing-20 1.25rem 20px
size-spacing-64 4rem 64px
Edit this section

Accessibility

The purpose of data tables is to present tabular information in a grid, or matrix, and to have column or rows that show the meaning of the information in the grid.

Sighted users can visually scan a table. They can quickly make visual associations between data in the table and their appropriate row and/or column headers.

Someone that cannot see the table cannot make these visual associations, so proper markup must be used to make a programmatic association between elements within the table. When the proper HTML markup is in place, users of screen readers can navigate through data tables one cell at a time, and they will hear the column and row headers spoken to them.

Considerations

  • The data table headers accurately describe the data contained in the rows and columns.
  • If the data table has a labels it should be clear and concise.
  • If the data table has a description, aria-describedby should be set on the table element with a value referring to the element containing the description.

To meet the WCAG 2.1 requirements, every table must have the correct attributes set.

Label

Every table should have a label. If it's a separate element, use aria-labelledby; otherwise use aria-label:

<table class="if table" aria-label="A label"></table>

Or:

<table class="if table" aria-labelledby="id-to-label-element"></table>

Description

Not every table needs to have a caption or description, but every table that does should use aria-describedby to connect it with that element:

<table class="if table" aria-describedby="id-to-description-element"></table>

Identify column and row headers

Identify the direction of the table headers. If the headers are horizontally, use scope="col" on the ths:

Header Header Header Header
Cell Cell Cell Cell
Cell Cell Cell Cell
Cell Cell Cell Cell
The headers are in the first row
<table class="if" >
  <thead class="if">
    <tr class="if">
      <th class="if" scope="col"></th>
      <th class="if" scope="col"></th>
      <th class="if" scope="col"></th>
      <th class="if" scope="col"></th>
    </tr>
  </thead>
</table>

If the headers are vertically, use scope="row" on the ths:

Header Cell Cell Cell
Header Cell Cell Cell
Header Cell Cell Cell
The headers are in the first column
<table class="if" >
  <tbody class="if">
    <tr class="if">
      <th class="if" scope="row"></th>      <td class="if"></td>
      <td class="if"></td>
      <td class="if"></td>
    </tr>
  </tbody>
</table>

The rule that applies to layout tables also applies to data tables. Let the browser window determine the width of the table whenever possible, to reduce the horizontal scrolling required of those with low vision. If cell widths need to be defined, use relative values, such a percentages, rather than pixel values. Defined cell heights should generally be avoided so the cell can expand downward to accommodate its content - something especially useful for users with low vision that may enlarge text content.

Resources

Edit this section

Implementation

Here is the default table implementation code for HTML. Features implementation code is listed next to each feature element.

<div class="if table-container">
  <h4 class="if title heading small" id="an-id-for-the-table-title">Table title</h4>
  <p class="if description" id="an-id-for-the-table-descripton">Additional description if needed</p>
  <table class="if table" aria-describedby="an-id-for-the-table-descripton" aria-labelledby="an-id-for-the-table-title">
    <thead class="if">
      <tr class="if">
        <th scope="col" class="if">Name</th>
        <th scope="col" class="if">Age</th>
        <th scope="col" class="if">Position</th>
        <th scope="col" class="if">Office</th>
        <th scope="col" class="if">Start date</th>
      </tr>
    </thead>
    <tbody class="if">
      <tr class="if">
        <td class="if">John Wicker</td>
        <td class="if">38</td>
        <td class="if">Hitman</td>
        <td class="if">London</td>
        <td class="if">01.01.1971</td>
      </tr>
      <tr class="if">
        <td class="if">John Wicker</td>
        <td class="if">38</td>
        <td class="if">Hitman</td>
        <td class="if">London</td>
        <td class="if">01.01.1971</td>
      </tr>
      <tr class="if">
        <td class="if">John Wicker</td>
        <td class="if">38</td>
        <td class="if">Hitman</td>
        <td class="if">London</td>
        <td class="if">01.01.1971</td>
      </tr>
    </tbody>
  </table>
</div>
Edit this section
Contact us