Skip to main content

Testing

Help us to improve the ONS Design System. Take part in a short study

Table

We use the table element to present tabular data in two dimensions.

Basic table

<table id="basic-table" class="table ">
  <caption class="table__caption">A basic table with a caption</caption>
  <thead class="table__head">
    <tr class="table__row">
      <th scope="col" class="table__header ">
        <span>Column A</span>
      </th>
      <th scope="col" class="table__header ">
        <span>Column B</span>
      </th>
      <th scope="col" class="table__header ">
        <span>Column C</span>
      </th>
    </tr>
  </thead>
  <tbody class="table__body">
    <tr class="table__row">
      <td class="table__cell " name="cell-name">
        Cell A1
      </td>
      <td class="table__cell ">
        Cell B1
      </td>
      <td class="table__cell ">
        Cell C1
      </td>
    </tr>
    <tr class="table__row">
      <td class="table__cell ">
        Cell A2
      </td>
      <td class="table__cell ">
        Cell B2
      </td>
      <td class="table__cell ">
        Cell C2
      </td>
    </tr>
  </tbody>
</table>
Nunjucks macro options
Name Type Required Description
table_class string false Classes to add to the table component
id string false ID to add to the table component
caption string false The caption for the table component
hideCaption boolean false Visually hides the caption
scrollable boolean false Sets the component to render as a scrollable table
ariaLabel string false The aria label added to the table if it is scrollable. Defaults to Scrollable table
sortable boolean false Sets the component to render as a sortable table
ths Array<th> true An array of th elements for table
trs Array<tr> true An array of tr elements for table
tfoot Array<tfootCell> false An array of td elements for tdfoot
ariaAsc string false Sets the data-aria-asc attribute for the table. Used to set aria labels when table is sorted
ariaDesc string false Sets the data-aria-desc attribute for the table. Used to set aria labels when table is sorted

th

Name Type Required Description
class string false Classes to add to the th element
ariaSort string false Default is “none”. Accepts “ascending” or “descending”
value string true The content for the th cell

tr

Name Type Required Description
tds Array<td> true An array of td elements for each tr
trHighlight boolean false Adds a class to the table row to highlight the row

td

Name Type Required Description
class string false Classes to add to the td element
name string false Name to add to the td element
data string false The corresponding th for the td for responsive tables
dataSort integer false numerical ordering of a column of td elements for sortable table
value string false The content for the td cell
form object false Form attributes information for method, action and the button

form

Name Type Required Description
method string false Default is post if no value is provided
action string true The action for the form
button Button (ref) false Configuration object for the form button
hiddenFormField object false Configuration object for hidden form fields

hiddenFormField

Name Type Required Description
name string false Hidden field name
value string false Hidden field value

tfootCell

Name Type Required Description
value string true The content for the td cell of tdfoot
{% from "components/table/_macro.njk" import onsTable %}
{{
    onsTable({
        "caption": "A basic table with a caption",
        "id": "basic-table",
        "ths": [
            {
                "value": "Column A"
            },
            {
                "value": "Column B"
            },
            {
                "value": "Column C"
            }
        ],
        "trs": [
            {
                "tds": [
                    {
                        "value": "Cell A1",
                        "name": "cell-name"
                    },
                    {
                        "value": "Cell B1"
                    },
                    {
                        "value": "Cell C1"
                    }
                ]
            },
            {
                "tds": [
                    {
                        "value": "Cell A2"
                    },
                    {
                        "value": "Cell B2"
                    },
                    {
                        "value": "Cell C2"
                    }
                ]
            }
        ]
    })
}}

{% macro onsTable(params) %}
    {% from "components/button/_macro.njk" import onsButton %}
    {% from "components/icons/_macro.njk" import onsIcon %}
    {% if params.scrollable is defined and params.scrollable %}
    <div class="table-scrollable table-scrollable--on">
        <div class="table-scrollable__content" tabindex="0" role="region" aria-label="{{ params.caption }}. {{ params.ariaLabel | default("Scrollable table") }} ">
    {% endif %}
            <table {% if params.id is defined and params.id %}id="{{ params.id }}"{% endif %} class="table {{ params.table_class }}" {% if params.sortable is defined and params.sortable %}data-aria-sort="{{ params.sortBy }}" data-aria-asc="{{ params.ariaAsc }}" data-aria-desc="{{ params.ariaDesc }}"{% endif %}>
                {% if params.caption is defined and params.caption %}
                <caption class="table__caption{{ " u-vh" if params.hideCaption }}">{{ params.caption }}</caption>
                {% endif %}
                <thead class="table__head">
                    <tr class="table__row">
                        {% for th in params.ths %}
                        <th scope="col" class="table__header {{ th.class }}"{% if th.ariaSort is defined and th.ariaSort %} aria-sort="{{- th.ariaSort | default('none') -}}"{% endif %}>
                            <span {% if params.sortable is defined and params.sortable %}class="u-vh"{% endif %}>{{- th.value -}}</span>
                            {% if params.sortable is defined and params.sortable %}
                                {{
                                    onsIcon({
                                        "icon": "sort-sprite",
                                        "id": th.value
                                    })
                                }}
                            {% endif %}
                        </th>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody class="table__body">
                    {% for tr in params.trs %}
                    <tr class="table__row{{ " table__row--highlight" if tr.highlight }}" {% if tr.name is defined and tr.name %} name="{{ tr.name }}"{% endif %} {% if tr.id is defined and tr.id %} id="{{ tr.id }}"{% endif %}>
                        {% for td in tr.tds %}
                        <td class="table__cell {{ td.class }}" {% if td.id is defined and td.id %} id="{{ td.id }}"{% endif %} {% if td.name is defined and td.name %} name="{{ td.name }}"{% endif %} {% if td.data is defined and td.data %} data-th="{{ td.data }}"{% endif %} {% if td.dataSort is defined and td.dataSort %} data-sort-value="{{ td.dataSort }}"{% endif %}>
                            {% if td.form is defined and td.form %}
                                <form action="{{ td.form.action }}" method="{{ td.form.method | default('POST')}}">
                                    {{
                                        onsButton({
                                            "text": td.form.button.text,
                                            "id": td.form.button.id if td.form.button.id,
                                            "classes": td.form.button.classes if td.form.button.classes,
                                            "url": td.form.button.url if td.form.button.url,
                                            "value": td.form.button.value | safe if td.form.button.value,
                                            "name": td.form.button.name if td.form.button.name
                                        })
                                    }}
                                    {% if td.form.hiddenFormField is defined and td.form.hiddenFormField %}
                                        {% for hiddenField in td.form.hiddenFormField %}
                                            <input type="hidden" {% if hiddenField.name is defined and hiddenField.name %} name="{{ hiddenField.name }}"{% endif %} {% if hiddenField.value is defined and hiddenField.value %} value="{{ hiddenField.value }}"{% endif %} />
                                        {% endfor %}
                                    {% endif %}
                                </form>
                            {% endif %}
                            {% if td.value is defined and td.value %}
                                {{ td.value | safe }}
                            {% endif %}
                        </td>
                        {% endfor %}
                    </tr>
                    {% endfor %}
                </tbody>
                {% if params.tfoot is defined and params.tfoot %}
                <tfoot class="table__foot">
                    <tr class="table__row">
                        {% for tfootCell in params.tfoot %}
                        <td class="table__cell u-fs-s">{{ tfootCell.value }}</td>
                        {% endfor %}
                    </tr>
                </tfoot>
                {% endif %}
            </table>
        {% if params.scrollable is defined and params.scrollable %}
        </div>
    </div>
    {% endif %}
{% endmacro %}

.table {
  border-collapse: collapse;
  border-spacing: 0;
  margin-bottom: 1rem;
  width: 100%;
  &__caption {
    font-weight: 700;
    text-align: left;
  }
  &__header,
  &__cell {
    @include nth-element(1, 0);
    border-bottom: 2px solid $color-grey-100;
    overflow: hidden;
    padding: 0.5rem 0 0.5rem 1rem;
    text-align: left;
    vertical-align: top;
    &--numeric {
      text-align: right;
    }
  }
  &__cell,
  &__header--row {
    border-bottom: 1px solid $color-borders;
  }
  &__row--highlight {
    background: $color-highlight;
  }
  &:not(.table--responsive) .table__body .table__row:last-child {
    .table__cell,
    .table__header--row {
      border: 0;
    }
  }
  &__foot .table__cell {
    border-bottom: 0;
    border-top: 1px solid $color-borders;
  }
  &--dense {
    .table__head,
    .table__body,
    .table__foot {
      font-size: 81.25%;
    }
  }
  &--row-hover {
    .table__body .table__row:hover {
      background: $color-grey-5;
    }
  }
  &--responsive {
    @include mq(xxs, s) {
      .table__header {
        display: none;
      }
      .table__body .table__row {
        border-bottom: 2px solid $color-grey-100;
        display: block;
        margin-bottom: 1rem;
      }
      .table__cell {
        display: block;
        padding-left: 0;
        text-align: right;
        &:last-child {
          border: 0;
        }
        &::before {
          content: attr(data-th);
          float: left;
          font-weight: 700;
          padding-right: 1rem;
        }
      }
    }
  }
}
.table-scrollable {
  position: relative;
  ::-webkit-scrollbar {
    height: 7px;
  }
  ::-webkit-scrollbar-thumb {
    background: $color-grey-75;
    border-radius: 20px;
  }
  &--on {
    .table__header,
    .table__cell {
      white-space: nowrap;
    }
  }
  &__content {
    overflow: visible;
    overflow-x: scroll;
    width: 100%;
    &:focus {
      outline: 3px solid $color-focus;
      outline-offset: 3px;
    }
    .table__header,
    .table__cell {
      @include mq(xxs, m) {
        white-space: nowrap;
      }
    }
    .table__right-shadow,
    .table__left-shadow {
      height: 100%;
      position: absolute;
      top: 0;
      width: 5px;
      z-index: 200;
      &.with-transition {
        transition: box-shadow 0.4s ease-out;
      }
    }
    .table__right-shadow {
      right: 0;
      &.visible {
        box-shadow: inset -1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
    .table__left-shadow {
      left: 0;
      &.visible {
        box-shadow: inset 1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
  }
}
.table--sortable {
  [aria-sort='descending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-grey-35;
      }
      .bottomTriangle {
        fill: $color-ocean-blue;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .topTriangle {
          fill: $color-black;
        }
      }
    }
  }
  [aria-sort='ascending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-ocean-blue;
      }
      .bottomTriangle {
        fill: $color-grey-35;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .bottomTriangle {
          fill: $color-black;
        }
      }
    }
  }
  .table__header {
    position: relative;
    .table__sort-button {
      background-color: transparent;
      border: 0;
      box-shadow: none;
      color: $color-text-link;
      display: inline-block;
      font-family: $font-sans;
      font-weight: 700;
      line-height: 1rem;
      text-decoration: underline;
      white-space: nowrap;
      &:hover {
        color: $color-text-link-hover;
        cursor: pointer;
        text-decoration: underline solid $color-text-link-hover 2px;
      }
      .svg-icon {
        fill: $color-grey-35;
        height: 0.8rem;
        padding-bottom: 0.1rem;
        width: 0.8rem;
      }
      &:focus {
        @extend a:focus;
        .svg-icon {
          fill: $color-black;
        }
      }
    }
  }
}

<table class="table ">
  <caption class="table__caption">A basic table with a footer</caption>
  <thead class="table__head">
    <tr class="table__row">
      <th scope="col" class="table__header ">
        <span>Column A</span>
      </th>
      <th scope="col" class="table__header ">
        <span>Column B</span>
      </th>
      <th scope="col" class="table__header ">
        <span>Column C</span>
      </th>
    </tr>
  </thead>
  <tbody class="table__body">
    <tr class="table__row">
      <td class="table__cell ">
        Cell A1
      </td>
      <td class="table__cell ">
        Cell B1
      </td>
      <td class="table__cell ">
        Cell C1
      </td>
    </tr>
    <tr class="table__row">
      <td class="table__cell ">
        Cell A2
      </td>
      <td class="table__cell ">
        Cell B2
      </td>
      <td class="table__cell ">
        Cell C2
      </td>
    </tr>
  </tbody>
  <tfoot class="table__foot">
    <tr class="table__row">
      <td class="table__cell u-fs-s">Column summary</td>
      <td class="table__cell u-fs-s">Column summary</td>
      <td class="table__cell u-fs-s">Column summary</td>
    </tr>
  </tfoot>
</table>
Nunjucks macro options
Name Type Required Description
table_class string false Classes to add to the table component
id string false ID to add to the table component
caption string false The caption for the table component
hideCaption boolean false Visually hides the caption
scrollable boolean false Sets the component to render as a scrollable table
ariaLabel string false The aria label added to the table if it is scrollable. Defaults to Scrollable table
sortable boolean false Sets the component to render as a sortable table
ths Array<th> true An array of th elements for table
trs Array<tr> true An array of tr elements for table
tfoot Array<tfootCell> false An array of td elements for tdfoot
ariaAsc string false Sets the data-aria-asc attribute for the table. Used to set aria labels when table is sorted
ariaDesc string false Sets the data-aria-desc attribute for the table. Used to set aria labels when table is sorted

th

Name Type Required Description
class string false Classes to add to the th element
ariaSort string false Default is “none”. Accepts “ascending” or “descending”
value string true The content for the th cell

tr

Name Type Required Description
tds Array<td> true An array of td elements for each tr
trHighlight boolean false Adds a class to the table row to highlight the row

td

Name Type Required Description
class string false Classes to add to the td element
name string false Name to add to the td element
data string false The corresponding th for the td for responsive tables
dataSort integer false numerical ordering of a column of td elements for sortable table
value string false The content for the td cell
form object false Form attributes information for method, action and the button

form

Name Type Required Description
method string false Default is post if no value is provided
action string true The action for the form
button Button (ref) false Configuration object for the form button
hiddenFormField object false Configuration object for hidden form fields

hiddenFormField

Name Type Required Description
name string false Hidden field name
value string false Hidden field value

tfootCell

Name Type Required Description
value string true The content for the td cell of tdfoot
{% from "components/table/_macro.njk" import onsTable %}
{{
    onsTable({
        "caption": "A basic table with a footer",
        "ths": [
            {
                "value": "Column A"
            },
            {
                "value": "Column B"
            },
            {
                "value": "Column C"
            }
        ],
        "trs": [
            {
                "tds": [
                    {
                        "value": "Cell A1"
                    },
                    {
                        "value": "Cell B1"
                    },
                    {
                        "value": "Cell C1"
                    }
                ]
            },
            {
                "tds": [
                    {
                    "value": "Cell A2"
                    },
                    {
                    "value": "Cell B2"
                    },
                    {
                    "value": "Cell C2"
                    }
                ]
            }
        ],
        "tfoot": [
            {
                "value": "Column summary"
            },
            {
                "value": "Column summary"
            },
            {
                "value": "Column summary"
            }
        ]
    })
}}

{% macro onsTable(params) %}
    {% from "components/button/_macro.njk" import onsButton %}
    {% from "components/icons/_macro.njk" import onsIcon %}
    {% if params.scrollable is defined and params.scrollable %}
    <div class="table-scrollable table-scrollable--on">
        <div class="table-scrollable__content" tabindex="0" role="region" aria-label="{{ params.caption }}. {{ params.ariaLabel | default("Scrollable table") }} ">
    {% endif %}
            <table {% if params.id is defined and params.id %}id="{{ params.id }}"{% endif %} class="table {{ params.table_class }}" {% if params.sortable is defined and params.sortable %}data-aria-sort="{{ params.sortBy }}" data-aria-asc="{{ params.ariaAsc }}" data-aria-desc="{{ params.ariaDesc }}"{% endif %}>
                {% if params.caption is defined and params.caption %}
                <caption class="table__caption{{ " u-vh" if params.hideCaption }}">{{ params.caption }}</caption>
                {% endif %}
                <thead class="table__head">
                    <tr class="table__row">
                        {% for th in params.ths %}
                        <th scope="col" class="table__header {{ th.class }}"{% if th.ariaSort is defined and th.ariaSort %} aria-sort="{{- th.ariaSort | default('none') -}}"{% endif %}>
                            <span {% if params.sortable is defined and params.sortable %}class="u-vh"{% endif %}>{{- th.value -}}</span>
                            {% if params.sortable is defined and params.sortable %}
                                {{
                                    onsIcon({
                                        "icon": "sort-sprite",
                                        "id": th.value
                                    })
                                }}
                            {% endif %}
                        </th>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody class="table__body">
                    {% for tr in params.trs %}
                    <tr class="table__row{{ " table__row--highlight" if tr.highlight }}" {% if tr.name is defined and tr.name %} name="{{ tr.name }}"{% endif %} {% if tr.id is defined and tr.id %} id="{{ tr.id }}"{% endif %}>
                        {% for td in tr.tds %}
                        <td class="table__cell {{ td.class }}" {% if td.id is defined and td.id %} id="{{ td.id }}"{% endif %} {% if td.name is defined and td.name %} name="{{ td.name }}"{% endif %} {% if td.data is defined and td.data %} data-th="{{ td.data }}"{% endif %} {% if td.dataSort is defined and td.dataSort %} data-sort-value="{{ td.dataSort }}"{% endif %}>
                            {% if td.form is defined and td.form %}
                                <form action="{{ td.form.action }}" method="{{ td.form.method | default('POST')}}">
                                    {{
                                        onsButton({
                                            "text": td.form.button.text,
                                            "id": td.form.button.id if td.form.button.id,
                                            "classes": td.form.button.classes if td.form.button.classes,
                                            "url": td.form.button.url if td.form.button.url,
                                            "value": td.form.button.value | safe if td.form.button.value,
                                            "name": td.form.button.name if td.form.button.name
                                        })
                                    }}
                                    {% if td.form.hiddenFormField is defined and td.form.hiddenFormField %}
                                        {% for hiddenField in td.form.hiddenFormField %}
                                            <input type="hidden" {% if hiddenField.name is defined and hiddenField.name %} name="{{ hiddenField.name }}"{% endif %} {% if hiddenField.value is defined and hiddenField.value %} value="{{ hiddenField.value }}"{% endif %} />
                                        {% endfor %}
                                    {% endif %}
                                </form>
                            {% endif %}
                            {% if td.value is defined and td.value %}
                                {{ td.value | safe }}
                            {% endif %}
                        </td>
                        {% endfor %}
                    </tr>
                    {% endfor %}
                </tbody>
                {% if params.tfoot is defined and params.tfoot %}
                <tfoot class="table__foot">
                    <tr class="table__row">
                        {% for tfootCell in params.tfoot %}
                        <td class="table__cell u-fs-s">{{ tfootCell.value }}</td>
                        {% endfor %}
                    </tr>
                </tfoot>
                {% endif %}
            </table>
        {% if params.scrollable is defined and params.scrollable %}
        </div>
    </div>
    {% endif %}
{% endmacro %}

.table {
  border-collapse: collapse;
  border-spacing: 0;
  margin-bottom: 1rem;
  width: 100%;
  &__caption {
    font-weight: 700;
    text-align: left;
  }
  &__header,
  &__cell {
    @include nth-element(1, 0);
    border-bottom: 2px solid $color-grey-100;
    overflow: hidden;
    padding: 0.5rem 0 0.5rem 1rem;
    text-align: left;
    vertical-align: top;
    &--numeric {
      text-align: right;
    }
  }
  &__cell,
  &__header--row {
    border-bottom: 1px solid $color-borders;
  }
  &__row--highlight {
    background: $color-highlight;
  }
  &:not(.table--responsive) .table__body .table__row:last-child {
    .table__cell,
    .table__header--row {
      border: 0;
    }
  }
  &__foot .table__cell {
    border-bottom: 0;
    border-top: 1px solid $color-borders;
  }
  &--dense {
    .table__head,
    .table__body,
    .table__foot {
      font-size: 81.25%;
    }
  }
  &--row-hover {
    .table__body .table__row:hover {
      background: $color-grey-5;
    }
  }
  &--responsive {
    @include mq(xxs, s) {
      .table__header {
        display: none;
      }
      .table__body .table__row {
        border-bottom: 2px solid $color-grey-100;
        display: block;
        margin-bottom: 1rem;
      }
      .table__cell {
        display: block;
        padding-left: 0;
        text-align: right;
        &:last-child {
          border: 0;
        }
        &::before {
          content: attr(data-th);
          float: left;
          font-weight: 700;
          padding-right: 1rem;
        }
      }
    }
  }
}
.table-scrollable {
  position: relative;
  ::-webkit-scrollbar {
    height: 7px;
  }
  ::-webkit-scrollbar-thumb {
    background: $color-grey-75;
    border-radius: 20px;
  }
  &--on {
    .table__header,
    .table__cell {
      white-space: nowrap;
    }
  }
  &__content {
    overflow: visible;
    overflow-x: scroll;
    width: 100%;
    &:focus {
      outline: 3px solid $color-focus;
      outline-offset: 3px;
    }
    .table__header,
    .table__cell {
      @include mq(xxs, m) {
        white-space: nowrap;
      }
    }
    .table__right-shadow,
    .table__left-shadow {
      height: 100%;
      position: absolute;
      top: 0;
      width: 5px;
      z-index: 200;
      &.with-transition {
        transition: box-shadow 0.4s ease-out;
      }
    }
    .table__right-shadow {
      right: 0;
      &.visible {
        box-shadow: inset -1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
    .table__left-shadow {
      left: 0;
      &.visible {
        box-shadow: inset 1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
  }
}
.table--sortable {
  [aria-sort='descending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-grey-35;
      }
      .bottomTriangle {
        fill: $color-ocean-blue;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .topTriangle {
          fill: $color-black;
        }
      }
    }
  }
  [aria-sort='ascending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-ocean-blue;
      }
      .bottomTriangle {
        fill: $color-grey-35;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .bottomTriangle {
          fill: $color-black;
        }
      }
    }
  }
  .table__header {
    position: relative;
    .table__sort-button {
      background-color: transparent;
      border: 0;
      box-shadow: none;
      color: $color-text-link;
      display: inline-block;
      font-family: $font-sans;
      font-weight: 700;
      line-height: 1rem;
      text-decoration: underline;
      white-space: nowrap;
      &:hover {
        color: $color-text-link-hover;
        cursor: pointer;
        text-decoration: underline solid $color-text-link-hover 2px;
      }
      .svg-icon {
        fill: $color-grey-35;
        height: 0.8rem;
        padding-bottom: 0.1rem;
        width: 0.8rem;
      }
      &:focus {
        @extend a:focus;
        .svg-icon {
          fill: $color-black;
        }
      }
    }
  }
}

When to use this component

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

How to use this component

The ‘basic table’ is the default style for a table. There are variations available, added via class modifiers which can improve the usability of the table element.

Dense table

<table class="table table--dense table--row-hover">
  <caption class="table__caption">A basic table that compacts if more columns are required</caption>
  <thead class="table__head">
    <tr class="table__row">
      <th scope="col" class="table__header ">
        <span>Column A</span>
      </th>
      <th scope="col" class="table__header ">
        <span>Column B</span>
      </th>
      <th scope="col" class="table__header ">
        <span>Column C</span>
      </th>
      <th scope="col" class="table__header ">
        <span>Column D</span>
      </th>
      <th scope="col" class="table__header ">
        <span>Column E</span>
      </th>
    </tr>
  </thead>
  <tbody class="table__body">
    <tr class="table__row">
      <td class="table__cell ">
        Cell A1
      </td>
      <td class="table__cell ">
        Cell B1
      </td>
      <td class="table__cell ">
        Cell C1
      </td>
      <td class="table__cell ">
        Cell D1
      </td>
      <td class="table__cell ">
        Cell E1
      </td>
    </tr>
    <tr class="table__row">
      <td class="table__cell ">
        Cell A2
      </td>
      <td class="table__cell ">
        Cell B2
      </td>
      <td class="table__cell ">
        Cell C2
      </td>
      <td class="table__cell ">
        Cell D2
      </td>
      <td class="table__cell ">
        Cell E2
      </td>
    </tr>
  </tbody>
</table>
Nunjucks macro options
Name Type Required Description
table_class string false Classes to add to the table component
id string false ID to add to the table component
caption string false The caption for the table component
hideCaption boolean false Visually hides the caption
scrollable boolean false Sets the component to render as a scrollable table
ariaLabel string false The aria label added to the table if it is scrollable. Defaults to Scrollable table
sortable boolean false Sets the component to render as a sortable table
ths Array<th> true An array of th elements for table
trs Array<tr> true An array of tr elements for table
tfoot Array<tfootCell> false An array of td elements for tdfoot
ariaAsc string false Sets the data-aria-asc attribute for the table. Used to set aria labels when table is sorted
ariaDesc string false Sets the data-aria-desc attribute for the table. Used to set aria labels when table is sorted

th

Name Type Required Description
class string false Classes to add to the th element
ariaSort string false Default is “none”. Accepts “ascending” or “descending”
value string true The content for the th cell

tr

Name Type Required Description
tds Array<td> true An array of td elements for each tr
trHighlight boolean false Adds a class to the table row to highlight the row

td

Name Type Required Description
class string false Classes to add to the td element
name string false Name to add to the td element
data string false The corresponding th for the td for responsive tables
dataSort integer false numerical ordering of a column of td elements for sortable table
value string false The content for the td cell
form object false Form attributes information for method, action and the button

form

Name Type Required Description
method string false Default is post if no value is provided
action string true The action for the form
button Button (ref) false Configuration object for the form button
hiddenFormField object false Configuration object for hidden form fields

hiddenFormField

Name Type Required Description
name string false Hidden field name
value string false Hidden field value

tfootCell

Name Type Required Description
value string true The content for the td cell of tdfoot
{% from "components/table/_macro.njk" import onsTable %}
{{
    onsTable({
        "table_class": "table--dense table--row-hover",
        "caption": "A basic table that compacts if more columns are required",
        "ths": [
            {
                "value": "Column A"
            },
            {
                "value": "Column B"
            },
            {
                "value": "Column C"
            },
            {
                "value": "Column D"
            },
            {
                "value": "Column E"
            }
        ],
        "trs": [
            {
                "tds": [
                    {
                        "value": "Cell A1"
                    },
                    {
                        "value": "Cell B1"
                    },
                    {
                        "value": "Cell C1"
                    },
                    {
                        "value": "Cell D1"
                    },
                    {
                        "value": "Cell E1"
                    }
                ]
            },
            {
                "tds": [
                    {
                    "value": "Cell A2"
                    },
                    {
                    "value": "Cell B2"
                    },
                    {
                    "value": "Cell C2"
                    },
                    {
                    "value": "Cell D2"
                    },
                    {
                    "value": "Cell E2"
                    }
                ]
            }
        ]
    })
}}

{% macro onsTable(params) %}
    {% from "components/button/_macro.njk" import onsButton %}
    {% from "components/icons/_macro.njk" import onsIcon %}
    {% if params.scrollable is defined and params.scrollable %}
    <div class="table-scrollable table-scrollable--on">
        <div class="table-scrollable__content" tabindex="0" role="region" aria-label="{{ params.caption }}. {{ params.ariaLabel | default("Scrollable table") }} ">
    {% endif %}
            <table {% if params.id is defined and params.id %}id="{{ params.id }}"{% endif %} class="table {{ params.table_class }}" {% if params.sortable is defined and params.sortable %}data-aria-sort="{{ params.sortBy }}" data-aria-asc="{{ params.ariaAsc }}" data-aria-desc="{{ params.ariaDesc }}"{% endif %}>
                {% if params.caption is defined and params.caption %}
                <caption class="table__caption{{ " u-vh" if params.hideCaption }}">{{ params.caption }}</caption>
                {% endif %}
                <thead class="table__head">
                    <tr class="table__row">
                        {% for th in params.ths %}
                        <th scope="col" class="table__header {{ th.class }}"{% if th.ariaSort is defined and th.ariaSort %} aria-sort="{{- th.ariaSort | default('none') -}}"{% endif %}>
                            <span {% if params.sortable is defined and params.sortable %}class="u-vh"{% endif %}>{{- th.value -}}</span>
                            {% if params.sortable is defined and params.sortable %}
                                {{
                                    onsIcon({
                                        "icon": "sort-sprite",
                                        "id": th.value
                                    })
                                }}
                            {% endif %}
                        </th>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody class="table__body">
                    {% for tr in params.trs %}
                    <tr class="table__row{{ " table__row--highlight" if tr.highlight }}" {% if tr.name is defined and tr.name %} name="{{ tr.name }}"{% endif %} {% if tr.id is defined and tr.id %} id="{{ tr.id }}"{% endif %}>
                        {% for td in tr.tds %}
                        <td class="table__cell {{ td.class }}" {% if td.id is defined and td.id %} id="{{ td.id }}"{% endif %} {% if td.name is defined and td.name %} name="{{ td.name }}"{% endif %} {% if td.data is defined and td.data %} data-th="{{ td.data }}"{% endif %} {% if td.dataSort is defined and td.dataSort %} data-sort-value="{{ td.dataSort }}"{% endif %}>
                            {% if td.form is defined and td.form %}
                                <form action="{{ td.form.action }}" method="{{ td.form.method | default('POST')}}">
                                    {{
                                        onsButton({
                                            "text": td.form.button.text,
                                            "id": td.form.button.id if td.form.button.id,
                                            "classes": td.form.button.classes if td.form.button.classes,
                                            "url": td.form.button.url if td.form.button.url,
                                            "value": td.form.button.value | safe if td.form.button.value,
                                            "name": td.form.button.name if td.form.button.name
                                        })
                                    }}
                                    {% if td.form.hiddenFormField is defined and td.form.hiddenFormField %}
                                        {% for hiddenField in td.form.hiddenFormField %}
                                            <input type="hidden" {% if hiddenField.name is defined and hiddenField.name %} name="{{ hiddenField.name }}"{% endif %} {% if hiddenField.value is defined and hiddenField.value %} value="{{ hiddenField.value }}"{% endif %} />
                                        {% endfor %}
                                    {% endif %}
                                </form>
                            {% endif %}
                            {% if td.value is defined and td.value %}
                                {{ td.value | safe }}
                            {% endif %}
                        </td>
                        {% endfor %}
                    </tr>
                    {% endfor %}
                </tbody>
                {% if params.tfoot is defined and params.tfoot %}
                <tfoot class="table__foot">
                    <tr class="table__row">
                        {% for tfootCell in params.tfoot %}
                        <td class="table__cell u-fs-s">{{ tfootCell.value }}</td>
                        {% endfor %}
                    </tr>
                </tfoot>
                {% endif %}
            </table>
        {% if params.scrollable is defined and params.scrollable %}
        </div>
    </div>
    {% endif %}
{% endmacro %}

.table {
  border-collapse: collapse;
  border-spacing: 0;
  margin-bottom: 1rem;
  width: 100%;
  &__caption {
    font-weight: 700;
    text-align: left;
  }
  &__header,
  &__cell {
    @include nth-element(1, 0);
    border-bottom: 2px solid $color-grey-100;
    overflow: hidden;
    padding: 0.5rem 0 0.5rem 1rem;
    text-align: left;
    vertical-align: top;
    &--numeric {
      text-align: right;
    }
  }
  &__cell,
  &__header--row {
    border-bottom: 1px solid $color-borders;
  }
  &__row--highlight {
    background: $color-highlight;
  }
  &:not(.table--responsive) .table__body .table__row:last-child {
    .table__cell,
    .table__header--row {
      border: 0;
    }
  }
  &__foot .table__cell {
    border-bottom: 0;
    border-top: 1px solid $color-borders;
  }
  &--dense {
    .table__head,
    .table__body,
    .table__foot {
      font-size: 81.25%;
    }
  }
  &--row-hover {
    .table__body .table__row:hover {
      background: $color-grey-5;
    }
  }
  &--responsive {
    @include mq(xxs, s) {
      .table__header {
        display: none;
      }
      .table__body .table__row {
        border-bottom: 2px solid $color-grey-100;
        display: block;
        margin-bottom: 1rem;
      }
      .table__cell {
        display: block;
        padding-left: 0;
        text-align: right;
        &:last-child {
          border: 0;
        }
        &::before {
          content: attr(data-th);
          float: left;
          font-weight: 700;
          padding-right: 1rem;
        }
      }
    }
  }
}
.table-scrollable {
  position: relative;
  ::-webkit-scrollbar {
    height: 7px;
  }
  ::-webkit-scrollbar-thumb {
    background: $color-grey-75;
    border-radius: 20px;
  }
  &--on {
    .table__header,
    .table__cell {
      white-space: nowrap;
    }
  }
  &__content {
    overflow: visible;
    overflow-x: scroll;
    width: 100%;
    &:focus {
      outline: 3px solid $color-focus;
      outline-offset: 3px;
    }
    .table__header,
    .table__cell {
      @include mq(xxs, m) {
        white-space: nowrap;
      }
    }
    .table__right-shadow,
    .table__left-shadow {
      height: 100%;
      position: absolute;
      top: 0;
      width: 5px;
      z-index: 200;
      &.with-transition {
        transition: box-shadow 0.4s ease-out;
      }
    }
    .table__right-shadow {
      right: 0;
      &.visible {
        box-shadow: inset -1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
    .table__left-shadow {
      left: 0;
      &.visible {
        box-shadow: inset 1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
  }
}
.table--sortable {
  [aria-sort='descending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-grey-35;
      }
      .bottomTriangle {
        fill: $color-ocean-blue;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .topTriangle {
          fill: $color-black;
        }
      }
    }
  }
  [aria-sort='ascending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-ocean-blue;
      }
      .bottomTriangle {
        fill: $color-grey-35;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .bottomTriangle {
          fill: $color-black;
        }
      }
    }
  }
  .table__header {
    position: relative;
    .table__sort-button {
      background-color: transparent;
      border: 0;
      box-shadow: none;
      color: $color-text-link;
      display: inline-block;
      font-family: $font-sans;
      font-weight: 700;
      line-height: 1rem;
      text-decoration: underline;
      white-space: nowrap;
      &:hover {
        color: $color-text-link-hover;
        cursor: pointer;
        text-decoration: underline solid $color-text-link-hover 2px;
      }
      .svg-icon {
        fill: $color-grey-35;
        height: 0.8rem;
        padding-bottom: 0.1rem;
        width: 0.8rem;
      }
      &:focus {
        @extend a:focus;
        .svg-icon {
          fill: $color-black;
        }
      }
    }
  }
}

  • Class - table--dense
  • Output - This reduces the font-size to compact the table for basic tables which have many rows but only a few columns.

Numeric table

<table class="table ">
  <caption class="table__caption">A basic table with numeric values</caption>
  <thead class="table__head">
    <tr class="table__row">
      <th scope="col" class="table__header  table__header--numeric">
        <span>Column A</span>
      </th>
      <th scope="col" class="table__header  table__header--numeric">
        <span>Column B</span>
      </th>
      <th scope="col" class="table__header  table__header--numeric">
        <span>Column C</span>
      </th>
    </tr>
  </thead>
  <tbody class="table__body">
    <tr class="table__row">
      <td class="table__cell table__cell--numeric">
        200
      </td>
      <td class="table__cell table__cell--numeric">
        365
      </td>
      <td class="table__cell table__cell--numeric">
        24
      </td>
    </tr>
    <tr class="table__row">
      <td class="table__cell table__cell--numeric">
        345
      </td>
      <td class="table__cell table__cell--numeric">
        33
      </td>
      <td class="table__cell  table__cell--numeric">
        13
      </td>
    </tr>
  </tbody>
</table>
Nunjucks macro options
Name Type Required Description
table_class string false Classes to add to the table component
id string false ID to add to the table component
caption string false The caption for the table component
hideCaption boolean false Visually hides the caption
scrollable boolean false Sets the component to render as a scrollable table
ariaLabel string false The aria label added to the table if it is scrollable. Defaults to Scrollable table
sortable boolean false Sets the component to render as a sortable table
ths Array<th> true An array of th elements for table
trs Array<tr> true An array of tr elements for table
tfoot Array<tfootCell> false An array of td elements for tdfoot
ariaAsc string false Sets the data-aria-asc attribute for the table. Used to set aria labels when table is sorted
ariaDesc string false Sets the data-aria-desc attribute for the table. Used to set aria labels when table is sorted

th

Name Type Required Description
class string false Classes to add to the th element
ariaSort string false Default is “none”. Accepts “ascending” or “descending”
value string true The content for the th cell

tr

Name Type Required Description
tds Array<td> true An array of td elements for each tr
trHighlight boolean false Adds a class to the table row to highlight the row

td

Name Type Required Description
class string false Classes to add to the td element
name string false Name to add to the td element
data string false The corresponding th for the td for responsive tables
dataSort integer false numerical ordering of a column of td elements for sortable table
value string false The content for the td cell
form object false Form attributes information for method, action and the button

form

Name Type Required Description
method string false Default is post if no value is provided
action string true The action for the form
button Button (ref) false Configuration object for the form button
hiddenFormField object false Configuration object for hidden form fields

hiddenFormField

Name Type Required Description
name string false Hidden field name
value string false Hidden field value

tfootCell

Name Type Required Description
value string true The content for the td cell of tdfoot
{% from "components/table/_macro.njk" import onsTable %}
{{
    onsTable({
        "caption": "A basic table with numeric values",
        "ths": [
            {
                "value": "Column A",
                "class": " table__header--numeric"
            },
            {
                "value": "Column B",
                "class": " table__header--numeric"
            },
            {
                "value": "Column C",
                "class": " table__header--numeric"
            }
        ],
        "trs": [
            {
                "tds": [
                    {
                        "value": "200",
                        "class": "table__cell--numeric"
                    },
                    {
                        "value": "365",
                        "class": "table__cell--numeric"
                    },
                    {
                        "value": "24",
                        "class": "table__cell--numeric"
                    }
                ]
            },
            {
                "tds": [
                    {
                    "value": "345",
                    "class": "table__cell--numeric"
                    },
                    {
                    "value": "33",
                    "class": "table__cell--numeric"
                    },
                    {
                    "value": "13",
                    "class": " table__cell--numeric"
                    }
                ]
            }
        ]
    })
}}

{% macro onsTable(params) %}
    {% from "components/button/_macro.njk" import onsButton %}
    {% from "components/icons/_macro.njk" import onsIcon %}
    {% if params.scrollable is defined and params.scrollable %}
    <div class="table-scrollable table-scrollable--on">
        <div class="table-scrollable__content" tabindex="0" role="region" aria-label="{{ params.caption }}. {{ params.ariaLabel | default("Scrollable table") }} ">
    {% endif %}
            <table {% if params.id is defined and params.id %}id="{{ params.id }}"{% endif %} class="table {{ params.table_class }}" {% if params.sortable is defined and params.sortable %}data-aria-sort="{{ params.sortBy }}" data-aria-asc="{{ params.ariaAsc }}" data-aria-desc="{{ params.ariaDesc }}"{% endif %}>
                {% if params.caption is defined and params.caption %}
                <caption class="table__caption{{ " u-vh" if params.hideCaption }}">{{ params.caption }}</caption>
                {% endif %}
                <thead class="table__head">
                    <tr class="table__row">
                        {% for th in params.ths %}
                        <th scope="col" class="table__header {{ th.class }}"{% if th.ariaSort is defined and th.ariaSort %} aria-sort="{{- th.ariaSort | default('none') -}}"{% endif %}>
                            <span {% if params.sortable is defined and params.sortable %}class="u-vh"{% endif %}>{{- th.value -}}</span>
                            {% if params.sortable is defined and params.sortable %}
                                {{
                                    onsIcon({
                                        "icon": "sort-sprite",
                                        "id": th.value
                                    })
                                }}
                            {% endif %}
                        </th>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody class="table__body">
                    {% for tr in params.trs %}
                    <tr class="table__row{{ " table__row--highlight" if tr.highlight }}" {% if tr.name is defined and tr.name %} name="{{ tr.name }}"{% endif %} {% if tr.id is defined and tr.id %} id="{{ tr.id }}"{% endif %}>
                        {% for td in tr.tds %}
                        <td class="table__cell {{ td.class }}" {% if td.id is defined and td.id %} id="{{ td.id }}"{% endif %} {% if td.name is defined and td.name %} name="{{ td.name }}"{% endif %} {% if td.data is defined and td.data %} data-th="{{ td.data }}"{% endif %} {% if td.dataSort is defined and td.dataSort %} data-sort-value="{{ td.dataSort }}"{% endif %}>
                            {% if td.form is defined and td.form %}
                                <form action="{{ td.form.action }}" method="{{ td.form.method | default('POST')}}">
                                    {{
                                        onsButton({
                                            "text": td.form.button.text,
                                            "id": td.form.button.id if td.form.button.id,
                                            "classes": td.form.button.classes if td.form.button.classes,
                                            "url": td.form.button.url if td.form.button.url,
                                            "value": td.form.button.value | safe if td.form.button.value,
                                            "name": td.form.button.name if td.form.button.name
                                        })
                                    }}
                                    {% if td.form.hiddenFormField is defined and td.form.hiddenFormField %}
                                        {% for hiddenField in td.form.hiddenFormField %}
                                            <input type="hidden" {% if hiddenField.name is defined and hiddenField.name %} name="{{ hiddenField.name }}"{% endif %} {% if hiddenField.value is defined and hiddenField.value %} value="{{ hiddenField.value }}"{% endif %} />
                                        {% endfor %}
                                    {% endif %}
                                </form>
                            {% endif %}
                            {% if td.value is defined and td.value %}
                                {{ td.value | safe }}
                            {% endif %}
                        </td>
                        {% endfor %}
                    </tr>
                    {% endfor %}
                </tbody>
                {% if params.tfoot is defined and params.tfoot %}
                <tfoot class="table__foot">
                    <tr class="table__row">
                        {% for tfootCell in params.tfoot %}
                        <td class="table__cell u-fs-s">{{ tfootCell.value }}</td>
                        {% endfor %}
                    </tr>
                </tfoot>
                {% endif %}
            </table>
        {% if params.scrollable is defined and params.scrollable %}
        </div>
    </div>
    {% endif %}
{% endmacro %}

.table {
  border-collapse: collapse;
  border-spacing: 0;
  margin-bottom: 1rem;
  width: 100%;
  &__caption {
    font-weight: 700;
    text-align: left;
  }
  &__header,
  &__cell {
    @include nth-element(1, 0);
    border-bottom: 2px solid $color-grey-100;
    overflow: hidden;
    padding: 0.5rem 0 0.5rem 1rem;
    text-align: left;
    vertical-align: top;
    &--numeric {
      text-align: right;
    }
  }
  &__cell,
  &__header--row {
    border-bottom: 1px solid $color-borders;
  }
  &__row--highlight {
    background: $color-highlight;
  }
  &:not(.table--responsive) .table__body .table__row:last-child {
    .table__cell,
    .table__header--row {
      border: 0;
    }
  }
  &__foot .table__cell {
    border-bottom: 0;
    border-top: 1px solid $color-borders;
  }
  &--dense {
    .table__head,
    .table__body,
    .table__foot {
      font-size: 81.25%;
    }
  }
  &--row-hover {
    .table__body .table__row:hover {
      background: $color-grey-5;
    }
  }
  &--responsive {
    @include mq(xxs, s) {
      .table__header {
        display: none;
      }
      .table__body .table__row {
        border-bottom: 2px solid $color-grey-100;
        display: block;
        margin-bottom: 1rem;
      }
      .table__cell {
        display: block;
        padding-left: 0;
        text-align: right;
        &:last-child {
          border: 0;
        }
        &::before {
          content: attr(data-th);
          float: left;
          font-weight: 700;
          padding-right: 1rem;
        }
      }
    }
  }
}
.table-scrollable {
  position: relative;
  ::-webkit-scrollbar {
    height: 7px;
  }
  ::-webkit-scrollbar-thumb {
    background: $color-grey-75;
    border-radius: 20px;
  }
  &--on {
    .table__header,
    .table__cell {
      white-space: nowrap;
    }
  }
  &__content {
    overflow: visible;
    overflow-x: scroll;
    width: 100%;
    &:focus {
      outline: 3px solid $color-focus;
      outline-offset: 3px;
    }
    .table__header,
    .table__cell {
      @include mq(xxs, m) {
        white-space: nowrap;
      }
    }
    .table__right-shadow,
    .table__left-shadow {
      height: 100%;
      position: absolute;
      top: 0;
      width: 5px;
      z-index: 200;
      &.with-transition {
        transition: box-shadow 0.4s ease-out;
      }
    }
    .table__right-shadow {
      right: 0;
      &.visible {
        box-shadow: inset -1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
    .table__left-shadow {
      left: 0;
      &.visible {
        box-shadow: inset 1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
  }
}
.table--sortable {
  [aria-sort='descending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-grey-35;
      }
      .bottomTriangle {
        fill: $color-ocean-blue;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .topTriangle {
          fill: $color-black;
        }
      }
    }
  }
  [aria-sort='ascending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-ocean-blue;
      }
      .bottomTriangle {
        fill: $color-grey-35;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .bottomTriangle {
          fill: $color-black;
        }
      }
    }
  }
  .table__header {
    position: relative;
    .table__sort-button {
      background-color: transparent;
      border: 0;
      box-shadow: none;
      color: $color-text-link;
      display: inline-block;
      font-family: $font-sans;
      font-weight: 700;
      line-height: 1rem;
      text-decoration: underline;
      white-space: nowrap;
      &:hover {
        color: $color-text-link-hover;
        cursor: pointer;
        text-decoration: underline solid $color-text-link-hover 2px;
      }
      .svg-icon {
        fill: $color-grey-35;
        height: 0.8rem;
        padding-bottom: 0.1rem;
        width: 0.8rem;
      }
      &:focus {
        @extend a:focus;
        .svg-icon {
          fill: $color-black;
        }
      }
    }
  }
}

  • Class - table__header--numeric + table__cell--numeric
  • Output - This aligns the content to the right for tables with all numeric values for best practice.

Responsive table

<table class="table table--responsive">
  <caption class="table__caption">Responsive table with stacked rows for small viewports</caption>
  <thead class="table__head">
    <tr class="table__row">
      <th scope="col" class="table__header ">
        <span>Column A</span>
      </th>
      <th scope="col" class="table__header ">
        <span>Column B</span>
      </th>
      <th scope="col" class="table__header ">
        <span>Column C</span>
      </th>
    </tr>
  </thead>
  <tbody class="table__body">
    <tr class="table__row">
      <td class="table__cell " data-th="Column A">
        Cell A1
      </td>
      <td class="table__cell " data-th="Column B">
        Cell B1
      </td>
      <td class="table__cell " data-th="Column C">
        Cell C1
      </td>
    </tr>
    <tr class="table__row">
      <td class="table__cell " data-th="Column A">
        Cell A2
      </td>
      <td class="table__cell " data-th="Column B">
        Cell B2
      </td>
      <td class="table__cell " data-th="Column C">
        Cell C2
      </td>
    </tr>
  </tbody>
</table>
Nunjucks macro options
Name Type Required Description
table_class string false Classes to add to the table component
id string false ID to add to the table component
caption string false The caption for the table component
hideCaption boolean false Visually hides the caption
scrollable boolean false Sets the component to render as a scrollable table
ariaLabel string false The aria label added to the table if it is scrollable. Defaults to Scrollable table
sortable boolean false Sets the component to render as a sortable table
ths Array<th> true An array of th elements for table
trs Array<tr> true An array of tr elements for table
tfoot Array<tfootCell> false An array of td elements for tdfoot
ariaAsc string false Sets the data-aria-asc attribute for the table. Used to set aria labels when table is sorted
ariaDesc string false Sets the data-aria-desc attribute for the table. Used to set aria labels when table is sorted

th

Name Type Required Description
class string false Classes to add to the th element
ariaSort string false Default is “none”. Accepts “ascending” or “descending”
value string true The content for the th cell

tr

Name Type Required Description
tds Array<td> true An array of td elements for each tr
trHighlight boolean false Adds a class to the table row to highlight the row

td

Name Type Required Description
class string false Classes to add to the td element
name string false Name to add to the td element
data string false The corresponding th for the td for responsive tables
dataSort integer false numerical ordering of a column of td elements for sortable table
value string false The content for the td cell
form object false Form attributes information for method, action and the button

form

Name Type Required Description
method string false Default is post if no value is provided
action string true The action for the form
button Button (ref) false Configuration object for the form button
hiddenFormField object false Configuration object for hidden form fields

hiddenFormField

Name Type Required Description
name string false Hidden field name
value string false Hidden field value

tfootCell

Name Type Required Description
value string true The content for the td cell of tdfoot
{% from "components/table/_macro.njk" import onsTable %}
{{
    onsTable({
        "table_class": "table--responsive",
        "caption": "Responsive table with stacked rows for small viewports",
        "ths": [
            {
                "value": "Column A"
            },
            {
                "value": "Column B"
            },
            {
                "value": "Column C"
            }
        ],
        "trs": [
            {
                "tds": [
                    {
                        "value": "Cell A1",
                        "data": "Column A"
                    },
                    {
                        "value": "Cell B1",
                        "data": "Column B"
                    },
                    {
                        "value": "Cell C1",
                        "data": "Column C"
                    }
                ]
            },
            {
                "tds": [
                    {
                        "value": "Cell A2",
                        "data": "Column A"
                    },
                    {
                        "value": "Cell B2",
                        "data": "Column B"
                    },
                    {
                        "value": "Cell C2",
                        "data": "Column C"
                    }
                ]
            }
        ]
    })
}}

{% macro onsTable(params) %}
    {% from "components/button/_macro.njk" import onsButton %}
    {% from "components/icons/_macro.njk" import onsIcon %}
    {% if params.scrollable is defined and params.scrollable %}
    <div class="table-scrollable table-scrollable--on">
        <div class="table-scrollable__content" tabindex="0" role="region" aria-label="{{ params.caption }}. {{ params.ariaLabel | default("Scrollable table") }} ">
    {% endif %}
            <table {% if params.id is defined and params.id %}id="{{ params.id }}"{% endif %} class="table {{ params.table_class }}" {% if params.sortable is defined and params.sortable %}data-aria-sort="{{ params.sortBy }}" data-aria-asc="{{ params.ariaAsc }}" data-aria-desc="{{ params.ariaDesc }}"{% endif %}>
                {% if params.caption is defined and params.caption %}
                <caption class="table__caption{{ " u-vh" if params.hideCaption }}">{{ params.caption }}</caption>
                {% endif %}
                <thead class="table__head">
                    <tr class="table__row">
                        {% for th in params.ths %}
                        <th scope="col" class="table__header {{ th.class }}"{% if th.ariaSort is defined and th.ariaSort %} aria-sort="{{- th.ariaSort | default('none') -}}"{% endif %}>
                            <span {% if params.sortable is defined and params.sortable %}class="u-vh"{% endif %}>{{- th.value -}}</span>
                            {% if params.sortable is defined and params.sortable %}
                                {{
                                    onsIcon({
                                        "icon": "sort-sprite",
                                        "id": th.value
                                    })
                                }}
                            {% endif %}
                        </th>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody class="table__body">
                    {% for tr in params.trs %}
                    <tr class="table__row{{ " table__row--highlight" if tr.highlight }}" {% if tr.name is defined and tr.name %} name="{{ tr.name }}"{% endif %} {% if tr.id is defined and tr.id %} id="{{ tr.id }}"{% endif %}>
                        {% for td in tr.tds %}
                        <td class="table__cell {{ td.class }}" {% if td.id is defined and td.id %} id="{{ td.id }}"{% endif %} {% if td.name is defined and td.name %} name="{{ td.name }}"{% endif %} {% if td.data is defined and td.data %} data-th="{{ td.data }}"{% endif %} {% if td.dataSort is defined and td.dataSort %} data-sort-value="{{ td.dataSort }}"{% endif %}>
                            {% if td.form is defined and td.form %}
                                <form action="{{ td.form.action }}" method="{{ td.form.method | default('POST')}}">
                                    {{
                                        onsButton({
                                            "text": td.form.button.text,
                                            "id": td.form.button.id if td.form.button.id,
                                            "classes": td.form.button.classes if td.form.button.classes,
                                            "url": td.form.button.url if td.form.button.url,
                                            "value": td.form.button.value | safe if td.form.button.value,
                                            "name": td.form.button.name if td.form.button.name
                                        })
                                    }}
                                    {% if td.form.hiddenFormField is defined and td.form.hiddenFormField %}
                                        {% for hiddenField in td.form.hiddenFormField %}
                                            <input type="hidden" {% if hiddenField.name is defined and hiddenField.name %} name="{{ hiddenField.name }}"{% endif %} {% if hiddenField.value is defined and hiddenField.value %} value="{{ hiddenField.value }}"{% endif %} />
                                        {% endfor %}
                                    {% endif %}
                                </form>
                            {% endif %}
                            {% if td.value is defined and td.value %}
                                {{ td.value | safe }}
                            {% endif %}
                        </td>
                        {% endfor %}
                    </tr>
                    {% endfor %}
                </tbody>
                {% if params.tfoot is defined and params.tfoot %}
                <tfoot class="table__foot">
                    <tr class="table__row">
                        {% for tfootCell in params.tfoot %}
                        <td class="table__cell u-fs-s">{{ tfootCell.value }}</td>
                        {% endfor %}
                    </tr>
                </tfoot>
                {% endif %}
            </table>
        {% if params.scrollable is defined and params.scrollable %}
        </div>
    </div>
    {% endif %}
{% endmacro %}

.table {
  border-collapse: collapse;
  border-spacing: 0;
  margin-bottom: 1rem;
  width: 100%;
  &__caption {
    font-weight: 700;
    text-align: left;
  }
  &__header,
  &__cell {
    @include nth-element(1, 0);
    border-bottom: 2px solid $color-grey-100;
    overflow: hidden;
    padding: 0.5rem 0 0.5rem 1rem;
    text-align: left;
    vertical-align: top;
    &--numeric {
      text-align: right;
    }
  }
  &__cell,
  &__header--row {
    border-bottom: 1px solid $color-borders;
  }
  &__row--highlight {
    background: $color-highlight;
  }
  &:not(.table--responsive) .table__body .table__row:last-child {
    .table__cell,
    .table__header--row {
      border: 0;
    }
  }
  &__foot .table__cell {
    border-bottom: 0;
    border-top: 1px solid $color-borders;
  }
  &--dense {
    .table__head,
    .table__body,
    .table__foot {
      font-size: 81.25%;
    }
  }
  &--row-hover {
    .table__body .table__row:hover {
      background: $color-grey-5;
    }
  }
  &--responsive {
    @include mq(xxs, s) {
      .table__header {
        display: none;
      }
      .table__body .table__row {
        border-bottom: 2px solid $color-grey-100;
        display: block;
        margin-bottom: 1rem;
      }
      .table__cell {
        display: block;
        padding-left: 0;
        text-align: right;
        &:last-child {
          border: 0;
        }
        &::before {
          content: attr(data-th);
          float: left;
          font-weight: 700;
          padding-right: 1rem;
        }
      }
    }
  }
}
.table-scrollable {
  position: relative;
  ::-webkit-scrollbar {
    height: 7px;
  }
  ::-webkit-scrollbar-thumb {
    background: $color-grey-75;
    border-radius: 20px;
  }
  &--on {
    .table__header,
    .table__cell {
      white-space: nowrap;
    }
  }
  &__content {
    overflow: visible;
    overflow-x: scroll;
    width: 100%;
    &:focus {
      outline: 3px solid $color-focus;
      outline-offset: 3px;
    }
    .table__header,
    .table__cell {
      @include mq(xxs, m) {
        white-space: nowrap;
      }
    }
    .table__right-shadow,
    .table__left-shadow {
      height: 100%;
      position: absolute;
      top: 0;
      width: 5px;
      z-index: 200;
      &.with-transition {
        transition: box-shadow 0.4s ease-out;
      }
    }
    .table__right-shadow {
      right: 0;
      &.visible {
        box-shadow: inset -1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
    .table__left-shadow {
      left: 0;
      &.visible {
        box-shadow: inset 1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
  }
}
.table--sortable {
  [aria-sort='descending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-grey-35;
      }
      .bottomTriangle {
        fill: $color-ocean-blue;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .topTriangle {
          fill: $color-black;
        }
      }
    }
  }
  [aria-sort='ascending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-ocean-blue;
      }
      .bottomTriangle {
        fill: $color-grey-35;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .bottomTriangle {
          fill: $color-black;
        }
      }
    }
  }
  .table__header {
    position: relative;
    .table__sort-button {
      background-color: transparent;
      border: 0;
      box-shadow: none;
      color: $color-text-link;
      display: inline-block;
      font-family: $font-sans;
      font-weight: 700;
      line-height: 1rem;
      text-decoration: underline;
      white-space: nowrap;
      &:hover {
        color: $color-text-link-hover;
        cursor: pointer;
        text-decoration: underline solid $color-text-link-hover 2px;
      }
      .svg-icon {
        fill: $color-grey-35;
        height: 0.8rem;
        padding-bottom: 0.1rem;
        width: 0.8rem;
      }
      &:focus {
        @extend a:focus;
        .svg-icon {
          fill: $color-black;
        }
      }
    }
  }
}

  • Class - table--responsive
  • Output - Stacks the rows on viewports that are 500px and lower. Displays the corresponding header value with each cell.

Scrollable table

<div class="table-scrollable table-scrollable--on">
  <div class="table-scrollable__content" tabindex="0" role="region" aria-label="Scrollable table. There are 7 columns in this table. Some of the table may be off screen. Scroll or drag horizontally to bring into view. ">
    <table class="table ">
      <caption class="table__caption">Scrollable table</caption>
      <thead class="table__head">
        <tr class="table__row">
          <th scope="col" class="table__header ">
            <span>ID</span>
          </th>
          <th scope="col" class="table__header ">
            <span>Title</span>
          </th>
          <th scope="col" class="table__header ">
            <span>Abbreviation</span>
          </th>
          <th scope="col" class="table__header ">
            <span>Legal basis</span>
          </th>
          <th scope="col" class="table__header ">
            <span>Frequency</span>
          </th>
          <th scope="col" class="table__header ">
            <span>Date</span>
          </th>
          <th scope="col" class="table__header ">
            <span>Status</span>
          </th>
        </tr>
      </thead>
      <tbody class="table__body">
        <tr class="table__row">
          <td class="table__cell ">
            023
          </td>
          <td class="table__cell ">
            Monthly Business Survey - Retail Sales Index
          </td>
          <td class="table__cell ">
            RSI
          </td>
          <td class="table__cell ">
            Statistics of Trade Act 1947
          </td>
          <td class="table__cell ">
            Monthly
          </td>
          <td class="table__cell ">
            20 Jan 2018
          </td>
          <td class="table__cell ">
            <span class='status status--success'>Ready</span>
          </td>
        </tr>
        <tr class="table__row">
          <td class="table__cell ">
            112
          </td>
          <td class="table__cell ">
            Annual Inward Foreign Direct Investment Survey
          </td>
          <td class="table__cell ">
            AIFDI
          </td>
          <td class="table__cell ">
            Statistics of Trade Act 1947
          </td>
          <td class="table__cell ">
            Annually
          </td>
          <td class="table__cell ">
            26 Feb 2018
          </td>
          <td class="table__cell ">
            <span class='status status--dead'>Not ready</span>
          </td>
        </tr>
        <tr class="table__row">
          <td class="table__cell ">
            332
          </td>
          <td class="table__cell ">
            Business Register and Employment Survey
          </td>
          <td class="table__cell ">
            BRES
          </td>
          <td class="table__cell ">
            Statistics of Trade Act 1947
          </td>
          <td class="table__cell ">
            Annually
          </td>
          <td class="table__cell ">
            23 Jan 2013
          </td>
          <td class="table__cell ">
            <span class='status status--info'>In progress</span>
          </td>
        </tr>
        <tr class="table__row">
          <td class="table__cell ">
            654
          </td>
          <td class="table__cell ">
            Quartely Survey of Building Materials Sand and Gravel
          </td>
          <td class="table__cell ">
            QBMS
          </td>
          <td class="table__cell ">
            Statistics of Trade Act 1947 - BEIS
          </td>
          <td class="table__cell ">
            Quartely
          </td>
          <td class="table__cell ">
            24 Jan 2015
          </td>
          <td class="table__cell ">
            <span class='status status--error'>Issue</span>
          </td>
        </tr>
        <tr class="table__row">
          <td class="table__cell ">
            765
          </td>
          <td class="table__cell ">
            Monthly Survey of Building Materials Concrete Building Blocks
          </td>
          <td class="table__cell ">
            MSBB
          </td>
          <td class="table__cell ">
            Voluntary
          </td>
          <td class="table__cell ">
            Monthly
          </td>
          <td class="table__cell ">
            25 Jan 2014
          </td>
          <td class="table__cell ">
            <span class='status status--success'>Ready</span>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</div>
Nunjucks macro options
Name Type Required Description
table_class string false Classes to add to the table component
id string false ID to add to the table component
caption string false The caption for the table component
hideCaption boolean false Visually hides the caption
scrollable boolean false Sets the component to render as a scrollable table
ariaLabel string false The aria label added to the table if it is scrollable. Defaults to Scrollable table
sortable boolean false Sets the component to render as a sortable table
ths Array<th> true An array of th elements for table
trs Array<tr> true An array of tr elements for table
tfoot Array<tfootCell> false An array of td elements for tdfoot
ariaAsc string false Sets the data-aria-asc attribute for the table. Used to set aria labels when table is sorted
ariaDesc string false Sets the data-aria-desc attribute for the table. Used to set aria labels when table is sorted

th

Name Type Required Description
class string false Classes to add to the th element
ariaSort string false Default is “none”. Accepts “ascending” or “descending”
value string true The content for the th cell

tr

Name Type Required Description
tds Array<td> true An array of td elements for each tr
trHighlight boolean false Adds a class to the table row to highlight the row

td

Name Type Required Description
class string false Classes to add to the td element
name string false Name to add to the td element
data string false The corresponding th for the td for responsive tables
dataSort integer false numerical ordering of a column of td elements for sortable table
value string false The content for the td cell
form object false Form attributes information for method, action and the button

form

Name Type Required Description
method string false Default is post if no value is provided
action string true The action for the form
button Button (ref) false Configuration object for the form button
hiddenFormField object false Configuration object for hidden form fields

hiddenFormField

Name Type Required Description
name string false Hidden field name
value string false Hidden field value

tfootCell

Name Type Required Description
value string true The content for the td cell of tdfoot
{% from "components/table/_macro.njk" import onsTable %}
{{
    onsTable({
        "scrollable": true,
        "caption": "Scrollable table",
        "ariaLabel": "There are 7 columns in this table. Some of the table may be off screen. Scroll or drag horizontally to bring into view.",
        "ths": [
            {
                "value": "ID"
            },
            {
                "value": "Title"
            },
            {
                "value": "Abbreviation"
            },
            {
                "value": "Legal basis"
            },
            {
                "value": "Frequency"
            },
            {
                "value": "Date"
            },
            {
                "value": "Status"
            }
        ],
        "trs": [
            {
                "tds": [
                    {
                        "value": "023"
                    },
                    {
                        "value": "Monthly Business Survey - Retail Sales Index"
                    },
                    {
                        "value": "RSI"
                    },
                    {
                        "value": "Statistics of Trade Act 1947"
                    },
                    {
                        "value": "Monthly"
                    },
                    {
                        "value": "20 Jan 2018"
                    },
                    {
                        "value": "<span class='status status--success'>Ready</span>"
                    }
                ]
            },
            {
                "tds": [
                    {
                        "value": "112"
                    },
                    {
                        "value": "Annual Inward Foreign Direct Investment Survey"
                    },
                    {
                        "value": "AIFDI"
                    },
                    {
                        "value": "Statistics of Trade Act 1947"
                    },
                    {
                        "value": "Annually"
                    },
                    {
                        "value": "26 Feb 2018"
                    },
                    {
                        "value": "<span class='status status--dead'>Not ready</span>"
                    }
                ]
            },
            {
                "tds": [
                    {
                        "value": "332"
                    },
                    {
                        "value": "Business Register and Employment Survey"
                    },
                    {
                        "value": "BRES"
                    },
                    {
                        "value": "Statistics of Trade Act 1947"
                    },
                    {
                        "value": "Annually"
                    },
                    {
                        "value": "23 Jan 2013"
                    },
                    {
                        "value": "<span class='status status--info'>In progress</span>"
                    }
                ]
            },
            {
                "tds": [
                    {
                        "value": "654"
                    },
                    {
                        "value": "Quartely Survey of Building Materials Sand and Gravel"
                    },
                    {
                        "value": "QBMS"
                    },
                    {
                        "value": "Statistics of Trade Act 1947 - BEIS"
                    },
                    {
                        "value": "Quartely"
                    },
                    {
                        "value": "24 Jan 2015"
                    },
                    {
                        "value": "<span class='status status--error'>Issue</span>"
                    }
                ]
            },
            {
                "tds": [
                    {
                        "value": "765"
                    },
                    {
                        "value": "Monthly Survey of Building Materials Concrete Building Blocks"
                    },
                    {
                        "value": "MSBB"
                    },
                    {
                        "value": "Voluntary"
                    },
                    {
                        "value": "Monthly"
                    },
                    {
                        "value": "25 Jan 2014"
                    },
                    {
                        "value": "<span class='status status--success'>Ready</span>"
                    }
                ]
            }
        ]
    })
}}

{% macro onsTable(params) %}
    {% from "components/button/_macro.njk" import onsButton %}
    {% from "components/icons/_macro.njk" import onsIcon %}
    {% if params.scrollable is defined and params.scrollable %}
    <div class="table-scrollable table-scrollable--on">
        <div class="table-scrollable__content" tabindex="0" role="region" aria-label="{{ params.caption }}. {{ params.ariaLabel | default("Scrollable table") }} ">
    {% endif %}
            <table {% if params.id is defined and params.id %}id="{{ params.id }}"{% endif %} class="table {{ params.table_class }}" {% if params.sortable is defined and params.sortable %}data-aria-sort="{{ params.sortBy }}" data-aria-asc="{{ params.ariaAsc }}" data-aria-desc="{{ params.ariaDesc }}"{% endif %}>
                {% if params.caption is defined and params.caption %}
                <caption class="table__caption{{ " u-vh" if params.hideCaption }}">{{ params.caption }}</caption>
                {% endif %}
                <thead class="table__head">
                    <tr class="table__row">
                        {% for th in params.ths %}
                        <th scope="col" class="table__header {{ th.class }}"{% if th.ariaSort is defined and th.ariaSort %} aria-sort="{{- th.ariaSort | default('none') -}}"{% endif %}>
                            <span {% if params.sortable is defined and params.sortable %}class="u-vh"{% endif %}>{{- th.value -}}</span>
                            {% if params.sortable is defined and params.sortable %}
                                {{
                                    onsIcon({
                                        "icon": "sort-sprite",
                                        "id": th.value
                                    })
                                }}
                            {% endif %}
                        </th>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody class="table__body">
                    {% for tr in params.trs %}
                    <tr class="table__row{{ " table__row--highlight" if tr.highlight }}" {% if tr.name is defined and tr.name %} name="{{ tr.name }}"{% endif %} {% if tr.id is defined and tr.id %} id="{{ tr.id }}"{% endif %}>
                        {% for td in tr.tds %}
                        <td class="table__cell {{ td.class }}" {% if td.id is defined and td.id %} id="{{ td.id }}"{% endif %} {% if td.name is defined and td.name %} name="{{ td.name }}"{% endif %} {% if td.data is defined and td.data %} data-th="{{ td.data }}"{% endif %} {% if td.dataSort is defined and td.dataSort %} data-sort-value="{{ td.dataSort }}"{% endif %}>
                            {% if td.form is defined and td.form %}
                                <form action="{{ td.form.action }}" method="{{ td.form.method | default('POST')}}">
                                    {{
                                        onsButton({
                                            "text": td.form.button.text,
                                            "id": td.form.button.id if td.form.button.id,
                                            "classes": td.form.button.classes if td.form.button.classes,
                                            "url": td.form.button.url if td.form.button.url,
                                            "value": td.form.button.value | safe if td.form.button.value,
                                            "name": td.form.button.name if td.form.button.name
                                        })
                                    }}
                                    {% if td.form.hiddenFormField is defined and td.form.hiddenFormField %}
                                        {% for hiddenField in td.form.hiddenFormField %}
                                            <input type="hidden" {% if hiddenField.name is defined and hiddenField.name %} name="{{ hiddenField.name }}"{% endif %} {% if hiddenField.value is defined and hiddenField.value %} value="{{ hiddenField.value }}"{% endif %} />
                                        {% endfor %}
                                    {% endif %}
                                </form>
                            {% endif %}
                            {% if td.value is defined and td.value %}
                                {{ td.value | safe }}
                            {% endif %}
                        </td>
                        {% endfor %}
                    </tr>
                    {% endfor %}
                </tbody>
                {% if params.tfoot is defined and params.tfoot %}
                <tfoot class="table__foot">
                    <tr class="table__row">
                        {% for tfootCell in params.tfoot %}
                        <td class="table__cell u-fs-s">{{ tfootCell.value }}</td>
                        {% endfor %}
                    </tr>
                </tfoot>
                {% endif %}
            </table>
        {% if params.scrollable is defined and params.scrollable %}
        </div>
    </div>
    {% endif %}
{% endmacro %}

.table {
  border-collapse: collapse;
  border-spacing: 0;
  margin-bottom: 1rem;
  width: 100%;
  &__caption {
    font-weight: 700;
    text-align: left;
  }
  &__header,
  &__cell {
    @include nth-element(1, 0);
    border-bottom: 2px solid $color-grey-100;
    overflow: hidden;
    padding: 0.5rem 0 0.5rem 1rem;
    text-align: left;
    vertical-align: top;
    &--numeric {
      text-align: right;
    }
  }
  &__cell,
  &__header--row {
    border-bottom: 1px solid $color-borders;
  }
  &__row--highlight {
    background: $color-highlight;
  }
  &:not(.table--responsive) .table__body .table__row:last-child {
    .table__cell,
    .table__header--row {
      border: 0;
    }
  }
  &__foot .table__cell {
    border-bottom: 0;
    border-top: 1px solid $color-borders;
  }
  &--dense {
    .table__head,
    .table__body,
    .table__foot {
      font-size: 81.25%;
    }
  }
  &--row-hover {
    .table__body .table__row:hover {
      background: $color-grey-5;
    }
  }
  &--responsive {
    @include mq(xxs, s) {
      .table__header {
        display: none;
      }
      .table__body .table__row {
        border-bottom: 2px solid $color-grey-100;
        display: block;
        margin-bottom: 1rem;
      }
      .table__cell {
        display: block;
        padding-left: 0;
        text-align: right;
        &:last-child {
          border: 0;
        }
        &::before {
          content: attr(data-th);
          float: left;
          font-weight: 700;
          padding-right: 1rem;
        }
      }
    }
  }
}
.table-scrollable {
  position: relative;
  ::-webkit-scrollbar {
    height: 7px;
  }
  ::-webkit-scrollbar-thumb {
    background: $color-grey-75;
    border-radius: 20px;
  }
  &--on {
    .table__header,
    .table__cell {
      white-space: nowrap;
    }
  }
  &__content {
    overflow: visible;
    overflow-x: scroll;
    width: 100%;
    &:focus {
      outline: 3px solid $color-focus;
      outline-offset: 3px;
    }
    .table__header,
    .table__cell {
      @include mq(xxs, m) {
        white-space: nowrap;
      }
    }
    .table__right-shadow,
    .table__left-shadow {
      height: 100%;
      position: absolute;
      top: 0;
      width: 5px;
      z-index: 200;
      &.with-transition {
        transition: box-shadow 0.4s ease-out;
      }
    }
    .table__right-shadow {
      right: 0;
      &.visible {
        box-shadow: inset -1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
    .table__left-shadow {
      left: 0;
      &.visible {
        box-shadow: inset 1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
  }
}
.table--sortable {
  [aria-sort='descending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-grey-35;
      }
      .bottomTriangle {
        fill: $color-ocean-blue;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .topTriangle {
          fill: $color-black;
        }
      }
    }
  }
  [aria-sort='ascending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-ocean-blue;
      }
      .bottomTriangle {
        fill: $color-grey-35;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .bottomTriangle {
          fill: $color-black;
        }
      }
    }
  }
  .table__header {
    position: relative;
    .table__sort-button {
      background-color: transparent;
      border: 0;
      box-shadow: none;
      color: $color-text-link;
      display: inline-block;
      font-family: $font-sans;
      font-weight: 700;
      line-height: 1rem;
      text-decoration: underline;
      white-space: nowrap;
      &:hover {
        color: $color-text-link-hover;
        cursor: pointer;
        text-decoration: underline solid $color-text-link-hover 2px;
      }
      .svg-icon {
        fill: $color-grey-35;
        height: 0.8rem;
        padding-bottom: 0.1rem;
        width: 0.8rem;
      }
      &:focus {
        @extend a:focus;
        .svg-icon {
          fill: $color-black;
        }
      }
    }
  }
}

  • Class - table--scrollable table-scrollable--on
  • Output - Creates a scrollable full width table on viewports that are 740px and lower. Optionally allows for the table to be set at 100% on all viewports (shown in the example above).

Sortable table

<table class="table  table--sortable" data-aria-sort="Sort by" data-aria-asc="ascending" data-aria-desc="descending">
  <caption class="table__caption">Javascript enhanced sortable table</caption>
  <thead class="table__head">
    <tr class="table__row">
      <th scope="col" class="table__header " aria-sort="none">
        <span class="u-vh">ID</span>
        <svg id="sort-sprite-id" class="svg-icon" viewBox="0 0 12 19" xmlns="http://www.w3.org/2000/svg" focusable="false">
          <path class="topTriangle" d="M6 0l6 7.2H0L6 0zm0 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
          <path class="bottomTriangle" d="M6 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
        </svg>
      </th>
      <th scope="col" class="table__header " aria-sort="none">
        <span class="u-vh">Title</span>
        <svg id="sort-sprite-title" class="svg-icon" viewBox="0 0 12 19" xmlns="http://www.w3.org/2000/svg" focusable="false">
          <path class="topTriangle" d="M6 0l6 7.2H0L6 0zm0 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
          <path class="bottomTriangle" d="M6 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
        </svg>
      </th>
      <th scope="col" class="table__header " aria-sort="none">
        <span class="u-vh">Abbreviation</span>
        <svg id="sort-sprite-abbreviation" class="svg-icon" viewBox="0 0 12 19" xmlns="http://www.w3.org/2000/svg" focusable="false">
          <path class="topTriangle" d="M6 0l6 7.2H0L6 0zm0 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
          <path class="bottomTriangle" d="M6 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
        </svg>
      </th>
      <th scope="col" class="table__header " aria-sort="none">
        <span class="u-vh">Legal basis</span>
        <svg id="sort-sprite-legal basis" class="svg-icon" viewBox="0 0 12 19" xmlns="http://www.w3.org/2000/svg" focusable="false">
          <path class="topTriangle" d="M6 0l6 7.2H0L6 0zm0 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
          <path class="bottomTriangle" d="M6 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
        </svg>
      </th>
      <th scope="col" class="table__header " aria-sort="none">
        <span class="u-vh">Frequency</span>
        <svg id="sort-sprite-frequency" class="svg-icon" viewBox="0 0 12 19" xmlns="http://www.w3.org/2000/svg" focusable="false">
          <path class="topTriangle" d="M6 0l6 7.2H0L6 0zm0 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
          <path class="bottomTriangle" d="M6 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
        </svg>
      </th>
      <th scope="col" class="table__header " aria-sort="none">
        <span class="u-vh">Date</span>
        <svg id="sort-sprite-date" class="svg-icon" viewBox="0 0 12 19" xmlns="http://www.w3.org/2000/svg" focusable="false">
          <path class="topTriangle" d="M6 0l6 7.2H0L6 0zm0 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
          <path class="bottomTriangle" d="M6 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
        </svg>
      </th>
      <th scope="col" class="table__header " aria-sort="none">
        <span class="u-vh">Status</span>
        <svg id="sort-sprite-status" class="svg-icon" viewBox="0 0 12 19" xmlns="http://www.w3.org/2000/svg" focusable="false">
          <path class="topTriangle" d="M6 0l6 7.2H0L6 0zm0 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
          <path class="bottomTriangle" d="M6 18.6l6-7.2H0l6 7.2zm0 3.6l6 7.2H0l6-7.2z" />
        </svg>
      </th>
    </tr>
  </thead>
  <tbody class="table__body">
    <tr class="table__row">
      <td class="table__cell ">
        023
      </td>
      <td class="table__cell ">
        Monthly Business Survey - Retail Sales Index
      </td>
      <td class="table__cell ">
        RSI
      </td>
      <td class="table__cell ">
        Statistics of Trade Act 1947
      </td>
      <td class="table__cell " data-sort-value="1">
        Monthly
      </td>
      <td class="table__cell " data-sort-value="2018-01-20">
        20 Jan 2018
      </td>
      <td class="table__cell " data-sort-value="0">
        <span class='status status--success'>Ready</span>
      </td>
    </tr>
    <tr class="table__row">
      <td class="table__cell ">
        112
      </td>
      <td class="table__cell ">
        Annual Inward Foreign Direct Investment Survey
      </td>
      <td class="table__cell ">
        AIFDI
      </td>
      <td class="table__cell ">
        Statistics of Trade Act 1947
      </td>
      <td class="table__cell " data-sort-value="12">
        Annually
      </td>
      <td class="table__cell " data-sort-value="2018-02-26">
        26 Feb 2018
      </td>
      <td class="table__cell " data-sort-value="1">
        <span class='status status--dead'>Not ready</span>
      </td>
    </tr>
    <tr class="table__row">
      <td class="table__cell ">
        332
      </td>
      <td class="table__cell ">
        Business Register and Employment Survey
      </td>
      <td class="table__cell ">
        BRES
      </td>
      <td class="table__cell ">
        Statistics of Trade Act 1947
      </td>
      <td class="table__cell " data-sort-value="12">
        Annually
      </td>
      <td class="table__cell " data-sort-value="2013-01-23">
        23 Jan 2013
      </td>
      <td class="table__cell " data-sort-value="2">
        <span class='status status--info'>In progress</span>
      </td>
    </tr>
    <tr class="table__row">
      <td class="table__cell ">
        654
      </td>
      <td class="table__cell ">
        Quartely Survey of Building Materials Sand and Gravel
      </td>
      <td class="table__cell ">
        QBMS
      </td>
      <td class="table__cell ">
        Statistics of Trade Act 1947 - BEIS
      </td>
      <td class="table__cell " data-sort-value="3">
        Quartely
      </td>
      <td class="table__cell " data-sort-value="2015-01-24">
        24 Jan 2015
      </td>
      <td class="table__cell " data-sort-value="3">
        <span class='status status--error'>Issue</span>
      </td>
    </tr>
    <tr class="table__row">
      <td class="table__cell ">
        765
      </td>
      <td class="table__cell ">
        Monthly Survey of Building Materials Concrete Building Blocks
      </td>
      <td class="table__cell ">
        MSBB
      </td>
      <td class="table__cell ">
        Voluntary
      </td>
      <td class="table__cell " data-sort-value="1">
        Monthly
      </td>
      <td class="table__cell " data-sort-value="2014-01-25">
        25 Jan 2014
      </td>
      <td class="table__cell " data-sort-value="0">
        <span class='status status--success'>Ready</span>
      </td>
    </tr>
  </tbody>
</table>
Nunjucks macro options
Name Type Required Description
table_class string false Classes to add to the table component
id string false ID to add to the table component
caption string false The caption for the table component
hideCaption boolean false Visually hides the caption
scrollable boolean false Sets the component to render as a scrollable table
ariaLabel string false The aria label added to the table if it is scrollable. Defaults to Scrollable table
sortable boolean false Sets the component to render as a sortable table
ths Array<th> true An array of th elements for table
trs Array<tr> true An array of tr elements for table
tfoot Array<tfootCell> false An array of td elements for tdfoot
ariaAsc string false Sets the data-aria-asc attribute for the table. Used to set aria labels when table is sorted
ariaDesc string false Sets the data-aria-desc attribute for the table. Used to set aria labels when table is sorted

th

Name Type Required Description
class string false Classes to add to the th element
ariaSort string false Default is “none”. Accepts “ascending” or “descending”
value string true The content for the th cell

tr

Name Type Required Description
tds Array<td> true An array of td elements for each tr
trHighlight boolean false Adds a class to the table row to highlight the row

td

Name Type Required Description
class string false Classes to add to the td element
name string false Name to add to the td element
data string false The corresponding th for the td for responsive tables
dataSort integer false numerical ordering of a column of td elements for sortable table
value string false The content for the td cell
form object false Form attributes information for method, action and the button

form

Name Type Required Description
method string false Default is post if no value is provided
action string true The action for the form
button Button (ref) false Configuration object for the form button
hiddenFormField object false Configuration object for hidden form fields

hiddenFormField

Name Type Required Description
name string false Hidden field name
value string false Hidden field value

tfootCell

Name Type Required Description
value string true The content for the td cell of tdfoot
{% from "components/table/_macro.njk" import onsTable %}
{{
    onsTable({
        "sortable": true,
        "table_class": " table--sortable",
        "caption": "Javascript enhanced sortable table",
        "sortBy": "Sort by",
        "ariaAsc": "ascending",
        "ariaDesc": "descending",
        "ths": [
                {
                    "value": "ID",
                    "ariaSort": "none"
                },
                {
                    "value": "Title",
                    "ariaSort": "none"
                },
                {
                    "value": "Abbreviation",
                    "ariaSort": "none"
                },
                {
                    "value": "Legal basis",
                    "ariaSort": "none"
                },
                {
                    "value": "Frequency",
                    "ariaSort": "none"
                },
                {
                    "value": "Date",
                    "ariaSort": "none"
                },
                {
                    "value": "Status",
                    "ariaSort": "none"
                }
            ],
            "trs": [
                {
                    "tds": [
                        {
                            "value": "023"
                        },
                        {
                            "value": "Monthly Business Survey - Retail Sales Index"
                        },
                        {
                            "value": "RSI"
                        },
                        {
                            "value": "Statistics of Trade Act 1947"
                        },
                        {
                            "value": "Monthly",
                            "dataSort": "1"
                        },
                        {
                            "value": "20 Jan 2018",
                            "dataSort": "2018-01-20"
                        },
                        {
                            "value": "<span class='status status--success'>Ready</span>",
                            "dataSort": "0"
                        }
                    ]
                },
                {
                    "tds": [
                        {
                            "value": "112"
                        },
                        {
                            "value": "Annual Inward Foreign Direct Investment Survey"
                        },
                        {
                            "value": "AIFDI"
                        },
                        {
                            "value": "Statistics of Trade Act 1947"
                        },
                        {
                            "value": "Annually",
                            "dataSort": "12"
                        },
                        {
                            "value": "26 Feb 2018",
                            "dataSort": "2018-02-26"
                        },
                        {
                            "value": "<span class='status status--dead'>Not ready</span>",
                            "dataSort": "1"
                        }
                    ]
                },
                {
                    "tds": [
                        {
                            "value": "332"
                        },
                        {
                            "value": "Business Register and Employment Survey"
                        },
                        {
                            "value": "BRES"
                        },
                        {
                            "value": "Statistics of Trade Act 1947"
                        },
                        {
                            "value": "Annually",
                            "dataSort": "12"
                        },
                        {
                            "value": "23 Jan 2013",
                            "dataSort": "2013-01-23"
                        },
                        {
                            "value": "<span class='status status--info'>In progress</span>",
                            "dataSort": "2"
                        }
                    ]
                },
                {
                    "tds": [
                        {
                            "value": "654"
                        },
                        {
                            "value": "Quartely Survey of Building Materials Sand and Gravel"
                        },
                        {
                            "value": "QBMS"
                        },
                        {
                            "value": "Statistics of Trade Act 1947 - BEIS"
                        },
                        {
                            "value": "Quartely",
                            "dataSort": "3"
                        },
                        {
                            "value": "24 Jan 2015",
                            "dataSort": "2015-01-24"
                        },
                        {
                            "value": "<span class='status status--error'>Issue</span>",
                            "dataSort": "3"
                        }
                    ]
                },
                {
                    "tds": [
                        {
                            "value": "765"
                        },
                        {
                            "value": "Monthly Survey of Building Materials Concrete Building Blocks"
                        },
                        {
                            "value": "MSBB"
                        },
                        {
                            "value": "Voluntary"
                        },
                        {
                            "value": "Monthly",
                            "dataSort": "1"
                        },
                        {
                            "value": "25 Jan 2014",
                            "dataSort": "2014-01-25"
                        },
                        {
                            "value": "<span class='status status--success'>Ready</span>",
                            "dataSort": "0"
                        }
                    ]
                }
            ]
    })
}}

{% macro onsTable(params) %}
    {% from "components/button/_macro.njk" import onsButton %}
    {% from "components/icons/_macro.njk" import onsIcon %}
    {% if params.scrollable is defined and params.scrollable %}
    <div class="table-scrollable table-scrollable--on">
        <div class="table-scrollable__content" tabindex="0" role="region" aria-label="{{ params.caption }}. {{ params.ariaLabel | default("Scrollable table") }} ">
    {% endif %}
            <table {% if params.id is defined and params.id %}id="{{ params.id }}"{% endif %} class="table {{ params.table_class }}" {% if params.sortable is defined and params.sortable %}data-aria-sort="{{ params.sortBy }}" data-aria-asc="{{ params.ariaAsc }}" data-aria-desc="{{ params.ariaDesc }}"{% endif %}>
                {% if params.caption is defined and params.caption %}
                <caption class="table__caption{{ " u-vh" if params.hideCaption }}">{{ params.caption }}</caption>
                {% endif %}
                <thead class="table__head">
                    <tr class="table__row">
                        {% for th in params.ths %}
                        <th scope="col" class="table__header {{ th.class }}"{% if th.ariaSort is defined and th.ariaSort %} aria-sort="{{- th.ariaSort | default('none') -}}"{% endif %}>
                            <span {% if params.sortable is defined and params.sortable %}class="u-vh"{% endif %}>{{- th.value -}}</span>
                            {% if params.sortable is defined and params.sortable %}
                                {{
                                    onsIcon({
                                        "icon": "sort-sprite",
                                        "id": th.value
                                    })
                                }}
                            {% endif %}
                        </th>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody class="table__body">
                    {% for tr in params.trs %}
                    <tr class="table__row{{ " table__row--highlight" if tr.highlight }}" {% if tr.name is defined and tr.name %} name="{{ tr.name }}"{% endif %} {% if tr.id is defined and tr.id %} id="{{ tr.id }}"{% endif %}>
                        {% for td in tr.tds %}
                        <td class="table__cell {{ td.class }}" {% if td.id is defined and td.id %} id="{{ td.id }}"{% endif %} {% if td.name is defined and td.name %} name="{{ td.name }}"{% endif %} {% if td.data is defined and td.data %} data-th="{{ td.data }}"{% endif %} {% if td.dataSort is defined and td.dataSort %} data-sort-value="{{ td.dataSort }}"{% endif %}>
                            {% if td.form is defined and td.form %}
                                <form action="{{ td.form.action }}" method="{{ td.form.method | default('POST')}}">
                                    {{
                                        onsButton({
                                            "text": td.form.button.text,
                                            "id": td.form.button.id if td.form.button.id,
                                            "classes": td.form.button.classes if td.form.button.classes,
                                            "url": td.form.button.url if td.form.button.url,
                                            "value": td.form.button.value | safe if td.form.button.value,
                                            "name": td.form.button.name if td.form.button.name
                                        })
                                    }}
                                    {% if td.form.hiddenFormField is defined and td.form.hiddenFormField %}
                                        {% for hiddenField in td.form.hiddenFormField %}
                                            <input type="hidden" {% if hiddenField.name is defined and hiddenField.name %} name="{{ hiddenField.name }}"{% endif %} {% if hiddenField.value is defined and hiddenField.value %} value="{{ hiddenField.value }}"{% endif %} />
                                        {% endfor %}
                                    {% endif %}
                                </form>
                            {% endif %}
                            {% if td.value is defined and td.value %}
                                {{ td.value | safe }}
                            {% endif %}
                        </td>
                        {% endfor %}
                    </tr>
                    {% endfor %}
                </tbody>
                {% if params.tfoot is defined and params.tfoot %}
                <tfoot class="table__foot">
                    <tr class="table__row">
                        {% for tfootCell in params.tfoot %}
                        <td class="table__cell u-fs-s">{{ tfootCell.value }}</td>
                        {% endfor %}
                    </tr>
                </tfoot>
                {% endif %}
            </table>
        {% if params.scrollable is defined and params.scrollable %}
        </div>
    </div>
    {% endif %}
{% endmacro %}

.table {
  border-collapse: collapse;
  border-spacing: 0;
  margin-bottom: 1rem;
  width: 100%;
  &__caption {
    font-weight: 700;
    text-align: left;
  }
  &__header,
  &__cell {
    @include nth-element(1, 0);
    border-bottom: 2px solid $color-grey-100;
    overflow: hidden;
    padding: 0.5rem 0 0.5rem 1rem;
    text-align: left;
    vertical-align: top;
    &--numeric {
      text-align: right;
    }
  }
  &__cell,
  &__header--row {
    border-bottom: 1px solid $color-borders;
  }
  &__row--highlight {
    background: $color-highlight;
  }
  &:not(.table--responsive) .table__body .table__row:last-child {
    .table__cell,
    .table__header--row {
      border: 0;
    }
  }
  &__foot .table__cell {
    border-bottom: 0;
    border-top: 1px solid $color-borders;
  }
  &--dense {
    .table__head,
    .table__body,
    .table__foot {
      font-size: 81.25%;
    }
  }
  &--row-hover {
    .table__body .table__row:hover {
      background: $color-grey-5;
    }
  }
  &--responsive {
    @include mq(xxs, s) {
      .table__header {
        display: none;
      }
      .table__body .table__row {
        border-bottom: 2px solid $color-grey-100;
        display: block;
        margin-bottom: 1rem;
      }
      .table__cell {
        display: block;
        padding-left: 0;
        text-align: right;
        &:last-child {
          border: 0;
        }
        &::before {
          content: attr(data-th);
          float: left;
          font-weight: 700;
          padding-right: 1rem;
        }
      }
    }
  }
}
.table-scrollable {
  position: relative;
  ::-webkit-scrollbar {
    height: 7px;
  }
  ::-webkit-scrollbar-thumb {
    background: $color-grey-75;
    border-radius: 20px;
  }
  &--on {
    .table__header,
    .table__cell {
      white-space: nowrap;
    }
  }
  &__content {
    overflow: visible;
    overflow-x: scroll;
    width: 100%;
    &:focus {
      outline: 3px solid $color-focus;
      outline-offset: 3px;
    }
    .table__header,
    .table__cell {
      @include mq(xxs, m) {
        white-space: nowrap;
      }
    }
    .table__right-shadow,
    .table__left-shadow {
      height: 100%;
      position: absolute;
      top: 0;
      width: 5px;
      z-index: 200;
      &.with-transition {
        transition: box-shadow 0.4s ease-out;
      }
    }
    .table__right-shadow {
      right: 0;
      &.visible {
        box-shadow: inset -1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
    .table__left-shadow {
      left: 0;
      &.visible {
        box-shadow: inset 1px 0 0 0 #bfc1c3, inset -5px 0 0 0 rgba(191, 193, 195, 0.4);
      }
    }
  }
}
.table--sortable {
  [aria-sort='descending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-grey-35;
      }
      .bottomTriangle {
        fill: $color-ocean-blue;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .topTriangle {
          fill: $color-black;
        }
      }
    }
  }
  [aria-sort='ascending'].table__header {
    .svg-icon {
      .topTriangle {
        fill: $color-ocean-blue;
      }
      .bottomTriangle {
        fill: $color-grey-35;
      }
    }
    .table__sort-button:focus {
      .svg-icon {
        .bottomTriangle {
          fill: $color-black;
        }
      }
    }
  }
  .table__header {
    position: relative;
    .table__sort-button {
      background-color: transparent;
      border: 0;
      box-shadow: none;
      color: $color-text-link;
      display: inline-block;
      font-family: $font-sans;
      font-weight: 700;
      line-height: 1rem;
      text-decoration: underline;
      white-space: nowrap;
      &:hover {
        color: $color-text-link-hover;
        cursor: pointer;
        text-decoration: underline solid $color-text-link-hover 2px;
      }
      .svg-icon {
        fill: $color-grey-35;
        height: 0.8rem;
        padding-bottom: 0.1rem;
        width: 0.8rem;
      }
      &:focus {
        @extend a:focus;
        .svg-icon {
          fill: $color-black;
        }
      }
    }
  }
}

  • Class - table--sortable
  • Output - Applies JS enhancement to allow each column to be sorted. By default it will sort alphabetically, an optional data-sort-value=foo can be added to each td to control it’s position for sorting.

Help improve this component

Let us know how we could improve this component or share your user research findings.

Discuss the ‘Table’ component on GitHub