Data Table

Updated:

Use for displaying large datasets with sorting, filtering, and selecting.

Development

Data Structure

The q2-data-table has two different properties that you are responsible for providing data to. One is headers , and the other is rows. Below are type definitions of both.

Header Data Structure

The data you provide to the headers property will be an array of objects with the following data structure.

interface Q2DataTableHeader {
  title: string;

  // Will be autogenerated from the title if not provided
  // The key is used to grab the data from the row data
  key?: string;

  // 'auto' to use built-in sorting for this column
  // 'manual' to implement your own sorting for this column
  sortable?: 'auto' | 'manual';

  // Indicates in what direction the data is sorted
  sorted?: 'ASC' | 'DESC';

  // Limits the number of rows that are displayed in a cell
  // Content will be truncated if it exceeds the limit
  lineClamp?: number;


  // Defines alignment of the content within each cell in this column
  // Defaults to 'center'
  align?: 'start' | 'center' | 'end';
  verticalAlign?: 'top' | 'bottom';
  ariaLabel?: string;

  // Defines the width of the column
  width?: string;

  // Defines the background of the column
  backgroundColor?: string;

  // Defines the content type for every cell in this column for styling puposes
  type?: 'text' | 'number' | 'icon' | 'badge' | 'boolean' | 'code';

  // When type = 'badge' - for single badge display
  badgeStatus?: 'info' | 'alert' | 'warning' | 'success';
  badgeTheme?: 'primary' | 'secondary' | 'tertiary';

  // When type = 'badge' - for multiple tags display
  // If tags array is provided, it takes precedence over single badge properties
  tags?: {
    value: any;
    badgeTheme: 'primary' | 'secondary' | 'tertiary';
  }[];
}
Row Data Structure

Similarly, the data you pass to rows is an array of objects with the following structure.

interface Q2DataTableRow {
  id: string | number;
  selected?: boolean;
  expanded?: boolean;
  disabled?: boolean;
  cells: Record<string, string | number | Q2DataTableCell>
}

interface Q2DataTableCell {
  value: string | number;

  // Defines alignment of the content within this cell
  // Takes priority over the column's `align` property when provided
  // Defaults to 'center'
  align?: 'start' | 'center' | 'end';
  verticalAlign?: 'top' | 'bottom';
  ariaLabel?: string;


  // Defines the content type for this cell for styling purposes
  // Takes priority over the column's `type` property when provided
  type?: 'text' | 'number' | 'icon' | 'badge' | 'boolean' | 'code';

  // Limits the number of rows that are displayed in a cell
  // Content will be truncated if it exceeds the limit
  lineClamp?: number;

  // When type = 'badge' - for single badge display
  badgeStatus?: 'info' | 'alert' | 'warning' | 'success';
  badgeTheme?: 'primary' | 'secondary' | 'tertiary';

  // When type = 'badge' - for multiple tags display
  // If tags array is provided, it takes precedence over single badge properties
  tags?: {
    value: any;
    badgeTheme: 'primary' | 'secondary' | 'tertiary';
  }[];
}

The cells property is an object where each property key corresponds to a key defined for one of the headers.

Sample

With that said, let's look at a sample set of data that you can provide to a q2-data-table to be displayed.

const headerData = [
  {
    title: 'Day of Week',
    key: 'day',
    backgroundColor: 'var(--t-gray-14)',
  },
  { title: 'Sales', key: 'sales', align: 'center' },
  { title: 'Revenue', key: 'revenue', align: 'center', sortable: true },
];

const rowData = [
  {
    id: 1,
    cells: {
      day: 'Monday',
      sales: 93,
      revenue: '$4,650',
    },
  },
  {
    id: 2,
    cells: {
      day: 'Tuesday',
      sales: 127,
      revenue: '$6,350',
    },
  },
  {
    id: 3,
    cells: {
      day: 'Wednesday',
      sales: 121,
      revenue: '$6,050',
    },
  },
  {
    id: 4,
    cells: {
      day: 'Thursday',
      sales: 140,
      revenue: '$7,000',
    },
  },
  {
    id: 5,
    cells: {
      day: 'Friday',
      sales: 83,
      revenue: '$4,150',
    },
  },
];

From here, you need to provide the q2-data-table with this data:

const salesTable = document.querySelector('q2-data-table');
salesTable.headers = headerData;
salesTable.rows = rowData;

Badge Columns with Multiple Tags

Badge columns can display multiple tags instead of a single badge by providing a tags array in the cell data. This is useful for showing categories, multiple statuses, or collections of labels for a given row. When the tags array is provided, the component will render multiple q2-tag components in a flexible, wrapping layout instead of a single q2-badge.

When to Use Each Mode
  • Single Badge: Use for status indicators, counts, or single values (e.g., "Active", "5 items", "Warning")
  • Multiple Tags: Use for categories, multiple statuses, or collections of labels (e.g., ["Feature", "Priority"], ["Approved", "Reviewed"])
Header Configuration for Badge Column

To configure a column for badge display, set type: 'badge' in the header data:

const headerData = [
  { title: 'Product', key: 'product' },
  { title: 'Categories', key: 'categories', type: 'badge', align: 'center' },
  { title: 'Price', key: 'price', align: 'end' }
];
Row Configuration with Multiple Tags

For each row, provide a tags array in the cell object. Each tag in the array should have a value and badgeTheme:

const rowData = [
  {
    id: 1,
    cells: {
      product: 'Premium Widget',
      categories: {
        tags: [
          { value: 'Featured', badgeTheme: 'primary' },
          { value: 'New', badgeTheme: 'secondary' }
        ]
      },
      price: '$49.99'
    }
  },
  {
    id: 2,
    cells: {
      product: 'Standard Widget',
      categories: {
        tags: [
          { value: 'Popular', badgeTheme: 'tertiary' }
        ]
      },
      price: '$29.99'
    }
  }
];
Complete Example

Here's a complete example showing how to set up a data table with badge columns that use multiple tags:

const productsTable = document.querySelector('q2-data-table');

const headerData = [
  { title: 'Product Name', key: 'name' },
  { title: 'Status', key: 'status', type: 'badge', align: 'center' },
  { title: 'Tags', key: 'tags', type: 'badge', align: 'center' },
  { title: 'Price', key: 'price', align: 'end' }
];

const rowData = [
  {
    id: 1,
    cells: {
      name: 'Premium Service',
      // Single badge example (traditional mode)
      status: {
        value: 'Active',
        badgeStatus: 'success',
        badgeTheme: 'primary'
      },
      // Multiple tags example
      tags: {
        tags: [
          { value: 'Featured', badgeTheme: 'primary' },
          { value: 'Best Seller', badgeTheme: 'secondary' },
          { value: 'New', badgeTheme: 'tertiary' }
        ]
      },
      price: '$99.99'
    }
  },
  {
    id: 2,
    cells: {
      name: 'Standard Service',
      status: {
        value: 'Pending',
        badgeStatus: 'warning',
        badgeTheme: 'secondary'
      },
      tags: {
        tags: [
          { value: 'Popular', badgeTheme: 'secondary' }
        ]
      },
      price: '$49.99'
    }
  }
];

productsTable.headers = headerData;
productsTable.rows = rowData;

When a tags array is provided in a badge cell, it takes precedence over the badgeStatus and badgeTheme properties. The component will render multiple q2-tag components instead of a single q2-badge.

Visual Behavior

The multiple tags feature includes the following visual characteristics:

  • Tags display in a centered, flexible container that automatically wraps to multiple lines on smaller screens
  • Consistent spacing between tags (both horizontally and vertically)
  • Each tag can have its own independent theme color (primary, secondary, or tertiary)
  • Empty tag arrays will not render any content in the cell

Slot Examples

While q2-data-table does a great job at displaying the data you need just by passing data into the component via properties, we understand that there will be times you want to customize the content. For this, we've exposed a series of dynamically named slots to allow you to do this.

In addition, by utilizing slots, you can further enhance your use of q2-data-table to provide expandable content or dropdowns to display a list of options for each row.

Header Slots: header-cell-<key>

The <key> identifies the column from the key property in the headers data.

With this slot, you can override the content of any header cell in the table. Using the header cell data from the Data Structure section above as reference, we use a dropdown as the "Day of Week" header.

Home Download as PDF Discombobulate
<q2-data-table>
    <q2-dropdown slot="header-cell-day" label="Day of Week" type="icon" icon="options">
      <q2-dropdown-item value="Home">Home</q2-dropdown-item>
      <q2-dropdown-item value="Download as PDF">Download as PDF</q2-dropdown-item>
      <q2-dropdown-item value="Discombobulate">Discombobulate</q2-dropdown-item>
    </q2-dropdown>
</q2-data-table>
Cell Slots: row-<id>-cell-<key>

The <id> corresponds to the id property provided for each entry in the rows property. Likewise, the <key> identifies the cell/column from the key property on each entry in the headers data.

With this slot, you can override the content of any cell in any row of the table. Using the data from the Data Structure section above as a reference, we can replace the "Sales" cell on the row for "Thursday" with our own custom content:

🤯 Lots
<q2-data-table>
  <div slot="row-4-cell-sales">🤯 Lots</div>
</q2-data-table>
Dropdown cell: row-<id>-dropdown

The <id> corresponds to the id property provided for each entry in the rows property.

One typical behavior is to have each row of a table contain a dropdown that presents actions to a user that they can take on that particular row of data. Due to the implementation of q2-dropdown and its intricacies, we are also exposing this as a slot. Again using the data from the Data Structure section, we can add a dropdown to the row for "Thursday" like this:

Report Ask question
<q2-data-table>
  <q2-dropdown
    slot="row-4-dropdown"
    icon="options"
    alignment="right"
  >
    <q2-dropdown-item value="second">Report</q2-dropdown-item>
    <q2-dropdown-item value="third">Ask question</q2-dropdown-item>
  </q2-dropdown>
</q2-data-table>
Expandable Content Slot: row-<id>-expandable-content

The <id> corresponds to the id property provided for each entry in the rows property.

Another common desire is to have a table row be clickable, where upon clicking, it expands to reveal more detailed content. Because this often requires complex HTML and Javascript to display the desired results, we expose it as a slot that you can optionally utilize. The following example adds expandable content to the "Thursday" row:

Thursday Was Great

But you already knew that.

<q2-data-table>
  <div slot='row-4-expandable-content'>
    <h3>Thursday Was Great</h3>
    <p>But you already knew that.</p>
  </div>
</q2-data-table>
Empty Table Slot: empty-table

With this slot you can customize the look of your table's body when no row data is present. Alternatively, you may use the emptyIcon and emptyMessage properties to customize the empty state of the table with a q2-icon and custom text.

Table has no row data.
<q2-data-table>
  <div slot="empty-table" style="display: flex; justify-content: center; align-items: center; gap: 10px;">
    <q2-icon type="warning"></q2-icon>
    <b>Table has no row data.</b>
  </div>
</q2-data-table>

Sorting

By default, if a column has sortable: 'auto' set in the header data, the component will do its best to sort the data you provide in the rows property automatically. This works for most use cases you may have. However, a common use case is when you have multiple pages of content or need to provide a more detailed algorithm with which to sort your data.

For that, we recommend setting sortable: 'manual' in the desired header data, and listening to the sort event that is emitted from the component when you click on a sortable column header. Here is an example.

const dataTableElement = document.querySelector("q2-data-table")

function sortTransactions(event) {
  // Calling preventDefault() will tell the Data Table
  // NOT to perform the default sort functionality
  event.preventDefault();

  const {
    direction,
    header: { key },
  } = event.detail;

  dataTableElement.rows = [
    ...allTransactions.sort((a, b) => {
      if (direction === 'ASC') return a.cells[key] > b.cells[key] ? 1 : -1;
      else return a.cells[key] < b.cells[key] ? 1 : -1;
    }),
  ]);

  // You'll also want to update the headers to reflect which
  // column was sorted and it's direction
  dataTableElement.headers = allHeaders.map(header => (
    { ...header, sorted: header.key === key ? direction : undefined }))
  );
}

tableElement.current?.addEventListener('sort', sortTransactions);

Slots

The q2-data-table element has multiple slots that can be used to insert custom content into the component.

Learn more about slots

An optional slot to display custom content when the table is empty.

A slot for the content of any cell in the header of the table.

A slot for overriding the content of any cell in any row of the table with custom content.

A slot to provide a Dropdown for a row.

A slot that makes the row expandable and displays the provided content.

Properties

The q2-data-table element has one or more properties that support text localization. Those properties are indicated by the localizable badge in the description.

Learn more about properties.

bordered

Adds borders between rows and/or columns in the table.

caption

Provides a caption for the data table.

clickable

Adds the ability to click a row and have the table emit an event with the selected row's data.

density

Determines the amount of padding for each of the cells in the table.

emptyIcon
empty-icon

Determines the q2-icon that will display when rows has no value.

emptyMessage
empty-message

Determines the message that will display when rows has no value.

Localizable
headers

Defines the headers of the table.

Example:

element.headers = [
 {
   title: 'Day of the Week',
   key: 'day',
 },
 {
   title: 'Sales',
   key: 'sales',
   align: 'end',
 }
]
hideCaption
hide-caption

Hides the caption from view, but still makes it available to screen readers for accessibility purposes.

hideClickable
hide-clickable

Visually hides the Select button that displays when clickable=true. It will still be discoverable by assistive technologies.

Use of this property requires clickable to be set to true.

loading

Displays a loading state on the table to indicate background activity.

rows

Defines the rows of the table.

Example:

element.rows = [
 {
   id: 1,
   cells: {
     day: 'Monday',
     sales: 93
   }
 },
 {
   id: 2,
   cells: {
     day: 'Tuesday',
     sales: 127
   }
 },
 {
   id: 3,
   cells: {
     day: 'Wednesday',
     sales: 121
   }
]
selectAlign
select-align

Controls the position of the checkbox column when selectable is true. When set to 'right', the checkbox column will appear on the right side of the table.

Use of this property requires selectable to be set to true.

selectMode
select-mode

Determines if the selectable checkboxes allow for multi-select or not. If set to "single", once a row is selected, all other rows will be disabled. See the documentation on the select event for how to handle selections.

Use of this property requires selectable to be set to true.

selectable

Adds a checkbox to each row of the table making it selectable.

selectedRows
selected-rows

Returns selected rows.

shadowed

Adds a shadow to the table

sticky

Makes table header sticky

striped

Enables alternating background colors for the table rows

Events

The q2-data-table element exposes events that can be used to send and receive data from the component based on user interaction.

Learn more about events.

tctClick

Emitted when a row is clicked.

Requires the clickable prop to be set to true.

Call event.preventDefault() to prevent the default click behavior.

Event Detail Type signature

{ row: Q2DataTableSerializedRow; }

tctSelect

Emitted when a row is selected.

Requires the selectable prop to be set to true.

Call event.preventDefault() to prevent the default selection behavior.

Event Detail Type signature

{ row: Q2DataTableSerializedRow; rows: Q2DataTableSerializedRow[]; allSelected: boolean; }

tctSelectAllRows

Emitted when the select-all checkbox is toggled.

Requires the selectable prop to be set to true and the selectMode prop to be set to multiple.

Call event.preventDefault() to prevent the default behavior.

Event Detail Type signature

{ checked: boolean; }

tctSort

Emitted when a column is sorted.

Requires the sortable prop to be set to true on the column.

Call event.preventDefault() to prevent the default sorting behavior.

Event Detail Type signature

{ header: Q2DataTableHeader; direction: "ASC" | "DESC"; }

tctToggle

Emitted when an expandable row is toggled.

Requires content in the row-{id}-expandable-content slot.

Call event.preventDefault() to prevent the default toggling behavior.

Event Detail Type signature

{ row: Q2DataTableSerializedRow; }

Methods

The q2-data-table element exposes methods that can be used to perform various actions on the component.

Learn more about methods.

clickRow

Test only

A method to click a row that accepts a row ID that is will be clicked.

Type signature

clickRow(rowId: number | string) => Promise<void>

getCellContent

Test only

A method that returns the plain text value of a particular cell (including slot content).

Type signature

getCellContent(rowId: number | string, columnKey: string) => Promise<string>

sortColumn

Test only

A method to sort a column that accepts a header object with key and sorted property.

Type signature

sortColumn(header: Q2DataTableHeader) => Promise<void>

toggleRowExpansion

Test only

A method to toggle row expansion that accepts a row ID that will be clicked to expand or collapse the expandable content

Type signature

toggleRowExpansion(rowId: number | string) => Promise<void>

toggleRowSelect

Test only

A method to toggle row selection that accepts a row ID whose checkbox will be checked, if the feature is enabled.

Type signature

toggleRowSelect(rowId: number | string) => Promise<void>

toggleSelectAllRows

Test only

A method to toggle select all button (checkbox) on left top corner.

Type signature

toggleSelectAllRows() => Promise<void>

Accessibility

Accessibility Report

Tecton components are designed and tested to be WCAG compliant when used appropriately, and do not get released without proper validation. Developers should prefer not to set ARIA attributes when using components from the Tecton Design System.

CSS Variables

The following CSS variables are available to override the default theme styles of the q2-data-table component.

Dependencies

Many Tecton components consume other components to maintain visual and functional consistency. If you are looking for a CSS variable you think should exist but are not seeing it, it may be defined in one of the dependent components below. Either way, if you think it's something we should expose more conveniently, let us know!

Changelog

The changelog provides a detailed history of new features, improvements, and bug fixes going back to Tecton 1.30.0. If the button is disabled, it indicates there have been no detectable changes since then.

Show changelog (37)