City of Ghent Style Guide

Table

When to use this component

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

A table is a great way to present things such as:

  • Financial data and numbers
  • Pricing
  • Features (for comparison)
  • Calendars
  • And many other tabular data

When to not use this component

Do not use the table component for layout.

Furthermore, do not use a table component:

  • To present simple, non-tabular data.
  • To present information that users typically do not have to compare.

For presenting simple, non-tabular data, consider using a list instead. For presenting teasers of content, use the collection of teasers instead.

How it works

A table organizes information in rows and columns:

  • The columns can have column headings to tell users what the columns represent.
  • The rows can have row headings to tell users what the rows represent.
  • The table component adds the possibility to add an optional caption tag that describes the table.

The table styling features alternating row colors.

On desktop resolutions, when it is desired, individual rows, columns or even individual cells can be highlighted by overriding the background color. The colors that can be used are success, warning and error. To override the background color with one of these colors, apply the desired CSS class to a row or cell.

On tablets in portrait mode and mobile resolutions, the table component is rendered as an HTML description list. Highlighting cannot be used here.

{# Table (desktop) #}
<div class="responsive-table">
  <div class="table-wrapper">
    <table>
      {% for row in table.rows %}
        <tr {{ row.attributes }}>
          {% for cell in row.cells %}
            {% if cell.header %}
              <th {{ cell.attributes }}>{{ cell.text }}</th>
            {% else %}
              <td {{ cell.attributes }}>{{ cell.text }}</td>
            {% endif %}
          {% endfor %}
        </tr>
      {% endfor %}
      <caption>
        {{ caption }}
      </caption>
    </table>
  </div>
</div>

{# List (mobile) #}
<div aria-labelledby="{{ _self.name }}-list-description" class="table-list">
  <ul>
    {% for row in table.rows|slice(table.columnHeaders ? 1 : 0) %}
    <li {{ row.attributes }}>

      {# Row title #}
      {% if table.rowHeaders %}
        {% set rowHeader = row.cells|first %}
        <h3>{{ rowHeader.text }}</h3>
      {% endif %}

      {# Cells #}
      <dl>
        {% for cell in row.cells|slice(table.rowHeaders ? 1 : 0) %}
          {% if table.columnHeaders %}
            {% set headingIndex = loop.index - (table.rowHeaders ? 0 : 1) %}
            <dt>{{ table.rows|first.cells[headingIndex].text }}</dt>
          {% endif %}

          <dd {{ cell.attributes }}>{{ cell.text }}</dd>
        {% endfor %}
      </dl>
    </li>
    {% endfor %}
  </ul>
  <div class="list-description" id="{{ _self.name }}-list-description">
    {{ caption }}
  </div>
</div>
<div class="responsive-table">
    <div class="table-wrapper">
        <table>
            <tr>
                <th scope="row">Header row 1</th>
                <td>Row 1, column 2</td>
                <td>Row 1, column 3</td>
                <td>Row 1, column 4</td>
                <td>Row 1, column 5</td>
                <td>Row 1, column 6</td>
            </tr>
            <tr>
                <th scope="row">Header row 2</th>
                <td class="success">Row 2, column 2</td>
                <td>Row 2, column 3</td>
                <td>Row 2, column 4</td>
                <td>Row 2, column 5</td>
                <td>Row 2, column 6</td>
            </tr>
            <tr>
                <th scope="row">Header row 3</th>
                <td>Row 3, column 2</td>
                <td>Row 3, column 3</td>
                <td>Row 3, column 4</td>
                <td>Row 3, column 5</td>
                <td>Row 3, column 6</td>
            </tr>
            <tr>
                <th scope="row">Header row 4</th>
                <td>Row 4, column 2</td>
                <td>Row 4, column 3</td>
                <td>Row 4, column 4</td>
                <td class="error">Row 4, column 5</td>
                <td>Row 4, column 6</td>
            </tr>
            <tr>
                <th scope="row">Header row 5</th>
                <td>Row 5, column 2</td>
                <td>Row 5, column 3</td>
                <td>Row 5, column 4</td>
                <td>Row 5, column 5</td>
                <td>Row 5, column 6</td>
            </tr>
            <tr>
                <th scope="row">Header row 6</th>
                <td>Row 6, column 2</td>
                <td>Row 6, column 3</td>
                <td class="warning">Row 6, column 4</td>
                <td>Row 6, column 5</td>
                <td>Row 6, column 6</td>
            </tr>
            <tr>
                <th scope="row">Header row 7</th>
                <td>Row 7, column 2</td>
                <td>Row 7, column 3</td>
                <td>Row 7, column 4</td>
                <td>Row 7, column 5</td>
                <td>Row 7, column 6</td>
            </tr>
            <tr>
                <th scope="row">Header row 8</th>
                <td>Row 8, column 2</td>
                <td>Row 8, column 3</td>
                <td>Row 8, column 4</td>
                <td>Row 8, column 5</td>
                <td>Row 8, column 6</td>
            </tr>
            <tr class="success">
                <th scope="row">Header row 9</th>
                <td>Row 9, column 2</td>
                <td>Row 9, column 3</td>
                <td>Row 9, column 4</td>
                <td>Row 9, column 5</td>
                <td>Row 9, column 6</td>
            </tr>
            <tr>
                <th scope="row">Header row 10</th>
                <td>Row 10, column 2</td>
                <td>Row 10, column 3</td>
                <td>Row 10, column 4</td>
                <td>Row 10, column 5</td>
                <td>Row 10, column 6</td>
            </tr>
            <caption>
                This is a table caption that describes the table content.
            </caption>
        </table>
    </div>
</div>

<div aria-labelledby="table-without-column-headers-list-description" class="table-list">
    <ul>
        <li>

            <h3>Header row 1</h3>

            <dl>

                <dd>Row 1, column 2</dd>

                <dd>Row 1, column 3</dd>

                <dd>Row 1, column 4</dd>

                <dd>Row 1, column 5</dd>

                <dd>Row 1, column 6</dd>
            </dl>
        </li>
        <li>

            <h3>Header row 2</h3>

            <dl>

                <dd class="success">Row 2, column 2</dd>

                <dd>Row 2, column 3</dd>

                <dd>Row 2, column 4</dd>

                <dd>Row 2, column 5</dd>

                <dd>Row 2, column 6</dd>
            </dl>
        </li>
        <li>

            <h3>Header row 3</h3>

            <dl>

                <dd>Row 3, column 2</dd>

                <dd>Row 3, column 3</dd>

                <dd>Row 3, column 4</dd>

                <dd>Row 3, column 5</dd>

                <dd>Row 3, column 6</dd>
            </dl>
        </li>
        <li>

            <h3>Header row 4</h3>

            <dl>

                <dd>Row 4, column 2</dd>

                <dd>Row 4, column 3</dd>

                <dd>Row 4, column 4</dd>

                <dd class="error">Row 4, column 5</dd>

                <dd>Row 4, column 6</dd>
            </dl>
        </li>
        <li>

            <h3>Header row 5</h3>

            <dl>

                <dd>Row 5, column 2</dd>

                <dd>Row 5, column 3</dd>

                <dd>Row 5, column 4</dd>

                <dd>Row 5, column 5</dd>

                <dd>Row 5, column 6</dd>
            </dl>
        </li>
        <li>

            <h3>Header row 6</h3>

            <dl>

                <dd>Row 6, column 2</dd>

                <dd>Row 6, column 3</dd>

                <dd class="warning">Row 6, column 4</dd>

                <dd>Row 6, column 5</dd>

                <dd>Row 6, column 6</dd>
            </dl>
        </li>
        <li>

            <h3>Header row 7</h3>

            <dl>

                <dd>Row 7, column 2</dd>

                <dd>Row 7, column 3</dd>

                <dd>Row 7, column 4</dd>

                <dd>Row 7, column 5</dd>

                <dd>Row 7, column 6</dd>
            </dl>
        </li>
        <li>

            <h3>Header row 8</h3>

            <dl>

                <dd>Row 8, column 2</dd>

                <dd>Row 8, column 3</dd>

                <dd>Row 8, column 4</dd>

                <dd>Row 8, column 5</dd>

                <dd>Row 8, column 6</dd>
            </dl>
        </li>
        <li class="success">

            <h3>Header row 9</h3>

            <dl>

                <dd>Row 9, column 2</dd>

                <dd>Row 9, column 3</dd>

                <dd>Row 9, column 4</dd>

                <dd>Row 9, column 5</dd>

                <dd>Row 9, column 6</dd>
            </dl>
        </li>
        <li>

            <h3>Header row 10</h3>

            <dl>

                <dd>Row 10, column 2</dd>

                <dd>Row 10, column 3</dd>

                <dd>Row 10, column 4</dd>

                <dd>Row 10, column 5</dd>

                <dd>Row 10, column 6</dd>
            </dl>
        </li>
    </ul>
    <div class="list-description" id="table-without-column-headers-list-description">
        This is a table caption that describes the table content.
    </div>
</div>
{
  "table": {
    "rows": [
      {
        "attributes": null,
        "cells": [
          {
            "header": true,
            "attributes": "scope=\"row\"",
            "text": "Header row 1"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 1, column 2"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 1, column 3"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 1, column 4"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 1, column 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 1, column 6"
          }
        ]
      },
      {
        "attributes": null,
        "cells": [
          {
            "header": true,
            "attributes": "scope=\"row\"",
            "text": "Header row 2"
          },
          {
            "header": false,
            "attributes": "class=\"success\"",
            "text": "Row 2, column 2"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 2, column 3"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 2, column 4"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 2, column 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 2, column 6"
          }
        ]
      },
      {
        "attributes": null,
        "cells": [
          {
            "header": true,
            "attributes": "scope=\"row\"",
            "text": "Header row 3"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 3, column 2"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 3, column 3"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 3, column 4"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 3, column 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 3, column 6"
          }
        ]
      },
      {
        "attributes": null,
        "cells": [
          {
            "header": true,
            "attributes": "scope=\"row\"",
            "text": "Header row 4"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 4, column 2"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 4, column 3"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 4, column 4"
          },
          {
            "header": false,
            "attributes": "class=\"error\"",
            "text": "Row 4, column 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 4, column 6"
          }
        ]
      },
      {
        "attributes": null,
        "cells": [
          {
            "header": true,
            "attributes": "scope=\"row\"",
            "text": "Header row 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 5, column 2"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 5, column 3"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 5, column 4"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 5, column 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 5, column 6"
          }
        ]
      },
      {
        "attributes": null,
        "cells": [
          {
            "header": true,
            "attributes": "scope=\"row\"",
            "text": "Header row 6"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 6, column 2"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 6, column 3"
          },
          {
            "header": false,
            "attributes": "class=\"warning\"",
            "text": "Row 6, column 4"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 6, column 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 6, column 6"
          }
        ]
      },
      {
        "attributes": null,
        "cells": [
          {
            "header": true,
            "attributes": "scope=\"row\"",
            "text": "Header row 7"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 7, column 2"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 7, column 3"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 7, column 4"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 7, column 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 7, column 6"
          }
        ]
      },
      {
        "attributes": null,
        "cells": [
          {
            "header": true,
            "attributes": "scope=\"row\"",
            "text": "Header row 8"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 8, column 2"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 8, column 3"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 8, column 4"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 8, column 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 8, column 6"
          }
        ]
      },
      {
        "attributes": "class=\"success\"",
        "cells": [
          {
            "header": true,
            "attributes": "scope=\"row\"",
            "text": "Header row 9"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 9, column 2"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 9, column 3"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 9, column 4"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 9, column 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 9, column 6"
          }
        ]
      },
      {
        "attributes": null,
        "cells": [
          {
            "header": true,
            "attributes": "scope=\"row\"",
            "text": "Header row 10"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 10, column 2"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 10, column 3"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 10, column 4"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 10, column 5"
          },
          {
            "header": false,
            "attributes": null,
            "text": "Row 10, column 6"
          }
        ]
      }
    ],
    "columnHeaders": false,
    "rowHeaders": true
  },
  "caption": "This is a table caption that describes the table content."
}
  • Content:
    div.responsive-table,
    div.table-list {
      position: relative;
      padding-bottom: 1.4rem;
    
      .list-description,
      caption {
        @include theme('color', 'color-tertiary--lighten-1', 'table-caption-color');
    
        position: absolute;
        bottom: 0;
        width: 100%;
        margin: .4rem 0;
        font-size: .6rem;
        text-align: right;
      }
    }
    
    div.responsive-table {
      @include focus-style;
    
      @include tablet {
        display: inline-block;
        max-width: 100%;
      }
    
      display: none;
    
      .table-wrapper {
        overflow-x: auto;
      }
    
    
      table {
        caption-side: bottom;
    
        th {
          @include bold-text;
    
          font-size: .9rem;
          line-height: 1.7;
    
          &[scope="col"] {
            @include theme('border-color', 'color-primary--lighten-5', 'table-header-border-color');
    
            border-bottom: 2px solid;
          }
        }
    
        td {
          font-size: .8rem;
    
          &:nth-of-type(n) {
            @include table-backgrounds;
          }
        }
    
        tr {
          &:nth-of-type(even) th:not([scope="col"]),
          &:nth-of-type(even) td {
            @include theme('background-color', 'color-primary--lighten-5', 'table-row-color');
          }
    
          &:nth-of-type(n) {
            @include table-backgrounds(th);
            @include table-backgrounds(td);
          }
        }
    
        // Tables with a thead have should have the same color scheme.
        thead ~ tbody tr {
          [class*="cs--"] &:nth-of-type(even) th:not([scope="col"]),
          [class*="cs--"] &:nth-of-type(even) td {
            background-color: transparent;
          }
    
          &:nth-of-type(odd) th:not([scope="col"]),
          &:nth-of-type(odd) td {
            @include theme('background-color', 'color-primary--lighten-5', 'table-row-color');
          }
        }
    
        th,
        td {
          @include theme('color', 'color-tertiary', 'table-content-color');
          padding: .3rem .2rem;
          text-align: left;
        }
      }
    }
    
    div.table-list {
      @include tablet {
        display: none;
      }
    
      display: block;
    
      &.js-hidden {
        display: none;
      }
    
      ul {
        @extend %list-no-style;
    
        li {
          padding: .6rem .3rem;
          font-size: .9rem;
    
          &:nth-child(even) {
            @include theme('background-color', 'color-primary--lighten-5', 'table-list-row-color');
          }
        }
      }
    
      dl {
        display: flex;
        flex-wrap: wrap;
        margin: 0;
    
        > dt {
          @include phablet {
            flex: 0 30%;
          }
    
          flex: 0 50%;
          line-height: 2.1;
    
          + dd {
            flex: 0 50%;
    
            @include phablet {
              flex: 0 70%;
            }
          }
        }
    
        > dd {
          flex: 0 100%;
          margin: 0;
          padding: 0;
          font-size: .8rem;
          line-height: 2.4;
        }
      }
    }
    
  • URL: /components/raw/table/_table.scss
  • Filesystem Path: components/21-atoms/table/_table.scss
  • Size: 2.6 KB
  • Content:
    /* global ResponsiveTable */
    
    'use strict';
    
    (function () {
      var tablesNodeList = document.querySelectorAll('.responsive-table .table-wrapper');
    
      // Optimise all tables with a wrapper div.responsive-table
      for (var i = 0; i < tablesNodeList.length; i++) {
        var table = tablesNodeList[i];
    
        // Adds accessibility support.
        new ResponsiveTable(table, {
          scrollableText: '(scroll to see more)'
        });
      }
    
    })();
    
  • URL: /components/raw/table/table.bindings.js
  • Filesystem Path: components/21-atoms/table/table.bindings.js
  • Size: 428 Bytes
  • Content:
    'use strict';
    
    (function (root, factory) {
      if (typeof define === 'function' && define.amd) {
        define(factory);
      }
      else {
        if (typeof exports === 'object') {
          module.exports = factory();
        }
        else {
          root.ResponsiveTable = factory();
        }
      }
    }(this || window, function () {
      return function (element, options) {
    
        /**
         * The table caption.
         */
        let caption = null;
    
        /**
         * Determine if the table should be focusable.
         */
        const determineFocusable = () => {
          var scrollableWidth = element.parentNode.querySelector('table').scrollWidth;
          var containerWidth = element.parentNode.clientWidth;
    
          // Check if element is scrollable.
          if (scrollableWidth <= containerWidth) {
            // If not remove the tab focus.
            element.removeAttribute('tabindex');
          }
          else if (caption) {
            caption.innerText += ' ' + options.scrollableText;
          }
        };
    
        /**
         * Setup the responsive table.
         */
        const setupResponsiveTable = () => {
          // Set caption id.
          if (caption && !caption.hasAttribute('id')) {
            const tableUid = Math.random().toString(36).substr(2, 16);
            caption.setAttribute('id', 'responsive-table-caption-caption-' + tableUid);
            element.setAttribute('aria-labelledby', caption.getAttribute('id'));
          }
    
          // Set table container attributes.
          element.setAttribute('tabindex', '0');
          element.setAttribute('role', 'group');
    
          // Set th scope attributes.
          let firstRow = element.querySelector('tr');
          let tableHeadingsNodeList = firstRow.querySelectorAll('th');
    
          if (firstRow.getElementsByTagName('th').length === firstRow.querySelectorAll('*').length) {
            for (let i = 0; i < tableHeadingsNodeList.length; i++) {
              // If no scope attribute, set it.
              if (!tableHeadingsNodeList[i].hasAttribute('scope')) {
                tableHeadingsNodeList[i].setAttribute('scope', 'col');
              }
            }
          }
    
          // Set tr scope attributes if first child is a th.
          let tableRowsNodeList = element.querySelectorAll('tr');
          for (let i = 0; i < tableRowsNodeList.length; i++) {
            let firstChild = tableRowsNodeList[i].firstElementChild;
    
            // If no scope attribute, set it.
            if (!firstChild.hasAttribute('scope')) {
              firstChild.setAttribute('scope', 'row');
            }
          }
    
          determineFocusable();
        };
    
        /**
         * Entry point of the script.
         *
         */
        const init = () => {
          if (!element) {
            return;
          }
    
          caption = element.querySelector('caption');
    
          setupResponsiveTable();
        };
    
        init();
    
        return {};
      };
    }));
    
  • URL: /components/raw/table/table.functions.js
  • Filesystem Path: components/21-atoms/table/table.functions.js
  • Size: 2.7 KB