Use a table to make it easier for users to compare and scan information.
Basic tables
2 column table
<table class="nhsuk-table">
<caption class="nhsuk-table__caption">Skin symptoms and possible causes</caption>
<thead role="rowgroup" class="nhsuk-table__head">
<tr role="row">
<th role="columnheader" class="" scope="col">
Skin symptoms
</th>
<th role="columnheader" class="" scope="col">
Possible cause
</th>
</tr>
</thead>
<tbody class="nhsuk-table__body">
<tr role="row" class="nhsuk-table__row">
<td class="nhsuk-table__cell">Blisters on lips or around the mouth</td>
<td class="nhsuk-table__cell ">Cold sores</td>
</tr>
<tr role="row" class="nhsuk-table__row">
<td class="nhsuk-table__cell">Itchy, dry, cracked, sore</td>
<td class="nhsuk-table__cell ">Eczema</td>
</tr>
<tr role="row" class="nhsuk-table__row">
<td class="nhsuk-table__cell">Itchy blisters</td>
<td class="nhsuk-table__cell ">Shingles, chickenpox</td>
</tr>
</tbody>
</table>
Nunjucks macro options
Use options to customise the appearance, content and behaviour of a component when using a macro, for example, changing the text.
Some options are required for the macro to work; these are marked as "Required" in the option description.
If you're using Nunjucks macros in production with "html" options, or ones ending with "html", you must sanitise the HTML to protect against cross-site scripting exploits.
Name | Type | Required | Description |
---|---|---|---|
rows | array | true | Array of table rows and cells. |
rows[].text | string | true | If `html` is set, this is not required. Text for cells in table rows. If `html` is provided, the `text` argument will be ignored. |
rows[].html | string | true | If `text` is set, this is not required. HTML for cells in table rows. If `html` is provided, the `text` argument will be ignored. |
rows[].format | string | false | Specify format of a cell. Currently we only use "numeric". |
rows[].colspan | integer | false | Specify how many columns a cell extends. |
rows[].rowspan | integer | false | Specify how many rows a cell extends. |
head | array | false | Array of table head cells. |
head[].text | string | false | If `html` is set, this is not required. Text for table head cells. If `html` is provided, the `text` argument will be ignored. |
head[].html | string | false | If `text` is set, this is not required. HTML for table head cells. If `html` is provided, the `text` argument will be ignored. |
head[].format | string | false | Specify format of a cell. Currently we only use "numeric". |
head[].colspan | integer | false | Specify how many columns a cell extends. |
head[].rowspan | integer | false | Specify how many rows a cell extends. |
heading | string | false | Heading/label of the panel if the panel argument is set to true. |
headingLevel | integer | false | Optional heading level for the heading. Default: 3. |
caption | string | false | Caption text. |
captionClasses | string | false | Classes for caption text size. Classes to add to the table caption, for example `nhsuk-table__caption--l`. |
firstCellIsHeader | boolean | false | If set to true, first cell in table row will be a TH instead of a TD. |
responsive | boolean | false | If set to true, responsive table classes will be applied. |
tableClasses | string | false | Classes to add to the table container. |
attributes | object | false | HTML attributes (for example data attributes) to add to the table container. |
{% from 'tables/macro.njk' import table %}
{{ table({
panel: false,
caption: "Skin symptoms and possible causes",
firstCellIsHeader: false,
head: [
{
text: "Skin symptoms"
},
{
text: "Possible cause"
}
],
rows: [
[
{
text: "Blisters on lips or around the mouth"
},
{
text: "Cold sores"
}
],
[
{
text: "Itchy, dry, cracked, sore"
},
{
text: "Eczema"
}
],
[
{
text: "Itchy blisters"
},
{
text: "Shingles, chickenpox"
}
]
]
}) }}
3 or more column table
If you have numeric data, right align the column header and cells to make it easier to compare numbers.
<table class="nhsuk-table">
<caption class="nhsuk-table__caption">Prescription prepayment certificate (PPC) charges</caption>
<thead role="rowgroup" class="nhsuk-table__head">
<tr role="row">
<th role="columnheader" class="" scope="col">
Item
</th>
<th role="columnheader" class=" nhsuk-table__header--numeric" scope="col">
Current charge
</th>
<th role="columnheader" class=" nhsuk-table__header--numeric" scope="col">
New charge
</th>
</tr>
</thead>
<tbody class="nhsuk-table__body">
<tr role="row" class="nhsuk-table__row">
<th class="nhsuk-table__header" scope="row">3-month</th>
<td class="nhsuk-table__cell nhsuk-table__cell--numeric">£31.25</td>
<td class="nhsuk-table__cell nhsuk-table__cell--numeric">£32.05</td>
</tr>
<tr role="row" class="nhsuk-table__row">
<th class="nhsuk-table__header" scope="row">12-month</th>
<td class="nhsuk-table__cell nhsuk-table__cell--numeric">£111.60</td>
<td class="nhsuk-table__cell nhsuk-table__cell--numeric">£114.50</td>
</tr>
<tr role="row" class="nhsuk-table__row">
<th class="nhsuk-table__header" scope="row">HRT</th>
<td class="nhsuk-table__cell nhsuk-table__cell--numeric">£19.30</td>
<td class="nhsuk-table__cell nhsuk-table__cell--numeric">£19.80</td>
</tr>
</tbody>
</table>
Nunjucks macro options
Use options to customise the appearance, content and behaviour of a component when using a macro, for example, changing the text.
Some options are required for the macro to work; these are marked as "Required" in the option description.
If you're using Nunjucks macros in production with "html" options, or ones ending with "html", you must sanitise the HTML to protect against cross-site scripting exploits.
Name | Type | Required | Description |
---|---|---|---|
rows | array | true | Array of table rows and cells. |
rows[].text | string | true | If `html` is set, this is not required. Text for cells in table rows. If `html` is provided, the `text` argument will be ignored. |
rows[].html | string | true | If `text` is set, this is not required. HTML for cells in table rows. If `html` is provided, the `text` argument will be ignored. |
rows[].format | string | false | Specify format of a cell. Currently we only use "numeric". |
rows[].colspan | integer | false | Specify how many columns a cell extends. |
rows[].rowspan | integer | false | Specify how many rows a cell extends. |
head | array | false | Array of table head cells. |
head[].text | string | false | If `html` is set, this is not required. Text for table head cells. If `html` is provided, the `text` argument will be ignored. |
head[].html | string | false | If `text` is set, this is not required. HTML for table head cells. If `html` is provided, the `text` argument will be ignored. |
head[].format | string | false | Specify format of a cell. Currently we only use "numeric". |
head[].colspan | integer | false | Specify how many columns a cell extends. |
head[].rowspan | integer | false | Specify how many rows a cell extends. |
heading | string | false | Heading/label of the panel if the panel argument is set to true. |
headingLevel | integer | false | Optional heading level for the heading. Default: 3. |
caption | string | false | Caption text. |
captionClasses | string | false | Classes for caption text size. Classes to add to the table caption, for example `nhsuk-table__caption--l`. |
firstCellIsHeader | boolean | false | If set to true, first cell in table row will be a TH instead of a TD. |
responsive | boolean | false | If set to true, responsive table classes will be applied. |
tableClasses | string | false | Classes to add to the table container. |
attributes | object | false | HTML attributes (for example data attributes) to add to the table container. |
{% from 'tables/macro.njk' import table %}
{{ table({
responsive: false,
panel: false,
caption: "Prescription prepayment certificate (PPC) charges",
firstCellIsHeader: true,
head: [
{
text: "Item"
},
{
text: "Current charge",
format: "numeric"
},
{
text: "New charge",
format: "numeric"
}
],
rows: [
[
{
text: "3-month"
},
{
text: "£31.25",
format: "numeric"
},
{
text: "£32.05",
format: "numeric"
}
],
[
{
text: "12-month"
},
{
text: "£111.60",
format: "numeric"
},
{
text: "£114.50",
format: "numeric"
}
],
[
{
text: "HRT"
},
{
text: "£19.30",
format: "numeric"
},
{
text: "£19.80",
format: "numeric"
}
]
]
}) }}
When not to use a basic table
Do not use a basic table:
- to lay out content on a page – use the grid system instead
- if your table content becomes squashed and hard to read on small screens – use a responsive table instead
Responsive table
This table has a responsive layout. On large screens it displays as a 3 column table. However, on small screens it stacks vertically.
Use a responsive table when your table becomes squashed and hard to read on small screens, 768px and smaller.
<table role="table" class="nhsuk-table-responsive">
<caption class="nhsuk-table__caption">Ibuprofen liquid dosages for children</caption>
<thead role="rowgroup" class="nhsuk-table__head">
<tr role="row">
<th role="columnheader" class="" scope="col">
Age
</th>
<th role="columnheader" class="" scope="col">
How much
</th>
<th role="columnheader" class="" scope="col">
How often
</th>
</tr>
</thead>
<tbody class="nhsuk-table__body">
<tr role="row" class="nhsuk-table__row">
<td role="cell" class="nhsuk-table__cell">
<span class="nhsuk-table-responsive__heading" aria-hidden="true">Age </span>4 to 6 years
</td>
<td role="cell" class="nhsuk-table__cell">
<span class="nhsuk-table-responsive__heading" aria-hidden="true">How much </span>7.5ml (150mg)
</td>
<td role="cell" class="nhsuk-table__cell">
<span class="nhsuk-table-responsive__heading" aria-hidden="true">How often </span>Max 3 times in 24 hours
</td>
</tr>
<tr role="row" class="nhsuk-table__row">
<td role="cell" class="nhsuk-table__cell">
<span class="nhsuk-table-responsive__heading" aria-hidden="true">Age </span>7 to 9 years
</td>
<td role="cell" class="nhsuk-table__cell">
<span class="nhsuk-table-responsive__heading" aria-hidden="true">How much </span>10ml (200mg)
</td>
<td role="cell" class="nhsuk-table__cell">
<span class="nhsuk-table-responsive__heading" aria-hidden="true">How often </span>Max 3 times in 24 hours
</td>
</tr>
<tr role="row" class="nhsuk-table__row">
<td role="cell" class="nhsuk-table__cell">
<span class="nhsuk-table-responsive__heading" aria-hidden="true">Age </span>10 to 11 years
</td>
<td role="cell" class="nhsuk-table__cell">
<span class="nhsuk-table-responsive__heading" aria-hidden="true">How much </span>15ml (300mg)
</td>
<td role="cell" class="nhsuk-table__cell">
<span class="nhsuk-table-responsive__heading" aria-hidden="true">How often </span>Max 3 times in 24 hours
</td>
</tr>
</tbody>
</table>
Nunjucks macro options
Use options to customise the appearance, content and behaviour of a component when using a macro, for example, changing the text.
Some options are required for the macro to work; these are marked as "Required" in the option description.
If you're using Nunjucks macros in production with "html" options, or ones ending with "html", you must sanitise the HTML to protect against cross-site scripting exploits.
Name | Type | Required | Description |
---|---|---|---|
rows | array | true | Array of table rows and cells. |
rows[].text | string | true | If `html` is set, this is not required. Text for cells in table rows. If `html` is provided, the `text` argument will be ignored. |
rows[].html | string | true | If `text` is set, this is not required. HTML for cells in table rows. If `html` is provided, the `text` argument will be ignored. |
rows[].format | string | false | Specify format of a cell. Currently we only use "numeric". |
rows[].colspan | integer | false | Specify how many columns a cell extends. |
rows[].rowspan | integer | false | Specify how many rows a cell extends. |
head | array | false | Array of table head cells. |
head[].text | string | false | If `html` is set, this is not required. Text for table head cells. If `html` is provided, the `text` argument will be ignored. |
head[].html | string | false | If `text` is set, this is not required. HTML for table head cells. If `html` is provided, the `text` argument will be ignored. |
head[].format | string | false | Specify format of a cell. Currently we only use "numeric". |
head[].colspan | integer | false | Specify how many columns a cell extends. |
head[].rowspan | integer | false | Specify how many rows a cell extends. |
heading | string | false | Heading/label of the panel if the panel argument is set to true. |
headingLevel | integer | false | Optional heading level for the heading. Default: 3. |
caption | string | false | Caption text. |
captionClasses | string | false | Classes for caption text size. Classes to add to the table caption, for example `nhsuk-table__caption--l`. |
firstCellIsHeader | boolean | false | If set to true, first cell in table row will be a TH instead of a TD. |
responsive | boolean | false | If set to true, responsive table classes will be applied. |
tableClasses | string | false | Classes to add to the table container. |
attributes | object | false | HTML attributes (for example data attributes) to add to the table container. |
{% from 'tables/macro.njk' import table %}
{{ table({
responsive: true,
panel: false,
caption: "Ibuprofen liquid dosages for children",
firstCellIsHeader: true,
head: [
{
text: "Age"
},
{
text: "How much"
},
{
text: "How often"
}
],
rows: [
[
{
header: "Age",
text: "4 to 6 years"
},
{
header: "How much",
text: "7.5ml (150mg)"
},
{
header: "How often",
text: "Max 3 times in 24 hours"
}
],
[
{
header: "Age",
text: "7 to 9 years"
},
{
header: "How much",
text: "10ml (200mg)"
},
{
header: "How often",
text: "Max 3 times in 24 hours"
}
],
[
{
header: "Age",
text: "10 to 11 years"
},
{
header: "How much",
text: "15ml (300mg)"
},
{
header: "How often",
text: "Max 3 times in 24 hours"
}
]
]
}) }}
When not to use a responsive table
Do not use a responsive table:
- to lay out content on a page – use the grid system instead
- if your content is easier to read and understand on a small screen in a non-responsive layout – use a basic table instead
How tables work
Accessibility
Follow WebAIM's guidance for tables and:
- give tables captions
- use the scope attribute to associate the data cells with the appropriate headers
- let the browser window determine the width of the table whenever possible, to reduce horizontal scrolling
Table captions
Use the <caption>
element to describe a table in the same way you would use a heading. A caption helps users find, navigate and understand tables.
Table headers
Use table headers to tell users what the rows and columns represent.
Research
Basic tables
These tables tested well with users of health information on the NHS website.
Responsive table
This table was tested at HM Revenue & Customs.
Table with panel
We have also designed a table with a panel. You can find an example in the frontend library. We have not included it in this list of components yet because it needs more testing.
Help us improve this guidance
Share insights or feedback and take part in the discussion. We use GitHub as a collaboration space. All the information on it is open to the public.
Read more about how to feedback or share insights.
If you have any questions, get in touch with the service manual team.
Updated: July 2024