Data Tables

Utility

A set of utility features for creating template-driven data tables.

Examples

ID User Title Body
1
sunt aut facere repellat provident occaecati excepturi optio reprehenderit quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam nostrum rerum est autem sunt rem eveniet architecto
2
qui est esse est rerum tempore vitae sequi sint nihil reprehenderit dolor beatae ea dolores neque fugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis qui aperiam non debitis possimus qui neque nisi nulla
3
ea molestias quasi exercitationem repellat qui ipsa sit aut et iusto sed quo iure voluptatem occaecati omnis eligendi aut ad voluptatem doloribus vel accusantium quis pariatur molestiae porro eius odio et labore et velit aut
4
eum et est occaecati ullam et saepe reiciendis voluptatem adipisci sit amet autem assumenda provident rerum culpa quis hic commodi nesciunt rem tenetur doloremque ipsam iure quis sunt voluptatem rerum illo velit
5
nesciunt quas odio repudiandae veniam quaerat sunt sed alias aut fugiat sit autem sed est voluptatem omnis possimus esse voluptatibus quis est aut tenetur dolor neque

Usage

What are Data Tables?

Within the context of Skeleton, data tables are not a singular feature, but rather a collection of utilities. These utilty features are opt-in, meaning you can progressively enhance any native HTML table to meet your requirements. This is one of the most complex features Skeleton provides, so please read carefully.


Getting Started

Let's start by importing all the utility features we'll need. We'll cover each of these in greater detail below.

ts
import {
	// Types
	type DataTableModel,
	// Utilities
	dataTableHandler,
	dataTableSelect,
	dataTableSelectAll,
	dataTableSort,
	// Svelte Actions
	tableInteraction,
	tableA11y
} from '@skeletonlabs/skeleton';

We need data to populate the table. For simplicity, let's create this locally. In a real world app you might fetch this from an external API.

ts
const sourceData = [
	{ position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
	{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
	{ position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
	{ position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
	{ position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
	{ position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' },
	{ position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
	{ position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
	{ position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
	{ position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' }
];

We'll make use of a few Tailwind Element table classes to provide base styles to our native HTML table element. These are optional, but recommended.

html
<div class="table-container">
	<table class="table table-hover">
		<thead>
			<tr>
				<th>Heading 1</th>
			</tr>
		</thead>
		<tbody>
			<tr>
				<td>Cell 1</td>
			</tr>
		</tbody>
	</table>
</div>

Data Table Model

To unlock the power of our data tables, we'll need to create what we'll refer to as a data table model. Create a new Svelte writable store, if you're using Typescript set the type to DataTableModel, then pass the store to the dataTableHandler method.

ts
const dataTableModel: Writable<DataTableModel> = writable({
	// The original unfiltered source data.
	source: sourceData,
	// The filtered source data, shown in UI.
	filtered: sourceData,
	// Optional: An array of selected row objects.
	selection: [],
	// Optional: The current search term.
	search: '',
	// Optional: The current sort key.
	sort: '',
	// Optional: The Paginator component settings.
	pagination: { offset: 0, limit: 5, size: 0, amounts: [1, 2, 5, 10] }
});

// Automatically handles search, sort, etc when the model updates.
dataTableModel.subscribe((v) => dataTableHandler(v));

Next, we'll update our table markup to display our model data on the page. Add each desired heading paired with a matching body cell value. We'll use an #each loop to create each table body row. Note we use $dataTableModel.filtered as our loop source. The features below will modify this data.

ts
<thead>
	<tr>
		<th>Position</th>
		<th>Name</th>
		<!-- ... --->
	</tr>
</thead>
<tbody>
	{#each $dataTableModel.filtered as row, rowIndex}
		<tr>
			<td>{row.position}</td>
			<td>{row.name}</td>
			<!-- ... --->
		</tr>
	{/each}
</tbody>

Search

To implement search, bind $dataTableModel.search to any search input. You can place this input anywhere on the page.

html
<input bind:value={$dataTableModel.search} type="search" placeholder="Search..." />

Sort

We'll use dataTableSort to automatically set $dataTableModel.sort when a table heading is tapped. Add the following to your table head element.

html
<thead on:click={(e) => { dataTableSort(e, dataTableModel) }} on:keypress>

Add a data-sort="(key)" attribute to each heading you wish to be sortable. Tapping a heading will set the $dataTableModel.sort value and update the UI. Tapping a heading repeatedly will toggle between ascending and descending sort order.

html
<th data-sort="position">Position</th>
<th data-sort="name">Name</th>
<!-- ... -->

While sort is working, we're lacking a visual UI indicator. To handle this, implement the Svelte Action called tableInteraction to your table element. This adds the appropriate CSS classes that show ↑ and ↓ sort arrows.

html
<table ... use:tableInteraction>

Selection

Per Row

To handle row selection, we'll add a new heading column. Keep the comment shown, as we'll replace it in a following step.

html
<th><!-- selection --></th>

Pair this with a matching table body cell that includes a checkbox input. Append bind:dataTableChecked to the input to extend the row object source data. When checked on/off, the dataTableHandler will automatically include/exclude the entire row object in $dataTableModel.selection.

html
<td><input type="checkbox" bind:checked={row.dataTableChecked} /></td>

If you wish to visually highlight the row selection, Tailwind Elements includes a semantic class for this. Append this to your table body row element.

html
<tr class:table-row-checked={row.dataTableChecked}>

Pre-Selected

You may wish to pre-select certain table rows. We've provided a utility method to handle this. Pass your model, the key to query against, and a whitelist of values. Any object that matches the conditions will be selected. Trigger this multiple times for multipe selection queries.

ts
// Select all objects with a position value of 1 or 2:
dataTableSelect(dataTableModel, 'position', [1,2]);

Select All

If you wish to add a select all feature, replace <th><!-- selection --></th> with the following.

html
<th><input type="checkbox" on:click={(e) => { dataTableSelectAll(e, dataTableModel) }} /></th>

Pagination

Please refer to the Paginators component to learn more about this feature. For data tables, use $dataTableModel.pagination to ensures the model updates reactively. The wrapping if statement is required.

html
{#if $dataTableModel.pagination}<Paginator bind:settings={$dataTableModel.pagination} />{/if}

Accessibility

Since data tables make use of native HTML table elements, you will need to implement accessibility features directly. However, we've simplified this by providing a Svelte Action called tableA11y. This implements the required event listeners for keyboard interaction. Start by appending role and action to your table element.

html
<table ... role="grid" use:tableA11y>

Implement the aria-rowindex attribute. This starts at 1 and increments per tr row. We can utilize the #each loop index value, named rowIndex.

html
<tr ... aria-rowindex={rowIndex + 1}>

Implement three attributes per table body td cell. role and tabindex are static, while aria-colindex starts at 1 and increments per cell.

html
<td ... role="gridcell" aria-colindex={1} tabindex="0">...</td>
<td ... role="gridcell" aria-colindex={2} tabindex="0">...</td>
<!-- ... -->

Reference the Keyboard tab section at the top of this page for a list of available keyboard interactions.


View Reference

If you wish to see a complete data table, we recommend tapping the Doc Source link at the top of this page. This will allow you to inspect how the featured example at the top of this page was constructed. This implements every available data table feature.


Table Components

Looking for a simpler data-driven table component? Visit the Table documentation.

View Tables