Styling for tables.
Check out how to include Origami components in your project to get started with o-table
.
Add an o-table
class to any table you wish to apply the styles to:
<table class="o-table" data-o-component="o-table">
...
</table>
Where table headings (th
) are used as row headings, scope="row"
attributes must be set on the th
:
<table class="o-table" data-o-component="o-table">
<tbody>
<tr>
<th scope="row" role="rowheader">Item</th>
<td>Holiday</td>
<td>Lunch</td>
</tr>
<tr>
<th scope="row" role="rowheader">Cost</th>
<td>£123.45</td>
<td>£7</td>
</tr>
</tbody>
...
</table>
The table's caption
element should include a header of the appropriate level and style for the table's context.
<table class="o-table" data-o-component="o-table">
<caption class="o-table__caption">
<h2>My Table Caption</h2>
</caption>
<thead>
...
</thead>
<tbody>
...
</tbody>
...
</table>
The table's footer tfoot
element may use the helper class o-table-footnote
to display sources, disclaimers, etc.
<table class="o-table" data-o-component="o-table">
<thead>
...
</thead>
<tbody>
...
</tbody>
<tfoot>
<tr>
<td colspan=2 class="o-table-footnote">
Source: The Origami team.
</td>
</tr>
</tfoot>
</table>
When a sortable table column is clicked an ascending sort is applied by default. If clicked again the sort order is toggled to a descending sort. Set the preferred sort order attribute data-o-table-preferred-sort-order="descending"
to inverse this, so a descending sort is applied on the first click.
<table class="o-table" data-o-component="o-table" data-o-table-preferred-sort-order="descending">
</table>
Table columns are sortable by default but may be disabled by adding data-o-table-sortable="false"
to the table.
<table class="o-table" data-o-component="o-table" data-o-table-sortable="false">
</table>
Or to disable sort per table column, add data-o-table-heading-disable-sort
to the column's th
element.
<table class="o-table" data-o-component="o-table">
<thead>
<tr>
<th>Heading One</th>
<!-- do not show the actions column as sortable -->
<th data-o-table-heading-disable-sort>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>Item One</td>
<td><a href="#edit">edit</a></td>
</tr>
</tbody>
</table>
There are three options for small viewports where the table does not fit.
To enable these set data-o-table-responsive
to the type of responsive table desired and add the classes for that type of table. Then wrap the table in o-table-container
, o-table-overlay-wrapper
, o-table-scroll-wrapper
. E.g for an "overflow" table:
<div class="o-table-container">
<div class="o-table-overlay-wrapper">
<div class="o-table-scroll-wrapper">
<table class="o-table o-table--horizontal-lines o-table--responsive-overflow"
data-o-component="o-table"
data-o-table-responsive="overflow">
...
</table>
</div>
</div>
</div>
If your project does not use the build service, you may need to specify an extra Sass option for responsive features and initialise o-table JavaScript.
More examples are available in the registry.
The "overflow" style of responsive table (see above) supports an expander to hide rows and offer a "show more" / "show fewer" button. To enable this feature set data-o-table-expanded="false"
to the table. The number of rows to show when the table is not expanded can be configured with data-o-table-minimum-row-count="20"
(default: 20).
<div class="o-table-container">
<div class="o-table-overlay-wrapper">
<div class="o-table-scroll--wrapper">
<table class="o-table o-table--horizontal-lines o-table--responsive-overflow"
data-o-component="o-table"
data-o-table-responsive="overflow"
data-o-table-expanded="false"
data-o-table-minimum-row-count="10">
...
</table>
</div>
</div>
</div>
To add a footnote to an expandable table, for example with disclaimers or sources, add the footnote within the container and link to the table with an id and the aria-describedby
attribute. If not working on an expandable table, use the tfoot
element instead.
<div class="o-table-container">
<div class="o-table-overlay-wrapper">
<div class="o-table-scroll--wrapper">
+ <table aria-describedby="demo-footnote">
...
</table>
</div>
</div>
+ <div id="demo-footnode" class="o-table-footnote">
+ Source: The Origami team's love of fruit.
+ </div>
</div>
o-table--compact
- Apply to the table for smaller typography and padding.o-table--row-stripes
- Apply to the table for alternating stripes on the table rows.o-table-footnote
- Style a tfoot
element subtily for sources, disclaimers, etc.o-table__cell--numeric
- Apply to numeric cells to align content to the right.o-table__cell--vertically-center
- Apply to cells which should center vertically.See more in the registry: o-table demos.
Use @include oTable()
to include styles for all table features. Alternatively styles may be included granularly with an $opts
map.
Include all table features:
@include oTable();
Alternatively include base styles with only selected optional features. E.g. to include only the "overflow" responsive table and styles for table lines:
@include oTable($opts: (
'responsive-overflow',
'lines'
));
To manually instantiate o-table
:
import OTable from '@financial-times/o-table';
OTable.init();
or
import OTable from '@financial-times/o-table';
oTable = new OTable(document.body);
This will return an instance of BasicTable
(default), OverflowTable
, FlatTable
, or ScrollTable
depending on the value of data-o-table-responsive
. All four table types extend BaseTable
.
Instantiation will add column sorting to all tables. It will also add scroll controls and, if configured, an expander to any OverflowTable
. These can be configured with data attributes or imperatively with an options object:
import OTable from '@financial-times/o-table';
OTable.init(document.body, {
sortable: true,
expanded: true,
preferredSortOrder: 'ascending',
minimumRowCount: 10,
});
All o-table
instances support filtering on a single column. Filters may be applied declaratively in HTML or by calling the o-table
JavaScript method filter
.
The style of form elements used to filter a table are not determined by o-table
. However we recommend using o-forms to style form elements used to filter an o-table
, such as input
or select
elements. See the o-table filter demos in the component registry for a demo using o-forms
styles.
Declarative filters are case insensitive and perform partial matches, e.g. a filter of "Kingdom" would reveal "United Kingdom".
To enable declarative table filtering add the data-o-table-filter-id
and data-o-table-filter-column
to a form input. Where data-o-table-filter-id
matches the id
of the table to filter and data-o-table-filter-column
is the numerical index of the column to filter (starting at 0).
For example, to filter a table based on a users selected option:
<label>Filter the table by country:</label>
<!-- the filter input specifies the table id in "data-o-table-filter-id" -->
<select data-o-table-filter-id="example-table" data-o-table-filter-column="0">
<option value="" selected>All</option>
<option value="Austria">Austria</option>
<option value="Belgium">Belgium</option>
<!-- more options -->
</select>
<!-- the table markup, this may be a responsive table -->
<div class="o-table-container">
<!-- the table element with an id -->
<table id="example-table">
<!-- ... -->
</table>
</div>
Or to filter a table based on a users selected option:
<label>Filter the table by country:</label>
<!-- the filter input specifies the table id in "data-o-table-filter-id" -->
<input type="text" data-o-table-filter-id="example-table" data-o-table-filter-column="0"/>
<!-- the table markup, this may be a responsive table -->
<div class="o-table-container">
<!-- the table element with an id -->
<table id="example-table">
<!-- ... -->
</table>
</div>
The table's filter
method may also be used to filter the table. Call it with the column index to filter and the filter to apply. The filter may be a string, which acts like a declarative filter (i.e. is case insensitive and performs a partial match):
const table = new OTable(tableElement);
table.filter(0, 'United Kingdom'); // Filter the first table column by "United Kingdom".
Alternatively a callback function may be given. The callback should accept a table cell element and return a boolean value:
const table = new OTable(tableElement);
table.filter(0, (cell) => {
return parseInt(cell.textContent, 10) > 3;
}); // Filter the first table column. Keep rows with a value more than 3.
All o-table
instances support sorting. Sorting on non-string values such as numbers works if the column type has been declared. E.g. for a column of numbers add the following to o-table
:
data-o-table-data-type="number"
.
Other data types for data-o-table-data-type
include:
It is possible to add sort support for a custom data type. Alternatively, the behaviour of an existing type may be modified.
If you are wanting to sort by a custom pattern, you can apply the sorting values to each row as a data attribute:
data-o-table-sort-value=${sort-value}
. These values can be strings or integers.
For example to support a custom date format set data-o-table-sort-value
to its UNIX Epoch.
<table class="o-table" data-o-component="o-table">
<thead>
<tr>
<th data-o-table-data-type="date">Custom Date Formats</th>
</tr>
</thead>
<tbody>
<tr>
<td data-o-table-sort-value="1533081600">Wednesday, 1 August 2018</td>
</tr>
<tr>
<td data-o-table-sort-value="1483228800">Jan 2017</td>
</tr>
<tr>
<td data-o-table-sort-value="723168000">1st December 1992</td>
</tr>
</tbody>
</table>
Or to provide an arbitrary sort order:
<table class="o-table" data-o-component="o-table">
<thead>
<tr>
<th>Things</th>
</tr>
</thead>
<tbody>
<tr>
<td data-o-table-sort-value=2>snowman</td>
</tr>
<tr>
<td data-o-table-sort-value=3>42</td>
</tr>
<tr>
<td data-o-table-sort-value=1>pangea</td>
</tr>
</tbody>
</table>
Rather than specify data-o-table-sort-value
declaratively, a formatter function may be provided client-side to generate sort values for a given data type.
For example we could add support for a custom data type emoji-time
.
<table class="o-table" data-o-component="o-table">
<thead>
<tr>
<th data-o-table-data-type="emoji-time">Emoji Time</th>
</tr>
</thead>
<tbody>
<tr>
<td>🌑</td>
</tr>
<tr>
<td>🌤️️</td>
</tr>
<tr>
<td>🌑</td>
</tr>
<tr>
<td>🌤️️</td>
</tr>
</tbody>
</table>
To do that call setSortFormatterForType
with the custom data type and a formatter function.
The formatter accepts the table cell (HTMLElement) and returns a sort value (Number or String) for that cell.
In this case we add support for our custom type emoji-time
by assigning the emoji a numerical sort value. This will effect all tables instantiated by OTable
.
import OTable from '@financial-times/o-table';
// Set a filter for custom data type "emoji-time".
// The return value may be a string or number.
OTable.setSortFormatterForType('emoji-time', (cell) => {
const text = cell.textContent.trim();
if (text === '🌑') {
return 1;
}
if (text === '🌤️️') {
return 2;
}
return 0;
});
OTable.init();
Which for an ascending sort, will result in:
<table class="o-table" data-o-component="o-table">
<thead>
<tr>
<th data-o-table-data-type="emoji-time" aria-sort="ascending">Emoji Time</th>
</tr>
</thead>
<tbody>
<tr>
<td data-o-table-sort-value=1>🌑</td>
</tr>
<tr>
<td data-o-table-sort-value=1>🌑</td>
</tr>
<tr>
<td data-o-table-sort-value=2>🌤️️</td>
</tr>
<tr>
<td data-o-table-sort-value=2>🌤️️</td>
</tr>
</tbody>
</table>
If rows are added or removed dynamically after the table is initialised call updateRows
; this will apply any existing sort or filter to the new rows.
The following events are fired by o-table
.
oTable.ready
oTable.sorting
oTable.sorted
oTable.ready
fires when the table has been initialised.
The event provides the following properties:
detail.instance
- The initialised o-table
instance (FlatTable | ScrollTable | OverflowTable | BasicTable).oTable.sorted
indicates a table has finished sorting. It includes details of the current sort status of the table.
The event provides the following properties:
detail.sortOrder
- The sort order e.g. "ascending" (String).detail.columnIndex
- The index of the sorted column heading (Number).detail.instance
- The effected o-table
instance (FlatTable | ScrollTable | OverflowTable | BasicTable).document.addEventListener('oTable.sorted', (event) => {
console.log(`The target table was just sorted by column "${event.detail.columnIndex}" in an "${event.detail.sortOrder}" order.`);
});
This event is fired just before a table sorts based on user interaction. It may be prevented to implement custom sort functionality. This may be useful to sort a paginated table server-side.
The event provides the following properties:
detail.sort
- The sort requested e.g. "ascending" (String).detail.columnIndex
- The index of the column heading which will be sorted (Number).detail.instance
- The effected o-table
instance (FlatTable | ScrollTable | OverflowTable | BasicTable).When intercepting the default sort the sorted
method must be called with relevant parameters when the custom sort is completed.
document.addEventListener('oTable.sorting', (event) => {
// Prevent default sorting.
event.preventDefault();
// Update the table with a custom sort.
console.log(`Update the table with sorted data here.`);
// Fire the sorted event, passing along the column index and sort.
event.detail.instance.sorted({
columnIndex: event.detail.columnIndex,
sortOrder: event.detail.sort
});
});
o-table
sort events provide a columnIndex
. This index maps to a column heading. To retrieve the column heading use getTableHeader
.
document.addEventListener('oTable.sorting', (event) => {
const table = event.detail.instance;
const columnIndex = event.detail.columnIndex;
// Get the table header from the column index.
console.log(table.getTableHeader(columnIndex));
});
Known issues:
If you have any questions or comments about this component, or need help using it, please either raise an issue, visit #origami-support or email Origami Support.
This software is published by the Financial Times under the MIT licence.