Data table

The DataTable component is a web component that displays data in a tabular format.

Examples

The datatable componet is based on the Tanstack Table library. Almost all the properties of the Tanstack Table are supported.

Default

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',
    },
  ]
Selection

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,
  }
Sorting

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',
    },
    ...
  ]   
Manual sorting

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"
  // }]
})
Remote data

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)
Custom cell renderer

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>`
  },
]
Custom cell editor

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
    },
  ]
Custom header renderer

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>`
  }
]
Loading state

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'
Pagination

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)
  })
Striped Rows

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,
  }
Row Click Events

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}`)
  })
Empty State

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.'
Custom Empty Slot

For more complex empty states with custom HTML, icons, or buttons, you can use the empty slot instead of the emptyText property.

HTML

No Data AvailableThere are no items to display at this time.Add New Item

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
Importing
import { DapDSDataTable } from 'dap-design-system'
Importing React
import { DapDSDataTableReact } from 'dap-design-system/react'
Tree-Shakeable Imports

For optimal bundle sizes, use the tree-shakeable import syntax:

import { DapDSDataTable } from 'dap-design-system/components'
Attributes
PropertyTypeDefaultDescription
dataRowData[][]Data to display in the table
columnsExtendedColumnDef<T>[][]Columns to display in the table
rowKeystring'id'Row key to use for row selection, this should be a unique key for each row
enableRowSelectionboolean, ((row: Row<T>) => boolean)falseEnable row selection on the table, can be a boolean or a function that returns a boolean
enableSortingbooleanfalseEnable sorting on the table
manualSortingbooleanfalseEnable manual sorting on the table
manualPaginationbooleanfalseEnables 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.
autoResetPageIndexbooleanfalseIf 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.
enableRowClickbooleanfalseEnable row click on the table
loadingbooleanfalseLoading state of the table
enableStripedRowsbooleanfalseWhether to enable striped rows
rowCountnumber, undefinedNumber of rows in the table
ariaDescribedBystring, undefinedID of element describing the table
captionstring, undefinedCaption 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'.
loadingStaticSizenumber, undefinedThe size of the spinner in pixels. This overrides the size attribute. Only used if loadingType is 'spinner'.
loadingTextstringThe loading text. Only used if loadingType is 'spinner'.
emptyTextstringThe text to display when the table is empty.
disableHeaderOnEmpty'true', 'false''true'Whether to disable the header when the table is empty.
showPagerOnEmptybooleanfalseWhether to show the pager component when the table is empty.
pagerbooleanfalseWhether to show the pager component
showPageSizeOptionsstring'true'Show the page size options.
showPageIndexstring'true'Show the page index.
showPageCountstring'true'Show the page count.
showFirstButtonstring'true'Show the first button.
showPreviousButtonstring'true'Show the previous button.
showNextButtonstring'true'Show the next button.
showLastButtonstring'true'Show the last button.
firstButtonLabelstringThe label of the first button
previousButtonLabelstringThe label of the previous button
nextButtonLabelstringThe label of the next button
lastButtonLabelstringThe label of the last button
pageStateText(pageIndex:number, pageSize: number, totalRows: number) => stringThe function to determine the pager text
pageSizeOptionsnumber[][10, 25, 50, 100]Available page size options for the pager
sortingSortingStateSorting state of the table
rowSelectionRowSelectionStateSelection state of the table
paginationPaginationStatePagination state of the table
columnSizingRecord<string,number> , undefinedColumn sizing state of the table
ariaLabelstring, nullnullID of element labeling the table
Slots
NameDescription
loadingThe loading content of the table.
emptyThe empty content of the table.
Events
Event NameDescriptionType
dds-sorting-changeFired when the sorting of the table changes.{sorting: SortingState }
dds-selection-changeFired when the selection of the table changes.{selection: RowSelectionState }
dds-pagination-changeFired when the pagination of the table changes.{pagination: PaginationState }
dds-row-clickFired when a row is clicked.{row: Row<T> }
CSS Parts
Part NameDescription
baseThe main table container.
headerThe header of the table.
header-rowThe header row of the table.
header-cellAll cells of the header.
bodyThe body of the table.
emptyThe empty content of the table.
loadingThe loading content of the table.
rowAll rows of the table.
cellAll cells of the table.
pagerThe pager of the table.
pager-baseThe base of the pager.
pager-firstThe first button of the pager.
pager-previousThe previous button of the pager.
pager-nextThe next button of the pager.
pager-lastThe last button of the pager.
pager-page-size-selectThe page size select of the pager.
How to Use CSS Parts

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.

CSS Custom Properties
Property NameDescription
--dds-datatable-bg-colorBackground color of the table. (default: var(--dds-background-neutral-subtle)).
--dds-datatable-border-colorBorder color of the table cells. (default: var(--dds-border-neutral-divider))
--dds-datatable-header-bg-colorBackground color of the table header. (default: var(--dds-background-neutral-subtle))
--dds-datatable-header-text-colorText color of the table header. (default: var(--dds-text-neutral-strong))
--dds-datatable-row-hover-bg-colorBackground color of hovered rows. (default: var(--dds-background-brand-strong))
--dds-datatable-row-selected-bg-colorBackground color of selected rows. (default: var(--dds-background-brand-strong))
--dds-datatable-cell-paddingPadding of table cells. (default: var(--dds-spacing-200))
--dds-datatable-header-paddingPadding of header cells. (default: var(--dds-spacing-200))
--dds-datatable-last-column-paddingPadding of the last column. (default: var(--dds-spacing-500))
--dds-datatable-first-column-paddingPadding of the first column. (default: var(--dds-spacing-500))
--dds-datatable-border-widthWidth of table borders. (default: var(--dds-border-width-base))
--dds-datatable-stripe-colorBackground color for striped rows. (default: var(--dds-background-neutral-base))
--dds-datatable-border-radiusBorder radius of the table. (default: var(--dds-radius-base))
--dds-datatable-shadowBox shadow of the table. (default: none)
--dds-datatable-transition-durationDuration of hover/selection transitions. (default: 0.2s)
--dds-datatable-z-indexZ-index of the table. (default: 1)
--dds-datatable-min-heightMinimum height of the table. (default: auto)
--dds-datatable-max-heightMaximum height of the table. (default: none)
--dds-datatable-overflow-xHorizontal overflow behavior. (default: auto)
--dds-datatable-overflow-yVertical overflow behavior. (default: auto)
How to Use CSS Custom Properties

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.