Table
Component status:Ready
A table is used to display tabular data in rows and columns.
A table is used to display repeating data with the same structure efficiently across rows and columns so that it can be easily scanned and compared. Tables should never take the place of columns within a page layout.
Examples
Default
Column 1 | Column 2 | Column 3 |
---|---|---|
Cell data A1 | Cell data B1 | Cell data C1 |
Cell data B1 | Cell data B2 | Cell data B3 |
Cell data C1 | Cell data C2 | Cell data C3 |
Some info about this default table.
<table class="bux-table" aria-describedby="summary-default">
<caption>
Default Table
</caption>
<thead>
<tr>
<th scope="col">Column 1</th>
<th scope="col">Column 2</th>
<th scope="col">Column 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell data A1</td>
<td>Cell data B1</td>
<td>Cell data C1</td>
</tr>
<tr>
<td>Cell data B1</td>
<td>Cell data B2</td>
<td>Cell data B3</td>
</tr>
<tr>
<td>Cell data C1</td>
<td>Cell data C2</td>
<td>Cell data C3</td>
</tr>
</tbody>
</table>
<div class="bux-table bux-table--announcement-region" aria-live="polite"></div>
<p id="summary-default" class="visually-hidden">
Some info about this default table.
</p>
{#
Buckeye UX - version 1.0.15
Copyright (C) 2024 The Ohio State University
#}
{#
Table
Available variables:
- striped: Set to true for a striped table.
- compact: Set to true for a compact table.
- sortable: Set to true for a sortable table.
- summary: Content of the table summary. Optional.
- summaryId: Unique ID for the summary.
- caption: Content of the table's caption.
- header_columns: Array of column headers.
- row_header: Set to true for row headers as well as column headers.
- rows: Array of table rows.
- rows.cells: Array of row cells.
#}
{% set classes_array = ["bux-table"] %}
{% if striped is defined %}
{% set classes_array = classes_array|merge(["bux-table--striped"]) %}
{% endif %}
{% if compact is defined %}
{% set classes_array = classes_array|merge(["bux-table--compact"]) %}
{% endif %}
{% if sortable is defined %}
{% set classes_array = classes_array|merge(["bux-table--sortable"]) %}
{% endif %}
{% set table_classes = classes_array|join(" ") %}
<table class="{{ table_classes }}" {% if summary %} aria-describedby="{{ summaryId }}" {% endif %}>
<caption>{{ caption }}</caption>
<thead>
<tr>
{% for col in header_columns %}
{% if sortable %}
<th scope="col" class="sort-by"><button aria-pressed="false">{{ col }}</button></th>
{% else %}
<th scope="col">{{ col }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
{% for cell in row.cells %}
{% if row_header and loop.first %}
<th scope="row">{{ cell }}</th>
{% else %}
<td>{{ cell }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<div class="{{ table_classes }} bux-table--announcement-region" aria-live="polite"></div>
{% if summary %}
<p id="{{ summaryId }}" class="visually-hidden"> {{ summary }}</p>
{% endif %}
Striped Table
Column 1 | Column 2 | Column 3 |
---|---|---|
Cell data A1 | Cell data B1 | Cell data C1 |
Cell data B1 | Cell data B2 | Cell data B3 |
Cell data C1 | Cell data C2 | Cell data C3 |
Some info about this striped table.
<table class="bux-table bux-table--striped" aria-describedby="summary-striped">
<caption>
Striped Table
</caption>
<thead>
<tr>
<th scope="col">Column 1</th>
<th scope="col">Column 2</th>
<th scope="col">Column 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell data A1</td>
<td>Cell data B1</td>
<td>Cell data C1</td>
</tr>
<tr>
<td>Cell data B1</td>
<td>Cell data B2</td>
<td>Cell data B3</td>
</tr>
<tr>
<td>Cell data C1</td>
<td>Cell data C2</td>
<td>Cell data C3</td>
</tr>
</tbody>
</table>
<div
class="bux-table bux-table--striped bux-table--announcement-region"
aria-live="polite"
></div>
<p id="summary-striped" class="visually-hidden">
Some info about this striped table.
</p>
{#
Buckeye UX - version 1.0.15
Copyright (C) 2024 The Ohio State University
#}
{#
Table
Available variables:
- striped: Set to true for a striped table.
- compact: Set to true for a compact table.
- sortable: Set to true for a sortable table.
- summary: Content of the table summary. Optional.
- summaryId: Unique ID for the summary.
- caption: Content of the table's caption.
- header_columns: Array of column headers.
- row_header: Set to true for row headers as well as column headers.
- rows: Array of table rows.
- rows.cells: Array of row cells.
#}
{% set classes_array = ["bux-table"] %}
{% if striped is defined %}
{% set classes_array = classes_array|merge(["bux-table--striped"]) %}
{% endif %}
{% if compact is defined %}
{% set classes_array = classes_array|merge(["bux-table--compact"]) %}
{% endif %}
{% if sortable is defined %}
{% set classes_array = classes_array|merge(["bux-table--sortable"]) %}
{% endif %}
{% set table_classes = classes_array|join(" ") %}
<table class="{{ table_classes }}" {% if summary %} aria-describedby="{{ summaryId }}" {% endif %}>
<caption>{{ caption }}</caption>
<thead>
<tr>
{% for col in header_columns %}
{% if sortable %}
<th scope="col" class="sort-by"><button aria-pressed="false">{{ col }}</button></th>
{% else %}
<th scope="col">{{ col }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
{% for cell in row.cells %}
{% if row_header and loop.first %}
<th scope="row">{{ cell }}</th>
{% else %}
<td>{{ cell }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<div class="{{ table_classes }} bux-table--announcement-region" aria-live="polite"></div>
{% if summary %}
<p id="{{ summaryId }}" class="visually-hidden"> {{ summary }}</p>
{% endif %}
Compact Table
Column 1 | Column 2 | Column 3 |
---|---|---|
Cell data A1 | Cell data B1 | Cell data C1 |
Cell data B1 | Cell data B2 | Cell data B3 |
Cell data C1 | Cell data C2 | Cell data C3 |
Some info about this compact table.
<table class="bux-table bux-table--compact" aria-describedby="summary-compact">
<caption>
Compact Table
</caption>
<thead>
<tr>
<th scope="col">Column 1</th>
<th scope="col">Column 2</th>
<th scope="col">Column 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell data A1</td>
<td>Cell data B1</td>
<td>Cell data C1</td>
</tr>
<tr>
<td>Cell data B1</td>
<td>Cell data B2</td>
<td>Cell data B3</td>
</tr>
<tr>
<td>Cell data C1</td>
<td>Cell data C2</td>
<td>Cell data C3</td>
</tr>
</tbody>
</table>
<div
class="bux-table bux-table--compact bux-table--announcement-region"
aria-live="polite"
></div>
<p id="summary-compact" class="visually-hidden">
Some info about this compact table.
</p>
{#
Buckeye UX - version 1.0.15
Copyright (C) 2024 The Ohio State University
#}
{#
Table
Available variables:
- striped: Set to true for a striped table.
- compact: Set to true for a compact table.
- sortable: Set to true for a sortable table.
- summary: Content of the table summary. Optional.
- summaryId: Unique ID for the summary.
- caption: Content of the table's caption.
- header_columns: Array of column headers.
- row_header: Set to true for row headers as well as column headers.
- rows: Array of table rows.
- rows.cells: Array of row cells.
#}
{% set classes_array = ["bux-table"] %}
{% if striped is defined %}
{% set classes_array = classes_array|merge(["bux-table--striped"]) %}
{% endif %}
{% if compact is defined %}
{% set classes_array = classes_array|merge(["bux-table--compact"]) %}
{% endif %}
{% if sortable is defined %}
{% set classes_array = classes_array|merge(["bux-table--sortable"]) %}
{% endif %}
{% set table_classes = classes_array|join(" ") %}
<table class="{{ table_classes }}" {% if summary %} aria-describedby="{{ summaryId }}" {% endif %}>
<caption>{{ caption }}</caption>
<thead>
<tr>
{% for col in header_columns %}
{% if sortable %}
<th scope="col" class="sort-by"><button aria-pressed="false">{{ col }}</button></th>
{% else %}
<th scope="col">{{ col }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
{% for cell in row.cells %}
{% if row_header and loop.first %}
<th scope="row">{{ cell }}</th>
{% else %}
<td>{{ cell }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<div class="{{ table_classes }} bux-table--announcement-region" aria-live="polite"></div>
{% if summary %}
<p id="{{ summaryId }}" class="visually-hidden"> {{ summary }}</p>
{% endif %}
Table with column and row headers
Column 1 | Column 2 | Column 3 |
---|---|---|
Row A | Cell data B1 | Cell data C1 |
Row B | Cell data B2 | Cell data B3 |
Row C | Cell data C2 | Cell data C3 |
Some info about this table with two headers.
<table class="bux-table" aria-describedby="summary-two-headers">
<caption>
Table with column and row headers
</caption>
<thead>
<tr>
<th scope="col">Column 1</th>
<th scope="col">Column 2</th>
<th scope="col">Column 3</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Row A</th>
<td>Cell data B1</td>
<td>Cell data C1</td>
</tr>
<tr>
<th scope="row">Row B</th>
<td>Cell data B2</td>
<td>Cell data B3</td>
</tr>
<tr>
<th scope="row">Row C</th>
<td>Cell data C2</td>
<td>Cell data C3</td>
</tr>
</tbody>
</table>
<div class="bux-table bux-table--announcement-region" aria-live="polite"></div>
<p id="summary-two-headers" class="visually-hidden">
Some info about this table with two headers.
</p>
{#
Buckeye UX - version 1.0.15
Copyright (C) 2024 The Ohio State University
#}
{#
Table
Available variables:
- striped: Set to true for a striped table.
- compact: Set to true for a compact table.
- sortable: Set to true for a sortable table.
- summary: Content of the table summary. Optional.
- summaryId: Unique ID for the summary.
- caption: Content of the table's caption.
- header_columns: Array of column headers.
- row_header: Set to true for row headers as well as column headers.
- rows: Array of table rows.
- rows.cells: Array of row cells.
#}
{% set classes_array = ["bux-table"] %}
{% if striped is defined %}
{% set classes_array = classes_array|merge(["bux-table--striped"]) %}
{% endif %}
{% if compact is defined %}
{% set classes_array = classes_array|merge(["bux-table--compact"]) %}
{% endif %}
{% if sortable is defined %}
{% set classes_array = classes_array|merge(["bux-table--sortable"]) %}
{% endif %}
{% set table_classes = classes_array|join(" ") %}
<table class="{{ table_classes }}" {% if summary %} aria-describedby="{{ summaryId }}" {% endif %}>
<caption>{{ caption }}</caption>
<thead>
<tr>
{% for col in header_columns %}
{% if sortable %}
<th scope="col" class="sort-by"><button aria-pressed="false">{{ col }}</button></th>
{% else %}
<th scope="col">{{ col }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
{% for cell in row.cells %}
{% if row_header and loop.first %}
<th scope="row">{{ cell }}</th>
{% else %}
<td>{{ cell }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<div class="{{ table_classes }} bux-table--announcement-region" aria-live="polite"></div>
{% if summary %}
<p id="{{ summaryId }}" class="visually-hidden"> {{ summary }}</p>
{% endif %}
Sortable Table
Cell data A1 | Cell data B1 | Cell data C1 |
Cell data B1 | Cell data B2 | Cell data B3 |
Cell data C1 | Cell data C2 | Cell data C3 |
Some info about this sortable table.
<table
class="bux-table bux-table--sortable"
aria-describedby="summary-sortable"
>
<caption>
Sortable Table
</caption>
<thead>
<tr>
<th scope="col" class="sort-by">
<button aria-pressed="false">Column 1</button>
</th>
<th scope="col" class="sort-by">
<button aria-pressed="false">Column 2</button>
</th>
<th scope="col" class="sort-by">
<button aria-pressed="false">Column 3</button>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell data A1</td>
<td>Cell data B1</td>
<td>Cell data C1</td>
</tr>
<tr>
<td>Cell data B1</td>
<td>Cell data B2</td>
<td>Cell data B3</td>
</tr>
<tr>
<td>Cell data C1</td>
<td>Cell data C2</td>
<td>Cell data C3</td>
</tr>
</tbody>
</table>
<div
class="bux-table bux-table--sortable bux-table--announcement-region"
aria-live="polite"
></div>
<p id="summary-sortable" class="visually-hidden">
Some info about this sortable table.
</p>
{#
Buckeye UX - version 1.0.15
Copyright (C) 2024 The Ohio State University
#}
{#
Table
Available variables:
- striped: Set to true for a striped table.
- compact: Set to true for a compact table.
- sortable: Set to true for a sortable table.
- summary: Content of the table summary. Optional.
- summaryId: Unique ID for the summary.
- caption: Content of the table's caption.
- header_columns: Array of column headers.
- row_header: Set to true for row headers as well as column headers.
- rows: Array of table rows.
- rows.cells: Array of row cells.
#}
{% set classes_array = ["bux-table"] %}
{% if striped is defined %}
{% set classes_array = classes_array|merge(["bux-table--striped"]) %}
{% endif %}
{% if compact is defined %}
{% set classes_array = classes_array|merge(["bux-table--compact"]) %}
{% endif %}
{% if sortable is defined %}
{% set classes_array = classes_array|merge(["bux-table--sortable"]) %}
{% endif %}
{% set table_classes = classes_array|join(" ") %}
<table class="{{ table_classes }}" {% if summary %} aria-describedby="{{ summaryId }}" {% endif %}>
<caption>{{ caption }}</caption>
<thead>
<tr>
{% for col in header_columns %}
{% if sortable %}
<th scope="col" class="sort-by"><button aria-pressed="false">{{ col }}</button></th>
{% else %}
<th scope="col">{{ col }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
{% for cell in row.cells %}
{% if row_header and loop.first %}
<th scope="row">{{ cell }}</th>
{% else %}
<td>{{ cell }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<div class="{{ table_classes }} bux-table--announcement-region" aria-live="polite"></div>
{% if summary %}
<p id="{{ summaryId }}" class="visually-hidden"> {{ summary }}</p>
{% endif %}
Usage
Dos
- Use a table for complex tabular data that needs to be scanned and/or compared
- Use the simplest configuration possible
Don’ts
- Don’t use if page structure is needed, reference the Grid
- Don’t use if it is a list with no comparisons, use a List Component
- Don’t use if the data set is small and only has a title and description, use a Description List
- Don’t use to control the layout of text on a page that is not tabular data
Implementation Notes
Table variants can be combined by adding multiple modifier classes. The following example code will produce a striped table that is also compact.
<table class="bux-table bux-table--striped bux-table--compact"></table>
Accessibility
- Give your table a meaningful caption. Change the default value of the caption element to text that clearly describes the type of data being presented by the table.
- Complex tables have more than two levels of headers. Each header should have a unique id and each data cell should have a headers attribute with each related header cell’s id listed.
- Add an aria-live region to the page when enabling row sorting. An aria-live region immediately following the
<table>
element automatically announces when the sort state changes for visitors using screen readers, but it must be added to the HTML document before load. - Don’t add aria-label attributes to sortable column headers. Enabling row sorting automatically adds aria-label attributes to the sortable column headers and their toggle sort buttons via JavaScript. These labels are updated to reflect each column’s current sort state (ascending, descending, or unsorted) whenever sort changes.
- Scrollable tables need to be focusable. When you use the .usa-table-container--scrollable variant with a table, you must add the
tabindex="0"
attribute to the scrollable element. This attribute assures that users navigating with a keyboard are able to select and scroll the table.tabindex="0"
enables focus on elements that do not get focus by default. This attribute does not change the tab order. It places the element in the logical navigation flow. - Web Accessibility in Mind Table Details