- Table Data
- Examples
- Data table
- Column configuration
- Hiding the checkbox column
- Text columns with formatting
- Button columns
- Input columns
- Progress bar columns
- Custom columns
- Sortable columns
- Nested table
- Column widths
- Column alignment
- Customising built-in column widths
- Row hover and striped rows
- Table title and action slot
- Slots
- Properties
- Events
- Custom Properties
- Parts
- Dependencies
Table Data
<sl-table-data> | SlTableData
Data table component to display data in a tabular format.
Examples
Data table
The data table displays tabular data using a column configuration array. Each column is defined as an object that controls its label, cell type, alignment, width, sorting, and formatting.
By default, the table includes:
- Row selection with checkboxes
- Column sorting (when enabled per column)
- Inline editable, button, and progress bar cells via the column
type - Expandable rows for nested data
<sl-table-data id="data-table-default"></sl-table-data> <script> const dataTable = document.getElementById('data-table-default'); dataTable.chartTitle = 'Special data table'; dataTable.rowHover = true; dataTable.striped = true; dataTable.columns = [ { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Age', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'City', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'input', label: 'Editable input', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false }, { type: 'button', label: 'Action', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false } ]; dataTable.data = [ { id: 1, rows: ['Alice', 12, 'New York', 'A value that needs to be edited', 'View'] }, { id: 2, rows: ['Jim', 38, 'Victoria', 'A value that needs to be edited', 'View'] }, { id: 3, rows: ['Bob', 50, 'Western AUS', 'A value that needs to be edited', 'View'] }, { id: 4, rows: ['Peter', 29, 'TN', 'A value that needs to be edited', 'View'] }, { id: 5, rows: ['Joe', 30, 'NSW', 'A value that needs to be edited', 'View'] } ]; dataTable.addEventListener('cell-edit', (event) => { const { id, rowId, colIndex, updatedValue } = event.detail; console.log(`Cell updated record id: ${id} at row ${rowId}, column ${colIndex}: ${updatedValue}`); }); dataTable.addEventListener('selection-change', (event) => { const { selectedRowIds } = event.detail; console.log(`Ids of rows selected: ${selectedRowIds}`); }); </script>
Column configuration
Each item in the columns array supports the following properties:
| Property | Type | Description |
|---|---|---|
type |
'text' | 'button' | 'input' |
'progress-bar' | 'custom'
|
Determines how cell values are rendered |
label |
string |
Header label displayed in the table |
render |
(value, context) => TemplateResult | string |
For custom columns, renders the cell content (see
Custom columns)
|
sort |
(a, b, context) => number |
For sortable custom columns, compares two cells (see
Sorting custom columns)
|
alignment |
'left' | 'center' | 'right' |
Horizontal alignment of header and cell content |
buttonVariant |
Button variants | Determine button variants |
width |
string |
Fixed column width (e.g. '200px') |
minWidth |
string |
Minimum column width |
maxWidth |
string |
Maximum column width |
formatOptions |
object |
Intl.NumberFormat options passed to toLocaleString for
text columns
|
sortable |
boolean |
When true, clicking the header sorts the column |
Hiding the checkbox column
Use hide-checkbox-column to hide the row selection checkboxes.
<sl-table-data id="data-table-hiding" hide-checkbox-column></sl-table-data> <script> const dataTable = document.getElementById('data-table-hiding'); dataTable.chartTitle = 'Data table with hidden columns'; dataTable.rowHover = true; dataTable.striped = true; dataTable.columns = [ { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Age', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'City', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true } ]; dataTable.data = [ { id: 1, rows: ['Alice', 12, 'New York'] }, { id: 2, rows: ['Jim', 38, 'Chicago'] }, { id: 3, rows: ['Bob', 50, 'Los Vegas'] } ]; </script>
Text columns with formatting
text columns render cell values using
toLocaleString('en-AU', formatOptions). Use formatOptions to control number,
currency, or date formatting.
<sl-table-data id="data-table-format" hide-checkbox-column></sl-table-data> <script> const dataTable = document.getElementById('data-table-format'); dataTable.chartTitle = 'Data table with formatted values'; dataTable.rowHover = true; dataTable.columns = [ { type: 'text', label: 'Asset', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Value', alignment: 'right', width: '', minWidth: '', maxWidth: '', formatOptions: { style: 'currency', currency: 'AUD' }, sortable: true } ]; dataTable.data = [ { id: 1, rows: ['Australian equities', 860611.01] }, { id: 2, rows: ['Global equities', 1458989] }, { id: 3, rows: ['Cash', 123130] } ]; </script>
Button columns
Set a column’s type to 'button' to render a tertiary button by default in
each cell, or specify the variant with buttonVariant. The cell value is used as the
button label; Button will not be render when empty, otherwise, the button-label attribute
value is used instead.
<sl-table-data id="data-table-button" button-label="View"></sl-table-data> <script> const dataTable = document.getElementById('data-table-button'); dataTable.chartTitle = 'Data table with button column'; dataTable.rowHover = true; dataTable.striped = true; dataTable.columns = [ { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Age', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'button', label: 'Action', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false, buttonVariant: 'primary' } ]; dataTable.data = [ { id: 1, rows: ['Alice', 12, 'Details'] }, { id: 2, rows: ['Jim', 38, 'Edit'] }, { id: 3, rows: ['Bob', 50, ''] }, { id: 4, rows: ['Peter', 58, ' '] }, { id: 5, rows: ['Jenny', 42] } ]; dataTable.addEventListener('select', (event) => { const { rowId, colIndex } = event.detail; console.log(`Button clicked at row ${rowId}, column ${colIndex}`); }); </script>
Input columns
Set a column’s type to 'input' to allow inline editing. The component emits
a cell-edit event when the user finishes editing a cell (on blur).
<sl-table-data id="data-table-input"></sl-table-data> <script> const dataTable = document.getElementById('data-table-input'); dataTable.chartTitle = 'Data table with editable column'; dataTable.rowHover = true; dataTable.columns = [ { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'input', label: 'Notes', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false } ]; dataTable.data = [ { id: 1, rows: ['Alice', 'A value that needs to be edited'] }, { id: 2, rows: ['Jim', 'Another editable value'] }, { id: 3, rows: ['Bob', 'Update me on blur'] } ]; dataTable.addEventListener('cell-edit', (event) => { const { id, rowId, colIndex, updatedValue } = event.detail; console.log(`Cell updated for id ${id}: ${updatedValue}`); }); </script>
Progress bar columns
Set a column’s type to 'progress-bar' to render an
sl-progress-bar in each cell. Cell values must be numbers from 0–100; non-numeric values
render an empty cell. The indicator color is chosen automatically based on the value: danger (≤ 70),
warning (> 70), or success (> 90). The current value is shown on the bar.
<sl-table-data id="data-table-progress-bar" hide-checkbox-column></sl-table-data> <script> const dataTable = document.getElementById('data-table-progress-bar'); dataTable.chartTitle = 'Data table with progress bar column'; dataTable.rowHover = true; dataTable.columns = [ { type: 'text', label: 'Goal', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'progress-bar', label: 'Progress', alignment: 'left', width: '200px', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true } ]; dataTable.data = [ { id: 1, rows: ['Revenue target', 95] }, { id: 2, rows: ['Client onboarding', 78] }, { id: 3, rows: ['Training completion', 45] } ]; </script>
Custom columns
Set a column’s type to 'custom' to render your own content in each cell.
There are two ways to provide that content, depending on how you’re consuming the component:
-
rendercallback — set arender(value, context)function on the column. It receives the cellvalueand acontextobject ({ rowId, colIndex, row }) and returns a LitTemplateResultor a string. This is the most direct option when building the table with Lit, plain JavaScript, or React. -
Named slot — when no
rendercallback is provided, each custom cell exposes a slot namedcell-<colIndex>-<rowId>. Project your own element (including a framework component) into that slot from the light DOM. This is the recommended approach for frameworks such as Vue or Angular, where a component instance is owned and kept reactive by the framework rather than by the table.
If neither a render callback nor slotted content is supplied, the cell falls back to the
formatted text value.
<sl-table-data id="data-table-custom" hide-checkbox-column></sl-table-data> <script> const dataTable = document.getElementById('data-table-custom'); dataTable.chartTitle = 'Data table with a custom column'; dataTable.rowHover = true; dataTable.columns = [ { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'custom', label: 'Tags', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false, // `value` is the cell value (an array of strings here); `context` provides { rowId, colIndex, row }. render: (value) => Array.isArray(value) ? value .map(tag => `<sl-tag size="small" style="margin-right: 4px;">${tag}</sl-tag>`) .join('') : '' } ]; dataTable.data = [ { id: 1, rows: ['Alice', ['AU', 'Equity', 'Core']] }, { id: 2, rows: ['Bob', ['Global', 'Growth']] } ]; </script>
When consuming the component from a framework, set columns and data as
properties (not attributes) and provide framework components through the
cell-<colIndex>-<rowId> slot so the framework retains ownership of their
rendering and reactivity.
Sorting custom columns
A custom column’s cell value is often not a directly comparable primitive (for example an
object or an array), so the built-in sort — which compares strings, numbers, and dates — can’t order
it. Set sortable: true and provide a
sort(a, b, context) comparator to make such a column sortable.
The comparator receives the two cell values (a and b) and a
context object ({ colIndex, rowA, rowB }), and returns a negative number
when a should come before b, a positive number when after, and
0 when equal — the same contract as Array.prototype.sort. Two things to keep
in mind:
- Always compare in ascending order. The table applies the current sort direction on top of the value you return, so you don’t handle descending yourself.
-
Use
rowA/rowBfor tiebreaks. They are the full rows behind each value, letting you fall back to another column when the primary values are equal.
<sl-table-data id="data-table-custom-sort" hide-checkbox-column></sl-table-data> <script> const dataTable = document.getElementById('data-table-custom-sort'); dataTable.chartTitle = 'Sortable custom column'; dataTable.rowHover = true; dataTable.columns = [ { type: 'text', label: 'Task', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'custom', label: 'Priority', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true, // Cell values are objects, so render the label and sort on the numeric rank (ascending). render: (value) => `<sl-tag size="small">${value.label}</sl-tag>`, sort: (a, b) => a.rank - b.rank } ]; dataTable.data = [ { id: 1, rows: ['Deploy release', { label: 'High', rank: 3 }] }, { id: 2, rows: ['Write docs', { label: 'Low', rank: 1 }] }, { id: 3, rows: ['Review PR', { label: 'Medium', rank: 2 }] } ]; </script>
Sortable columns
Sorting is controlled per column via the sortable property. Click a sortable header to
toggle between ascending and descending order. Columns without sortable: true are not
sortable.
<sl-table-data id="data-table-sortable"></sl-table-data> <script> const dataTable = document.getElementById('data-table-sortable'); dataTable.chartTitle = 'Data table with sortable columns'; dataTable.rowHover = true; dataTable.striped = true; dataTable.columns = [ { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Age', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'input', label: 'Editable input', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false } ]; dataTable.data = [ { id: 1, rows: ['Alice', 12, 'A value that needs to be edited'] }, { id: 2, rows: ['Jim', 38, 'A value that needs to be edited'] }, { id: 3, rows: ['Bob', 50, 'A value that needs to be edited'] } ]; </script>
Nested table
Table rows can be expanded when nestedData and nestedColumns are provided on
a row. The nested table is rendered inside the expanded row with the checkbox column hidden.
A maximum height is applied to the nested table body; content that exceeds this height scrolls vertically.
<sl-table-data id="nested-table-example" hide-checkbox-column></sl-table-data> <script> const dataTable = document.getElementById('nested-table-example'); dataTable.chartTitle = 'Nested data table'; dataTable.columns = [ { type: 'text', label: 'Code', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Quantity', alignment: 'right', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Price valuation date', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false }, { type: 'text', label: 'Gross Capital Gain/(Loss)', alignment: 'right', width: '', minWidth: '', maxWidth: '', formatOptions: { style: 'currency', currency: 'AUD' }, sortable: false } ]; const nestedColumns = [ { type: 'text', label: 'Parcel date', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false }, { type: 'text', label: 'Accounting cost base', alignment: 'right', width: '', minWidth: '', maxWidth: '', formatOptions: { style: 'currency', currency: 'AUD' }, sortable: false }, { type: 'text', label: 'Cost base', alignment: 'right', width: '', minWidth: '', maxWidth: '', formatOptions: { style: 'currency', currency: 'AUD' }, sortable: false }, { type: 'text', label: 'Market Value', alignment: 'right', width: '', minWidth: '', maxWidth: '', formatOptions: { style: 'currency', currency: 'AUD' }, sortable: false }, { type: 'text', label: 'Gross Capital Gain/(Loss)', alignment: 'right', width: '', minWidth: '', maxWidth: '', formatOptions: { style: 'currency', currency: 'AUD' }, sortable: false } ]; dataTable.data = [ { id: 1, rows: ['ANZ', 'Australia and New Zealand Banking Group', 100, '12/04/2026', 1000], nestedColumns, nestedData: [ { id: 1, rows: ['22/01/2026', 1000, 1000, 1000, 1000] }, { id: 2, rows: ['22/01/2026', 1000, 1000, 1000, 1000] }, { id: 3, rows: ['22/01/2026', 1000, 1000, 1000, 1000] } ] }, { id: 2, rows: ['APA', 'APA Group', 50, '12/04/2026', 500], nestedColumns, nestedData: [ { id: 1, rows: ['22/01/2026', 500, 500, 500, 500] }, { id: 2, rows: ['22/01/2026', 500, 500, 500, 500] } ] }, { id: 3, rows: ['CBA', 'Commonwealth Bank', 75, '12/04/2026', 750] }, { id: 4, rows: ['CH01', 'Charter Hall', 25, '12/04/2026', 250] } ]; </script>
Column widths
Set width, minWidth, and/or maxWidth on individual column
definitions to control column sizing. When any column has a width set, the table uses fixed layout.
<sl-table-data id="parent-table" hide-checkbox-column></sl-table-data> <script> const parentTable = document.getElementById('parent-table'); parentTable.chartTitle = 'Table with custom column widths'; parentTable.rowHover = true; parentTable.striped = true; parentTable.columns = [ { type: 'text', label: 'Name', alignment: 'left', width: '300px', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Age', alignment: 'left', width: '100px', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'City', alignment: 'left', width: '200px', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false } ]; parentTable.data = [ { id: 1, rows: ['Alice', 25, 'New York'] }, { id: 2, rows: ['Bob', 30, 'Los Angeles'] }, { id: 3, rows: ['Charlie', 35, 'Chicago'] } ]; </script>
Nested table column widths
Apply widths directly on each column in the nestedColumns array passed to expandable
rows.
<sl-table-data id="nested-table-custom-width" hide-checkbox-column></sl-table-data> <script> const dataTable = document.getElementById('nested-table-custom-width'); dataTable.chartTitle = 'Nested table with custom column widths'; dataTable.columns = [ { type: 'text', label: 'Code', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true } ]; const nestedColumns = [ { type: 'text', label: 'Parcel date', alignment: 'left', width: '300px', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false }, { type: 'text', label: 'Cost base', alignment: 'right', width: '300px', minWidth: '', maxWidth: '', formatOptions: { style: 'currency', currency: 'AUD' }, sortable: false }, { type: 'text', label: 'Market Value', alignment: 'right', width: '300px', minWidth: '', maxWidth: '', formatOptions: { style: 'currency', currency: 'AUD' }, sortable: false } ]; dataTable.data = [ { id: 1, rows: ['ANZ', 'Australia and New Zealand Banking Group'], nestedColumns, nestedData: [ { id: 11, rows: ['22/01/2026', 1000, 1050] }, { id: 12, rows: ['23/01/2026', 1200, 1180] } ] } ]; </script>
Column alignment
Set alignment to 'left', 'center', or 'right' on
each column definition. Alignment applies to both the header and body cells.
<sl-table-data id="data-table-alignment" hide-checkbox-column></sl-table-data> <script> const dataTable = document.getElementById('data-table-alignment'); dataTable.chartTitle = 'Data table with custom column alignment'; dataTable.rowHover = true; dataTable.striped = true; dataTable.columns = [ { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Age', alignment: 'center', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'City', alignment: 'right', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false } ]; dataTable.data = [ { id: 1, rows: ['Alice', 25, 'Sydney'] }, { id: 2, rows: ['Bob', 38, 'Melbourne'] }, { id: 3, rows: ['Charlie', 29, 'Brisbane'] } ]; </script>
Customising built-in column widths
The checkbox column can be resized using a CSS custom property on the component.
<sl-table-data id="data-table-css-widths" style="--table-data-checkbox-column-width: 56px;" ></sl-table-data> <script> const dataTable = document.getElementById('data-table-css-widths'); dataTable.chartTitle = 'Table with custom checkbox column width'; dataTable.columns = [ { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false } ]; dataTable.data = [ { id: 1, rows: ['Alice'] }, { id: 2, rows: ['Bob'] } ]; </script>
Row hover and striped rows
Use row-hover to highlight rows on hover and striped to apply alternating
row backgrounds.
<sl-table-data id="data-table-styles" row-hover striped></sl-table-data> <script> const dataTable = document.getElementById('data-table-styles'); dataTable.columns = [ { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false }, { type: 'text', label: 'Age', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: false } ]; dataTable.data = [ { id: 1, rows: ['Alice', 12] }, { id: 2, rows: ['Jim', 38] }, { id: 3, rows: ['Bob', 50] } ]; </script>
Table title and action slot
Use the table-title attribute (or chartTitle property) to display a heading
above the table. Place action buttons in the default slot to render them beside the title.
<sl-table-data id="data-table-slot" table-title="Portfolio holdings"> <sl-button size="small" variant="secondary">Export</sl-button> </sl-table-data> <script> const dataTable = document.getElementById('data-table-slot'); dataTable.columns = [ { type: 'text', label: 'Code', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true }, { type: 'text', label: 'Name', alignment: 'left', width: '', minWidth: '', maxWidth: '', formatOptions: {}, sortable: true } ]; dataTable.data = [ { id: 1, rows: ['ANZ', 'Australia and New Zealand Banking Group'] }, { id: 2, rows: ['CBA', 'Commonwealth Bank'] } ]; </script>
[component-metadata:sl-table-data]
Slots
| Name | Description |
|---|---|
| (default) | The default slot. |
cell-{colIndex}-{rowId}
|
Content for a custom column cell that has no render callback. Project
a framework component here (e.g. from Vue or Angular) so it stays owned and reactive within that
framework.
|
Learn more about using slots.
Properties
| Name | Description | Reflects | Type | Default |
|---|---|---|---|---|
data
|
The data to be displayed in the table. |
|
TableData[]
|
[]
|
columns
|
The titles to be displayed in the table header columns. |
Column[]
|
[]
|
|
chartTitle
table-title
|
The title of the data table. |
string
|
''
|
|
rowHover
row-hover
|
Adds hover effect to the table rows. |
boolean
|
false
|
|
striped
|
Adds alternating stripes to the table rows. |
boolean
|
false
|
|
hideCheckboxColumn
hide-checkbox-column
|
Hides the checkbox selection column. |
boolean
|
false
|
|
buttonLabel
button-label
|
Default label used when button cell doesn’t override it. |
string
|
'Label'
|
|
buttonVariant
button-variant
|
Default button variant used when button cell doesn’t override it. |
SlButton['variant']
|
'tertiary'
|
|
updateComplete |
A read-only promise that resolves when the component has finished updating. |
Learn more about attributes and properties.
Events
| Name | React Event | Description | Event Detail |
|---|---|---|---|
cell-edit |
|
event emitted when a cell is edited. | - |
select |
|
event emitted when the selection changes. | - |
Learn more about events.
Custom Properties
| Name | Description | Default |
|---|---|---|
--table-data-checkbox-column-width |
The customisable width of the checkbox column. |
Learn more about customizing CSS custom properties.
Parts
| Name | Description |
|---|---|
base |
The component’s base wrapper. |
table |
The table element. |
Learn more about customizing CSS parts.
Dependencies
This component automatically imports the following dependencies.
<sl-checkbox><sl-icon><sl-icon-button><sl-input>