The DataTable component is a web component that displays data in a tabular format.
The datatable componet is based on the Tanstack Table library. Almost all the properties of the Tanstack Table are supported.
The default datatable only needs the data and columns properties to be set. Check the Tanstack table column definition for more information.
HTML
Js
{/* <dap-ds-datatable id="auto"></dap-ds-datatable> */}
const dataCount = 100
const dataManual = () => Array.from({ length: dataCount }, (_, i) => ({
id: i,
title: `Title ${i}`,
category: `Category ${i}`,
price: 100 + i,
rating: 4.5 + i / 10,
}))
const datatable = document.querySelector('dap-ds-datatable')
datatable.data = dataManual()
datatable.columns = [
{
id: 'title',
accessorKey: 'title',
header: 'Title',
},
{
id: 'category',
accessorKey: 'category',
header: 'Category',
},
{
id: 'price',
accessorKey: 'price',
header: 'Price',
},
{
id: 'rating',
accessorKey: 'rating',
header: 'Rating',
},
]
The datatable component supports selection of rows. By default the selection is disabled.
To enable selection, set the enableRowSelection property to true. You can use the dds-selection-change event to get the selected rows.
You can use the rowSelection property to set the initial selection. Read more about the rowKey property.
HTML
Js
{/* <dap-ds-datatable id="auto" enableRowSelection></dap-ds-datatable> */}
autoTable.addEventListener('dds-selection-change', event => {
console.log(`Selected ${Object.keys(event.detail.selection).length} rows`)
})
{/* Set initial selection
The key is the row id and the value is a boolean.
If the value is true, the row is selected.
You can change the key with the `rowKey` property on the table.
*/}
autoTable.rowKey = 'title'
autoTable.rowSelection = {
'Title 1': true,
'Title 2': true,
}
The datatable component supports sorting by clicking on the column header. By default the sorting is disabled.
To enable sorting, set the enableSorting property to true. The default sorting mechanism is using the getToggleSortingHandler function.
The default behavior when using either the getToggleSortingHandler or toggleSorting APIs is to cycle through the sorting states like this: 'none' -> 'desc' -> 'asc' -> 'none' -> 'desc' -> 'asc' -> ...
HTML
Js
{/* <dap-ds-datatable id="auto" enableSorting></dap-ds-datatable> */}
const datatable = document.querySelector('dap-ds-datatable')
datatable.enableSorting = true
datatable.columns = [
{
id: 'title',
accessorKey: 'title',
header: 'Title',
},
...
]
If you want to use a custom sorting mechanism, you can use the manualSorting property. In this case clicking on the column header will not trigger the sorting. Instead the sorting is triggered by the dds-sorting-change event.
You can also pass a default sorting state to the sorting property. These are useful when you use remote data.
datatable.sorting = [{
desc : false,
id : "title"
}]
datatable.addEventListener('dds-sorting-change', event => {
console.log(event.detail.sorting[0])
// [{
// desc : false
// id : "title"
// }]
})
The datatable component supports remote data. If you choose to use remote data you have to handle the pagination, sorting and filtering yourself.
First you have to set the manualPagination and manualSorting property to true. Then you have to handle the dds-pagination-change, dds-sorting-change events.
HTML
Js
{/* <dap-ds-datatable id="auto" manualPagination manualSorting></dap-ds-datatable> */}
const fetchData = (table, skip, pageSize, sort, search) => {
if (search) {
fetch(`https://dummyjson.com/products/search?q=${search}`)
.then(res => res.json())
.then(data => {
console.log(data)
table.data = data?.products
// Set the total number of rows to make the pager work
table.rowCount = data.total
})
} else {
fetch(
`https://dummyjson.com/products?limit=${pageSize}&skip=${skip}${sort ? `&sortBy=${sort.id}&order=${sort.desc ? 'desc' : 'asc'} ` : ''}${search ? `&search=${search}` : ''}`,
)
.then(res => res.json())
.then(data => {
console.log(data)
table.data = data?.products
// Set the total number of rows to make the pager work
table.rowCount = data.total
})
}
}
const pageSize = 11
let pageIndex = 0 //page * pageSize
datatable.manualPagination = true
datatable.manualSorting = true
datatable.pagination = {
pageIndex,
pageSize,
}
datatable.pageSizeOptions = [11, 12, 23]
datatable.enableRowSelection = true
datatable.rowSelection = {
4: true,
6: true,
}
datatable.addEventListener('dds-sorting-change', event => {
console.log(event.detail.sorting)
fetchData(
datatable,
datatable.pagination.pageSize * datatable.pagination.pageIndex,
datatable.pagination.pageSize,
event.detail.sorting[0],
null,
)
})
datatable.addEventListener('dds-selection-change', event => {
console.log(event.detail.selection)
})
datatable.addEventListener('dds-pagination-change', event => {
console.log(event.detail)
pageIndex = event.detail.pagination.pageIndex
fetchData(
datatable,
event.detail.pagination.pageSize * event.detail.pagination.pageIndex,
event.detail.pagination.pageSize,
null,
null,
)
})
fetchData(datatable, 0, 10, null, null)
The datatable component supports custom cell renderer. You can pass a function to the cell property of the column definition.
Because the whole library is written in lit, the cell renderer is also written in lit. This means if you want to pass a custom cell renderer, you need to pass a lit template.
This can be done by using the html tag from the lit library. You can install it with npm install lit-html. Read more about lit here.
HTML
Js
import { html } from 'lit/static-html.js'
{/* <dap-ds-datatable id="auto"></dap-ds-datatable> */}
const dataCount = 100
datatable.columns = [
{
id: 'title',
accessorKey: 'title',
header: 'Title',
cell: row =>
html`<div>
<dap-ds-button>
<dap-ds-icon name="home-6-line"></dap-ds-icon>
</dap-ds-button>
${row.getValue()}
</div>`
},
]
The datatable component supports custom cell editor. You can pass a function to the cell property of the column definition.
Because the whole library is written in lit, the cell editor is also written in lit. This means if you want to pass a custom cell editor, you need to pass a lit template.
This can be done by using the html tag from the lit library. You can install it with npm install lit-html. Read more about lit here.
To make a cell editable, you can replace the default content on a click event for example, and render the desired editor component. This example uses the dap-ds-input component as an editor.
Click a cell in the title column to see the custom cell editor.
HTML
Js
{/* <dap-ds-datatable id="auto"></dap-ds-datatable> */}
table.columns = [
{
accessorKey: 'title',
cell: ({ getValue, row, column, table }) => {
const initialValue = getValue()
const span = document.createElement('span')
span.textContent = getValue()
span.addEventListener('click', () => {
const input = document.createElement('dap-ds-input')
input.value = span.textContent
input.addEventListener('dds-blur', () => {
console.log('dds-blur', input.value, row, column, table)
// optimistic update
span.textContent = input.value
input.replaceWith(span)
// call the update here
...
})
input.addEventListener('dds-keydown', event => {
if (event.detail.event.key === 'Enter') {
input.blur()
}
})
// replace the span with the edit input
span.replaceWith(input)
input.updateComplete.then(() => {
console.log('input focus')
input.focus()
})
})
return span
},
]
The datatable component supports custom header renderer. You can pass a function to the header (header API) property of the column definition.
Because the whole library is written in lit, the header renderer is also written in lit. This means if you want to pass a custom header renderer, you need to pass a lit template.
This can be done by using the html tag from the lit library. You can install it with npm install lit-html. Read more about lit here.
Use the disableDefaultSorting attribute to disable the default sorting.
Click the title to see the custom header renderer. This example uses the <dap-ds-command> component to create a custom header renderer.
HTML
Js
{/*import { html } from 'lit/static-html.js' */}
{/* <dap-ds-datatable id="auto" customHeader></dap-ds-datatable> */}
datatable.columns = [
{
id: 'title',
accessorKey: 'title',
disableDefaultSorting: customHeader,
header: () => html`<dap-ds-command floatingStrategy="fixed">
<dap-ds-button variant="clean" slot="trigger"
>Title</dap-ds-button
>
<dap-ds-command-group label="Sort" exclusive>
<dap-ds-command-item
value="asc"
selectable
?selected=${header.column.getIsSorted() === 'asc'}
@dds-command-item-click=${() => {
if (header.column.getIsSorted() === 'asc') {
header.column.clearSorting()
} else {
header.column.toggleSorting(false)
}
}}
>Asc</dap-ds-command-item
>
<dap-ds-command-item
value="desc"
selectable
?selected=${header.column.getIsSorted() === 'desc'}
@dds-command-item-click=${() => {
if (header.column.getIsSorted() === 'desc') {
header.column.clearSorting()
} else {
header.column.toggleSorting(true)
}
}}
>Desc</dap-ds-command-item
>
</dap-ds-command-group>
<dap-ds-command-item value="hide">Hide</dap-ds-command-item>
</dap-ds-command>`
}
]
The datatable component supports loading state. You can set the loading property to true to show the loading state.
The loading state can be shown with a spinner or a skeleton. The default is a spinner. To show a skeleton, set the loadingType property to skeleton.
HTML
Js
{/* <dap-ds-datatable id="auto" loading></dap-ds-datatable> */}
const datatable = document.querySelector('dap-ds-datatable')
datatable.loading = true
datatable.loadingType = 'skeleton'
The datatable component supports client-side pagination. By default the pager is hidden.
To enable the pager, set the pager property to true. You can configure page size options with pageSizeOptions and set initial pagination state with the pagination property.
Use the dds-pagination-change event to track pagination changes.
HTML
Js
{/* <dap-ds-datatable id="auto"></dap-ds-datatable> */}
const dataCount = 100
const dataManual = () => Array.from({ length: dataCount }, (_, i) => ({
id: i,
title: `Title ${i}`,
category: `Category ${i}`,
price: 100 + i,
rating: 4.5 + i / 10,
}))
const datatable = document.querySelector('dap-ds-datatable')
datatable.data = dataManual()
datatable.columns = [
{
id: 'title',
accessorKey: 'title',
header: 'Title',
},
{
id: 'category',
accessorKey: 'category',
header: 'Category',
},
{
id: 'price',
accessorKey: 'price',
header: 'Price',
},
{
id: 'rating',
accessorKey: 'rating',
header: 'Rating',
},
]
// Enable the pager component
datatable.pager = true
// Set page size options
datatable.pageSizeOptions = [5, 10, 20, 50]
// Optional: Set initial pagination state
datatable.pagination = {
pageIndex: 0,
pageSize: 10,
}
// Listen to pagination changes
datatable.addEventListener('dds-pagination-change', event => {
console.log('Page index:', event.detail.pagination.pageIndex)
console.log('Page size:', event.detail.pagination.pageSize)
})
You can enable alternating row colors for better readability by setting the enableStripedRows property to true.
HTML
Js
{/* <dap-ds-datatable id="auto"></dap-ds-datatable> */}
const dataCount = 50
const dataManual = () => Array.from({ length: dataCount }, (_, i) => ({
id: i,
title: `Title ${i}`,
category: `Category ${i}`,
price: 100 + i,
rating: 4.5 + i / 10,
}))
const datatable = document.querySelector('dap-ds-datatable')
datatable.data = dataManual()
datatable.columns = [
{
id: 'title',
accessorKey: 'title',
header: 'Title',
},
{
id: 'category',
accessorKey: 'category',
header: 'Category',
},
{
id: 'price',
accessorKey: 'price',
header: 'Price',
},
{
id: 'rating',
accessorKey: 'rating',
header: 'Rating',
},
]
// Enable striped rows
datatable.enableStripedRows = true
// Optional: Add pagination for better visibility
datatable.pager = true
datatable.pagination = {
pageIndex: 0,
pageSize: 10,
}
The datatable can emit events when rows are clicked. Enable this feature with the enableRowClick property and listen to the dds-row-click event.
HTML
Js
{/* <dap-ds-datatable id="auto"></dap-ds-datatable> */}
const dataCount = 20
const dataManual = () => Array.from({ length: dataCount }, (_, i) => ({
id: i,
title: `Title ${i}`,
category: `Category ${i}`,
price: 100 + i,
rating: 4.5 + i / 10,
}))
const datatable = document.querySelector('dap-ds-datatable')
datatable.data = dataManual()
datatable.columns = [
{
id: 'title',
accessorKey: 'title',
header: 'Title',
},
{
id: 'category',
accessorKey: 'category',
header: 'Category',
},
{
id: 'price',
accessorKey: 'price',
header: 'Price',
},
{
id: 'rating',
accessorKey: 'rating',
header: 'Rating',
},
]
// Enable row click events
datatable.enableRowClick = true
// Listen to row click events
datatable.addEventListener('dds-row-click', event => {
const row = event.detail.row
console.log('Row clicked:', row.original)
alert(`Clicked on ${row.original.title}`)
})
When the datatable has no data to display, you can customize the empty state message using the emptyText property.
HTML
Js
{/* <dap-ds-datatable id="auto"></dap-ds-datatable> */}
const datatable = document.querySelector('dap-ds-datatable')
// Set up columns
datatable.columns = [
{
id: 'title',
accessorKey: 'title',
header: 'Title',
},
{
id: 'category',
accessorKey: 'category',
header: 'Category',
},
{
id: 'price',
accessorKey: 'price',
header: 'Price',
},
]
// Set empty data array
datatable.data = []
// Set custom empty message
datatable.emptyText = 'No products found. Try adjusting your filters.'
For more complex empty states with custom HTML, icons, or buttons, you can use the empty slot instead of the emptyText property.
HTML
Js
{/*
<dap-ds-datatable id="auto">
<div slot="empty" style="text-align: center; padding: 2rem;">
<dap-ds-icon name="inbox-line" size="48"></dap-ds-icon>
<h3>No Data Available</h3>
<p>There are no items to display at this time.</p>
<dap-ds-button variant="primary">Add New Item</dap-ds-button>
</div>
</dap-ds-datatable>
*/}
const datatable = document.querySelector('dap-ds-datatable')
// Set up columns
datatable.columns = [
{
id: 'title',
accessorKey: 'title',
header: 'Title',
},
{
id: 'category',
accessorKey: 'category',
header: 'Category',
},
{
id: 'price',
accessorKey: 'price',
header: 'Price',
},
]
// Set empty data array
datatable.data = []
// The custom empty slot content is defined in the HTML above
import { DapDSDataTable } from 'dap-design-system'
import { DapDSDataTableReact } from 'dap-design-system/react'
For optimal bundle sizes, use the tree-shakeable import syntax:
import { DapDSDataTable } from 'dap-design-system/components'
| Property | Type | Default | Description |
|---|---|---|---|
data | RowData[] | [] | Data to display in the table |
columns | ExtendedColumnDef<T>[] | [] | Columns to display in the table |
rowKey | string | 'id' | Row key to use for row selection, this should be a unique key for each row |
enableRowSelection | boolean, ((row: Row<T>) => boolean) | false | Enable row selection on the table, can be a boolean or a function that returns a boolean |
enableSorting | boolean | false | Enable sorting on the table |
manualSorting | boolean | false | Enable manual sorting on the table |
manualPagination | boolean | false | Enables manual pagination. If this option is set to true, the table will not automatically paginate rows and instead will expect you to manually paginate the rows before passing them to the table. This is useful if you are doing server-side pagination and aggregation. |
autoResetPageIndex | boolean | false | If set to true, pagination will be reset to the first page when page-altering state changes eg. data is updated, filters change, grouping changes, etc. This behavior is automatically disabled when manualPagination is true but it can be overridden by explicitly assigning a boolean value to the autoResetPageIndex table option. |
enableRowClick | boolean | false | Enable row click on the table |
loading | boolean | false | Loading state of the table |
enableStripedRows | boolean | false | Whether to enable striped rows |
rowCount | number, undefined | Number of rows in the table | |
ariaDescribedBy | string, undefined | ID of element describing the table | |
caption | string, undefined | Caption text for the table | |
loadingType | 'spinner', 'skeleton' | 'spinner' | The type of loading to use |
loadingVariant | "neutral", "brand" , "negative" , "positive" , "inverted" | 'neutral' | The variant of the spinner. Only used if loadingType is 'spinner'. |
loadingSize | "xxl", "xl" , "lg" , "md" , "sm" , "xs" | 'lg' | The size of the spinner. Only used if loadingType is 'spinner'. |
loadingStaticSize | number, undefined | The size of the spinner in pixels. This overrides the size attribute. Only used if loadingType is 'spinner'. | |
loadingText | string | The loading text. Only used if loadingType is 'spinner'. | |
emptyText | string | The text to display when the table is empty. | |
disableHeaderOnEmpty | 'true', 'false' | 'true' | Whether to disable the header when the table is empty. |
showPagerOnEmpty | boolean | false | Whether to show the pager component when the table is empty. |
pager | boolean | false | Whether to show the pager component |
showPageSizeOptions | string | 'true' | Show the page size options. |
showPageIndex | string | 'true' | Show the page index. |
showPageCount | string | 'true' | Show the page count. |
showFirstButton | string | 'true' | Show the first button. |
showPreviousButton | string | 'true' | Show the previous button. |
showNextButton | string | 'true' | Show the next button. |
showLastButton | string | 'true' | Show the last button. |
firstButtonLabel | string | The label of the first button | |
previousButtonLabel | string | The label of the previous button | |
nextButtonLabel | string | The label of the next button | |
lastButtonLabel | string | The label of the last button | |
pageStateText | (pageIndex:number, pageSize: number, totalRows: number) => string | The function to determine the pager text | |
pageSizeOptions | number[] | [10, 25, 50, 100] | Available page size options for the pager |
sorting | SortingState | Sorting state of the table | |
rowSelection | RowSelectionState | Selection state of the table | |
pagination | PaginationState | Pagination state of the table | |
columnSizing | Record<string,number> , undefined | Column sizing state of the table | |
ariaLabel | string, null | null | ID of element labeling the table |
| Name | Description |
|---|---|
loading | The loading content of the table. |
empty | The empty content of the table. |
| Event Name | Description | Type |
|---|---|---|
dds-sorting-change | Fired when the sorting of the table changes. | {sorting: SortingState } |
dds-selection-change | Fired when the selection of the table changes. | {selection: RowSelectionState } |
dds-pagination-change | Fired when the pagination of the table changes. | {pagination: PaginationState } |
dds-row-click | Fired when a row is clicked. | {row: Row<T> } |
| Part Name | Description |
|---|---|
base | The main table container. |
header | The header of the table. |
header-row | The header row of the table. |
header-cell | All cells of the header. |
body | The body of the table. |
empty | The empty content of the table. |
loading | The loading content of the table. |
row | All rows of the table. |
cell | All cells of the table. |
pager | The pager of the table. |
pager-base | The base of the pager. |
pager-first | The first button of the pager. |
pager-previous | The previous button of the pager. |
pager-next | The next button of the pager. |
pager-last | The last button of the pager. |
pager-page-size-select | The page size select of the pager. |
You can style CSS parts using the ::part() pseudo-element selector:
/* Target a specific part */
.my-custom-dap-ds-datatable::part(base) {
/* Your custom styles */
}
/* Target multiple parts */
.my-custom-dap-ds-datatable::part(base),
.my-custom-dap-ds-datatable::part(header) {
/* Shared styles */
}
Example usage:
<dap-ds-datatable class="my-custom-dap-ds-datatable">
Data table
</dap-ds-datatable>
.my-custom-dap-ds-datatable::part(base) {
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
CSS parts allow you to style internal elements of the component while maintaining encapsulation. Learn more in our styling guide.
| Property Name | Description |
|---|---|
--dds-datatable-bg-color | Background color of the table. (default: var(--dds-background-neutral-subtle)). |
--dds-datatable-border-color | Border color of the table cells. (default: var(--dds-border-neutral-divider)) |
--dds-datatable-header-bg-color | Background color of the table header. (default: var(--dds-background-neutral-subtle)) |
--dds-datatable-header-text-color | Text color of the table header. (default: var(--dds-text-neutral-strong)) |
--dds-datatable-row-hover-bg-color | Background color of hovered rows. (default: var(--dds-background-brand-strong)) |
--dds-datatable-row-selected-bg-color | Background color of selected rows. (default: var(--dds-background-brand-strong)) |
--dds-datatable-cell-padding | Padding of table cells. (default: var(--dds-spacing-200)) |
--dds-datatable-header-padding | Padding of header cells. (default: var(--dds-spacing-200)) |
--dds-datatable-last-column-padding | Padding of the last column. (default: var(--dds-spacing-500)) |
--dds-datatable-first-column-padding | Padding of the first column. (default: var(--dds-spacing-500)) |
--dds-datatable-border-width | Width of table borders. (default: var(--dds-border-width-base)) |
--dds-datatable-stripe-color | Background color for striped rows. (default: var(--dds-background-neutral-base)) |
--dds-datatable-border-radius | Border radius of the table. (default: var(--dds-radius-base)) |
--dds-datatable-shadow | Box shadow of the table. (default: none) |
--dds-datatable-transition-duration | Duration of hover/selection transitions. (default: 0.2s) |
--dds-datatable-z-index | Z-index of the table. (default: 1) |
--dds-datatable-min-height | Minimum height of the table. (default: auto) |
--dds-datatable-max-height | Maximum height of the table. (default: none) |
--dds-datatable-overflow-x | Horizontal overflow behavior. (default: auto) |
--dds-datatable-overflow-y | Vertical overflow behavior. (default: auto) |
CSS custom properties (CSS variables) can be set directly on the component or in your stylesheet:
Method 1: Inline styles (Quick customization)
<dap-ds-datatable
style="--dds-datatable-bg-color: value; --dds-datatable-border-color: value;">
Data table
</dap-ds-datatable>
Method 2: CSS classes (Reusable styles)
.my-custom-dap-ds-datatable {
--dds-datatable-bg-color: value;
--dds-datatable-border-color: value;
--dds-datatable-header-bg-color: value;
}
<dap-ds-datatable class="my-custom-dap-ds-datatable">
Data table
</dap-ds-datatable>
Method 3: Global theme customization
/* Apply to all instances */
dap-ds-datatable {
--dds-datatable-bg-color: value;
--dds-datatable-border-color: value;
}
CSS custom properties inherit through the Shadow DOM, making them perfect for theming. Changes apply immediately without rebuilding.