Skip to main content

Your privacy settings

We use cookies to help you with journey planning and relevant disruptions, remember your login and show you content you might be interested in. If you’re happy with the use of cookies by West Midlands Combined Authority and our selected partners, click ‘Accept all cookies’. Or click ‘Manage cookies’ to learn more.

Manage Cookies
Beta

This is a new service - your feedback will help us to improve it.

Components

Table

About

What does it do?

  • A table is used to make information easier for users to compare and scan

When to use it?

  • Use the table component to let users compare information in rows and columns.

When not to use it?

  • Never use the table component to layout content on a page. Instead, use the grid system.

How it works

  • You need to create a heading (title) for the table using <caption> tags. A caption helps users find, navigate and understand tables.
  • Use table headers to tell users what the rows and columns represent. Use the scope attribute to help users of assistive technology distinguish between row and column headers.
  • When comparing columns of numbers, align the numbers to the right in table cells.
  • If you are trying to display lots of columns, contact Design Team to discuss how best to achieve this.

Table with Headers


Table title

Table caption
Header 1Header 2Header 3
Cell 1Cell 2Cell 3
Cell 1Cell 2Cell 3

HTML markup
<table class="wmnds-table">
  <h3>
    Table title
  </h3>
  <caption class="wmnds-table__caption">
    Table caption
  </caption>
  <thead>
    <tr>
      <th scope="col">Header 1</th>
      <th scope="col">Header 2</th>
      <th scope="col">Header 3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row" data-header="Header 1">
        Cell 1
      </th>
      <td data-header="Header 2">
        Cell 2
      </td>
      <td data-header="Header 3">
        Cell 3
      </td>
    </tr>
    <tr>
      <th scope="row" data-header="Header 1">
        Cell 1
      </th>
      <td data-header="Header 2">
        Cell 2
      </td>
      <td data-header="Header 3">
        Cell 3
      </td>
    </tr>
  </tbody>
</table>

Nunjucks markup
{% from "wmnds/components/table/_table.njk" import wmndsTable %}

{{
  wmndsTable(
    {
      caption: "Table Caption",
      title: "Table Title",
      firstCellIsHeader: true,
      classes: "wmnds-m-b-xl",
      rows: [
        [
          {
            contentText: "Cell 1",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          },
          {
            contentText: "Cell 2",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          },
          {
            contentText: "Cell 3",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          }
        ],
        [
          {
            contentText: "Cell 1",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          },
          {
            contentText: "Cell 2",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          },
          {
            contentText: "Cell 3",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          }
        ]
      ],
      head: [
        {
          contentText: "Header 1",
          contentHTML: null,
          classes: null,
          colspan: 1,
          rowspan: 1,
        },
        {
          contentText: "Header 2",
          contentHTML: null,
          classes: null,
          colspan: 1,
          rowspan: 1,
        },
        {
          contentText: "Header 3",
          contentHTML: null,
          classes: null,
          colspan: 1,
          rowspan: 1,
        }
      ],
      hideHeader: false
    })
}}

Nunjucks properties
NameTypeDescription
captionstringCaption text
titlestringTable title (displayed as h3)
firstCellIsHeaderbooleanIf set to false, first cell in table row will be a td instead of a th. Default value is true
classesstringAdditional classes to add to the table container.
rowsarrayRequired. Array of table rows and cells. See options for rows below.
headarrayArray of table head cells. See options for head below. Will not show if hideHeader is true.
hideHeaderbooleanDetermines whether to display a table header row (thead) at the top of the table. Set to false by default.

Options for rows

NameTypeDescription
contentTextstringRequired. If html is set, this is not required. Text for cells in table rows. If html is provided, the contentText argument will be ignored.
contentHTMLstringRequired. If contentText is set, this is not required. HTML for cells in table rows. If contentHTML is provided, the contentText argument will be ignored
classesstringAdditional classes to add to the table row cell.
colspanintegerSpecify how many columns a cell extends.
rowspanintegerSpecify how many rows a cell extends.

Options for head

NameTypeDescription
contentTextstringRequired. Text for cells in table head. If contentHTML is provided, the contentText argument will be used for mobile headers.
contentHTMLstringRequired. If contentText is set, this is not required. HTML for cells in table rows. If contentHTML is provided, the contentText argument will be ignored
classesstringAdditional classes to add to the table row cell.
colspanintegerSpecify how many columns a cell extends.
rowspanintegerSpecify how many rows a cell extends.

Recommended javascript (browser compatible)
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports["default"] = void 0;
var tableJS = function tableJS() {
  var tables = document.querySelectorAll('.wmnds-table');
  var getTableHeadChildren = function getTableHeadChildren(table) {
    var tHeadChildren = table.querySelectorAll('thead tr > *'); // Using querySelectorAll as it has a fallback of an empty node list. Using children returns HTMLCollection which we can't forEach through.

    return tHeadChildren; // Return node list
  };

  var iterateTables = function iterateTables(table) {
    var theadChildren = getTableHeadChildren(table); // Get all children from thead section

    if (!theadChildren.length) return table.classList.add('wmnds-table--without-header'); // If no headers found, add without-header class

    // Otherwise...

    // If our node list is greater than 1, then there are headers
    var tRows = table.querySelectorAll('tbody > tr'); // Get all table rows in our table body section

    return tRows.forEach(function(tRow) {
      var cells = tRow.querySelectorAll('td'); // Get all cells within our row

      // Loop through each cell within our row...
      cells.forEach(function(cell, i) {
        var newCell = cell; // Map to new var to avoid mutating original
        var tHeadText = theadChildren[i].innerText; // Map the index of the cell to the index of the header we want as it will be in the same column of the table
        newCell.dataset.header = tHeadText; // Change the data-header on our cell to the header text so it appears on mobile
      });
    });
  };

  // START HERE
  if (tables.length) tables.forEach(iterateTables);
};
var _default = tableJS();
exports["default"] = _default;

Recommended javascript (ES6)
const tableJS = () => {
  const tables = document.querySelectorAll('.wmnds-table');

  const getTableHeadChildren = table => {
    const tHeadChildren = table.querySelectorAll('thead tr > *'); // Using querySelectorAll as it has a fallback of an empty node list. Using children returns HTMLCollection which we can't forEach through.

    return tHeadChildren; // Return node list
  };

  const iterateTables = table => {
    const theadChildren = getTableHeadChildren(table); // Get all children from thead section

    if (!theadChildren.length) return table.classList.add('wmnds-table--without-header'); // If no headers found, add without-header class

    // Otherwise...

    // If our node list is greater than 1, then there are headers
    const tRows = table.querySelectorAll('tbody > tr'); // Get all table rows in our table body section

    return tRows.forEach(tRow => {
      const cells = tRow.querySelectorAll('td'); // Get all cells within our row

      // Loop through each cell within our row...
      cells.forEach((cell, i) => {
        const newCell = cell; // Map to new var to avoid mutating original
        const tHeadText = theadChildren[i].innerText; // Map the index of the cell to the index of the header we want as it will be in the same column of the table
        newCell.dataset.header = tHeadText; // Change the data-header on our cell to the header text so it appears on mobile
      });
    });
  };

  // START HERE
  if (tables.length) tables.forEach(iterateTables);
};

export default tableJS();

Table without Headers


Table Title
Cell 1Cell 2
Cell 1Cell 2

HTML markup
<table class="wmnds-table wmnds-table--without-header">
  <caption class="wmnds-table__caption">
    Table Title
  </caption>
  <tbody>
    <tr>
      <th scope="row" data-header="Header 1">
        Cell 1
      </th>
      <td data-header="Header 2">
        Cell 2
      </td>
    </tr>
    <tr>
      <th scope="row" data-header="Header 1">
        Cell 1
      </th>
      <td data-header="Header 2">
        Cell 2
      </td>
    </tr>
  </tbody>
</table>

Nunjucks markup
{% from "wmnds/components/table/_table.njk" import wmndsTable %}

{{
  wmndsTable(
    {
      caption: "Table Caption",
      title: "Table Title",
      firstCellIsHeader: true,
      classes: "wmnds-m-b-xl",
      rows: [
        [
          {
            contentText: "Cell 1",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          },
          {
            contentText: "Cell 2",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          },
          {
            contentText: "Cell 3",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          }
        ],
        [
          {
            contentText: "Cell 1",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          },
          {
            contentText: "Cell 2",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          },
          {
            contentText: "Cell 3",
            contentHTML: null,
            classes: null,
            colspan: 1,
            rowspan: 1,
          }
        ]
      ],
      head: [
        {
          contentText: "Header 1",
          contentHTML: null,
          classes: null,
          colspan: 1,
          rowspan: 1,
        },
        {
          contentText: "Header 2",
          contentHTML: null,
          classes: null,
          colspan: 1,
          rowspan: 1,
        },
        {
          contentText: "Header 3",
          contentHTML: null,
          classes: null,
          colspan: 1,
          rowspan: 1,
        }
      ],
      hideHeader: false
    })
}}

Nunjucks properties
NameTypeDescription
captionstringCaption text
titlestringTable title (displayed as h3)
firstCellIsHeaderbooleanIf set to false, first cell in table row will be a td instead of a th. Default value is true
classesstringAdditional classes to add to the table container.
rowsarrayRequired. Array of table rows and cells. See options for rows below.
headarrayArray of table head cells. See options for head below. Will not show if hideHeader is true.
hideHeaderbooleanDetermines whether to display a table header row (thead) at the top of the table. Set to false by default.

Options for rows

NameTypeDescription
contentTextstringRequired. If html is set, this is not required. Text for cells in table rows. If html is provided, the contentText argument will be ignored.
contentHTMLstringRequired. If contentText is set, this is not required. HTML for cells in table rows. If contentHTML is provided, the contentText argument will be ignored
classesstringAdditional classes to add to the table row cell.
colspanintegerSpecify how many columns a cell extends.
rowspanintegerSpecify how many rows a cell extends.

Options for head

NameTypeDescription
contentTextstringRequired. Text for cells in table head. If contentHTML is provided, the contentText argument will be used for mobile headers.
contentHTMLstringRequired. If contentText is set, this is not required. HTML for cells in table rows. If contentHTML is provided, the contentText argument will be ignored
classesstringAdditional classes to add to the table row cell.
colspanintegerSpecify how many columns a cell extends.
rowspanintegerSpecify how many rows a cell extends.

Recommended javascript (browser compatible)
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports["default"] = void 0;
var tableJS = function tableJS() {
  var tables = document.querySelectorAll('.wmnds-table');
  var getTableHeadChildren = function getTableHeadChildren(table) {
    var tHeadChildren = table.querySelectorAll('thead tr > *'); // Using querySelectorAll as it has a fallback of an empty node list. Using children returns HTMLCollection which we can't forEach through.

    return tHeadChildren; // Return node list
  };

  var iterateTables = function iterateTables(table) {
    var theadChildren = getTableHeadChildren(table); // Get all children from thead section

    if (!theadChildren.length) return table.classList.add('wmnds-table--without-header'); // If no headers found, add without-header class

    // Otherwise...

    // If our node list is greater than 1, then there are headers
    var tRows = table.querySelectorAll('tbody > tr'); // Get all table rows in our table body section

    return tRows.forEach(function(tRow) {
      var cells = tRow.querySelectorAll('td'); // Get all cells within our row

      // Loop through each cell within our row...
      cells.forEach(function(cell, i) {
        var newCell = cell; // Map to new var to avoid mutating original
        var tHeadText = theadChildren[i].innerText; // Map the index of the cell to the index of the header we want as it will be in the same column of the table
        newCell.dataset.header = tHeadText; // Change the data-header on our cell to the header text so it appears on mobile
      });
    });
  };

  // START HERE
  if (tables.length) tables.forEach(iterateTables);
};
var _default = tableJS();
exports["default"] = _default;

Recommended javascript (ES6)
const tableJS = () => {
  const tables = document.querySelectorAll('.wmnds-table');

  const getTableHeadChildren = table => {
    const tHeadChildren = table.querySelectorAll('thead tr > *'); // Using querySelectorAll as it has a fallback of an empty node list. Using children returns HTMLCollection which we can't forEach through.

    return tHeadChildren; // Return node list
  };

  const iterateTables = table => {
    const theadChildren = getTableHeadChildren(table); // Get all children from thead section

    if (!theadChildren.length) return table.classList.add('wmnds-table--without-header'); // If no headers found, add without-header class

    // Otherwise...

    // If our node list is greater than 1, then there are headers
    const tRows = table.querySelectorAll('tbody > tr'); // Get all table rows in our table body section

    return tRows.forEach(tRow => {
      const cells = tRow.querySelectorAll('td'); // Get all cells within our row

      // Loop through each cell within our row...
      cells.forEach((cell, i) => {
        const newCell = cell; // Map to new var to avoid mutating original
        const tHeadText = theadChildren[i].innerText; // Map the index of the cell to the index of the header we want as it will be in the same column of the table
        newCell.dataset.header = tHeadText; // Change the data-header on our cell to the header text so it appears on mobile
      });
    });
  };

  // START HERE
  if (tables.length) tables.forEach(iterateTables);
};

export default tableJS();