Table

Compare data in rows and columns with sorting, filtering, and selection.

Live preview
Interact with the component to see results here.
View code
HTML
<sy-table
  aria-label="Team work status"
  selection-mode="none"
  variant="horizontal"
  line-style="solid"
  id="sample"
></sy-table>

<script type="module">
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "team",
        "label": "Team",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "rows": [{
        "id": "alex",
        "name": "Alex",
        "team": "Design",
        "tasks": 12
      },
      {
        "id": "jin",
        "name": "Jin",
        "team": "Design",
        "tasks": 8
      },
      {
        "id": "min",
        "name": "Min",
        "team": "Engineering",
        "tasks": 21
      },
      {
        "id": "sam",
        "name": "Sam",
        "team": "Engineering",
        "tasks": 15
      }
    ]
  });
</script>

Usage guide

#

Use for browsing, sorting, and selecting lightweight lists. Use Grid when large datasets require virtualization.

  1. Connect columns and data

    Pass key and label in columns, and a JavaScript array in rows.

  2. Set row identifiers

    Set rowKey to a stable, unique key.

  3. Enable the interactions you need

    Choose per-column sortable and filterable, set selectionMode to none, single, or multiple, and connect events to app state.

Examples

#

Sorting and filtering

Click a column name to sort or its filter button to open search conditions.

View code
HTML
<sy-table aria-label="Team work status" id="sample"></sy-table>

<script type="module">
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "team",
        "label": "Team",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "rows": [{
        "id": "alex",
        "name": "Alex",
        "team": "Design",
        "tasks": 12
      },
      {
        "id": "jin",
        "name": "Jin",
        "team": "Design",
        "tasks": 8
      },
      {
        "id": "min",
        "name": "Min",
        "team": "Engineering",
        "tasks": 21
      },
      {
        "id": "sam",
        "name": "Sam",
        "team": "Engineering",
        "tasks": 15
      }
    ]
  });
</script>

Multiple rows

Select rows using checkboxes and retrieve the selected data.

View code
HTML
<sy-table
  aria-label="Team work status"
  selection-mode="multiple"
  id="sample"
></sy-table>
<div class="demo-actions"
  style="display:flex;flex-wrap:wrap;gap:8px;margin-top:16px"
>
  <sy-button id="action-0" size="small">Read selected rows</sy-button>
  <sy-button id="action-1" size="small">Deselect</sy-button>
  <sy-button id="action-2" size="small">Select Alex</sy-button>
</div>
<output id="demo-result" aria-live="polite"></output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? 'Done';
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "team",
        "label": "Team",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "rows": [{
        "id": "alex",
        "name": "Alex",
        "team": "Design",
        "tasks": 12
      },
      {
        "id": "jin",
        "name": "Jin",
        "team": "Design",
        "tasks": 8
      },
      {
        "id": "min",
        "name": "Min",
        "team": "Engineering",
        "tasks": 21
      },
      {
        "id": "sam",
        "name": "Sam",
        "team": "Engineering",
        "tasks": 15
      }
    ]
  });
  const actionTarget0 = document.querySelector("#sample");
  document.querySelector('#action-0').addEventListener('click', async () => {
    const result = await actionTarget0.getSelectedRows();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget1 = document.querySelector("#sample");
  document.querySelector('#action-1').addEventListener('click', async () => {
    const result = await actionTarget1.clearSelection();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget2 = document.querySelector("#sample");
  document.querySelector('#action-2').addEventListener('click', async () => {
    const result = await actionTarget2.setSelectedKeys([
      "alex"
    ]);
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
</script>

Single row

Select one row at a time.

View code
HTML
<sy-table
  aria-label="Team work status"
  selection-mode="single"
  id="sample"
></sy-table>

<script type="module">
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "team",
        "label": "Team",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "rows": [{
        "id": "alex",
        "name": "Alex",
        "team": "Design",
        "tasks": 12
      },
      {
        "id": "jin",
        "name": "Jin",
        "team": "Design",
        "tasks": 8
      },
      {
        "id": "min",
        "name": "Min",
        "team": "Engineering",
        "tasks": 21
      },
      {
        "id": "sam",
        "name": "Sam",
        "team": "Engineering",
        "tasks": 15
      }
    ]
  });
</script>

External sorting and filtering

Connect the app's filter tools to the table.

View code
HTML
<sy-table aria-label="Team work status" id="sample"></sy-table>
<div class="demo-actions"
  style="display:flex;flex-wrap:wrap;gap:8px;margin-top:16px"
>
  <sy-button id="action-0" size="small">Most completed first</sy-button>
  <sy-button id="action-1" size="small">Design only</sy-button>
  <sy-button id="action-2" size="small">Read filters</sy-button>
  <sy-button id="action-3" size="small">Clear filters</sy-button>
</div>
<output id="demo-result" aria-live="polite"></output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? 'Done';
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "team",
        "label": "Team",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "rows": [{
        "id": "alex",
        "name": "Alex",
        "team": "Design",
        "tasks": 12
      },
      {
        "id": "jin",
        "name": "Jin",
        "team": "Design",
        "tasks": 8
      },
      {
        "id": "min",
        "name": "Min",
        "team": "Engineering",
        "tasks": 21
      },
      {
        "id": "sam",
        "name": "Sam",
        "team": "Engineering",
        "tasks": 15
      }
    ]
  });
  const actionTarget0 = document.querySelector("#sample");
  document.querySelector('#action-0').addEventListener('click', async () => {
    const result = await actionTarget0.setSort("tasks", "desc");
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget1 = document.querySelector("#sample");
  document.querySelector('#action-1').addEventListener('click', async () => {
    const result = await actionTarget1.setColumnFilter("team", "Design");
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget2 = document.querySelector("#sample");
  document.querySelector('#action-2').addEventListener('click', async () => {
    const result = await actionTarget2.getColumnFilters();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget3 = document.querySelector("#sample");
  document.querySelector('#action-3').addEventListener('click', async () => {
    const result = await actionTarget3.clearFilters();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
</script>

Merge consecutive cells

Display consecutive rows with the same team as one cell.

View code
HTML
<sy-table aria-label="Team work status" id="sample"></sy-table>

<script type="module">
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "team",
        "label": "Team",
        "autoMerge": true
      },
      {
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "rows": [{
        "id": "alex",
        "name": "Alex",
        "team": "Design",
        "tasks": 12
      },
      {
        "id": "jin",
        "name": "Jin",
        "team": "Design",
        "tasks": 8
      },
      {
        "id": "min",
        "name": "Min",
        "team": "Engineering",
        "tasks": 21
      },
      {
        "id": "sam",
        "name": "Sam",
        "team": "Engineering",
        "tasks": 15
      }
    ]
  });
</script>

Resize columns

Drag the header's right edge or set the width through a method.

View code
HTML
<sy-table
  aria-label="Team work status"
  resizable
  id="sample"
></sy-table>
<div class="demo-actions"
  style="display:flex;flex-wrap:wrap;gap:8px;margin-top:16px"
>
  <sy-button id="action-0" size="small">Name column: 160px</sy-button>
</div>
<output id="demo-result" aria-live="polite"></output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? 'Done';
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "team",
        "label": "Team",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "rows": [{
        "id": "alex",
        "name": "Alex",
        "team": "Design",
        "tasks": 12
      },
      {
        "id": "jin",
        "name": "Jin",
        "team": "Design",
        "tasks": 8
      },
      {
        "id": "min",
        "name": "Min",
        "team": "Engineering",
        "tasks": 21
      },
      {
        "id": "sam",
        "name": "Sam",
        "team": "Engineering",
        "tasks": 15
      }
    ]
  });
  const actionTarget0 = document.querySelector("#sample");
  document.querySelector('#action-0').addEventListener('click', async () => {
    const result = await actionTarget0.setColumnWidth("name", 160);
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
</script>

Empty results

Provide guidance when there are no rows to display.

View code
HTML
<sy-table
  aria-label="Team work status"
  empty-text="No team members match the criteria."
  id="sample"
></sy-table>

<script type="module">
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "team",
        "label": "Team",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "rows": []
  });
</script>

Events and payloads

Interact with the component and inspect the latest event name and detail. The code includes subscriptions to events emitted by this component and its children.

View code
HTML
<sy-table
  aria-label="Team work status"
  selection-mode="none"
  variant="horizontal"
  line-style="solid"
  id="sample"
></sy-table>
<output id="demo-events" aria-live="polite">Interact with the component to see
  its latest event and payload.</output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? 'Done';
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "team",
        "label": "Team",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "rows": [{
        "id": "alex",
        "name": "Alex",
        "team": "Design",
        "tasks": 12
      },
      {
        "id": "jin",
        "name": "Jin",
        "team": "Design",
        "tasks": 8
      },
      {
        "id": "min",
        "name": "Min",
        "team": "Engineering",
        "tasks": 21
      },
      {
        "id": "sam",
        "name": "Sam",
        "team": "Engineering",
        "tasks": 15
      }
    ]
  });
  for (const name of ["sortChanged", "selectionChanged", "rowClicked", "filterChanged",
      "columnResized"
    ]) {
    document.addEventListener(name, event => {
      document.querySelector('#demo-events').textContent =
        `${event.target.localName} · ${name}: ${describeResult(event.detail)}`;
    });
  }
</script>

Compact row spacing

compact reduces row spacing to fit more information in the same space.

View code
HTML
<sy-table
  aria-label="Team work status"
  selection-mode="none"
  variant="horizontal"
  line-style="solid"
  compact
  id="sample"
></sy-table>

<script type="module">
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "team",
        "label": "Team",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "rows": [{
        "id": "alex",
        "name": "Alex",
        "team": "Design",
        "tasks": 12
      },
      {
        "id": "jin",
        "name": "Jin",
        "team": "Design",
        "tasks": 8
      },
      {
        "id": "min",
        "name": "Min",
        "team": "Engineering",
        "tasks": 21
      },
      {
        "id": "sam",
        "name": "Sam",
        "team": "Engineering",
        "tasks": 15
      }
    ]
  });
  const component2 = document.querySelector("#sample");
  Object.assign(component2, {
    "compact": true
  });
</script>

Pass rows through data

You can also pass the row array through data. Use one input path instead of setting both rows and data on the same example.

View code
HTML
<sy-table
  aria-label="Team work status"
  selection-mode="none"
  variant="horizontal"
  line-style="solid"
  id="sample"
></sy-table>

<script type="module">
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "columns": [{
        "key": "name",
        "label": "Name",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "team",
        "label": "Team",
        "sortable": true,
        "filterable": true
      },
      {
        "key": "tasks",
        "label": "Complete",
        "sortable": true,
        "align": "right"
      }
    ],
    "data": [{
        "id": "alex",
        "name": "Alex",
        "team": "Design",
        "tasks": 12
      },
      {
        "id": "jin",
        "name": "Jin",
        "team": "Design",
        "tasks": 8
      },
      {
        "id": "min",
        "name": "Min",
        "team": "Engineering",
        "tasks": 21
      },
      {
        "id": "sam",
        "name": "Sam",
        "team": "Engineering",
        "tasks": 15
      }
    ]
  });
</script>

API reference

#

Hover over a name or focus it with the keyboard to see the code. On touch screens, tap the name.

<sy-table>

Properties

Attribute / PropertyTypeDefaultDescription
SyTableColumn[] | string[]Array of column definitions with key, label, sortable, filterable, and other properties.

Pass objects and arrays as JavaScript properties.

SyTableRow[] | string[]Array of row data. Each row must have a unique key.

Pass objects and arrays as JavaScript properties.

SyTableRow[] | string''Alias of `rows`; takes precedence when set so consumers can bind data without choosing a name.

Pass objects and arrays as JavaScript properties.

string'id'Field name used to identify a row uniquely. Spec name: `row-key`.
'none' | 'single' | 'multiple''none'Selection mode. Spec name: `selection-mode`.
'none' | 'horizontal' | 'vertical' | 'full' | 'outline''horizontal'Display style for row, column, and outer borders.
'solid' | 'dashed' | 'dotted''solid'Border line style for every divider in the table — outer frame, column dividers (`variant: 'vertical' | 'full'`), and row dividers (`variant: 'horizontal' | 'full'`). Width and colour stay on the design-system tokens; only the dash pattern changes. Spec name: `line-style`.
booleanfalseAlternate row background colour.
booleanfalseReduce row height.
booleantruePin the header inside the scroll container. Spec name: `sticky-header`.
booleanfalseHide the column header (`<thead>`). Use when the table has columns but the visual design omits the header — e.g. the row-header pattern where the leftmost column already identifies each row, or when the header lives outside the table. Spec name: `hide-header`.
booleanfalseRender the first data column as a row header (`<th scope="row">`). Useful for grids where the leftmost column identifies the row (think spreadsheet row labels). Spec name: `row-header`.
booleantruePin the row header to the left edge while the body scrolls horizontally. Has no effect unless `row-header` is also set. Spec name: `sticky-row-header`.
string'No data'Empty-state text when no rows are present. Spec name: `empty-text`.
string''Currently sorted column key. Spec name: `sort-key`.
SyTableSortDirection'none'Active sort direction. Spec name: `sort-direction`.
string[] | string[]Array of currently selected row keys.

Pass objects and arrays as JavaScript properties.

booleanfalseIndicate that processing is in progress.
string''Optional accessible label applied to the `<table>` element.
booleanfalseAllow the user to drag a column's right edge to resize it. The new width is committed on pointer-up and a `columnResized` event fires. Resize state is keyed by `column.key` so initial `column.width` and explicit changes coexist. Drag handles appear on every cell (header + body) so the user can grab the column edge from any row.

Events

EventDetailDescription
CustomEvent<{ key: string; direction: SyTableSortDirection }>Read the result from the event's detail.
CustomEvent<{ selectedKeys: string[]; selectedRows: SyTableRow[] }>Read the result from the event's detail.
CustomEvent<{ row: SyTableRow; index: number; key: string; originalEvent: MouseEvent | KeyboardEvent }>Read the result from the event's detail.
CustomEvent<{ key: string; value: string; filters: Record<string, string> }>Fires after a column filter is applied or cleared. `value` is the committed text (empty string = filter cleared); `filters` is a snapshot of every active filter so consumers can serialize the entire filter set.
CustomEvent<{ key: string; width: number; widths: Record<string, number> }>Fires when a resize gesture commits the new width on pointer-up.

Methods

MethodReturnsDescription
Promise<SyTableRow[]>Return an array of selected row data.
Promise<void>Deselect all rows.
Promise<void>Select the row with the specified key.
Promise<void>Set a column's sort direction.
Promise<void>Toggle the loading indicator.
Promise<void>Scroll the table to the top.
Promise<void>Programmatically set or clear a column filter. Pass an empty string to clear. Bypasses the popover UI so consumers can drive filters from external controls (saved views, query string, etc.). Fires `filterChanged`.
Promise<Record<string, string>>Returns a snapshot of every active filter keyed by column key.
Promise<void>Clears every active filter at once. Fires `filterChanged` with `key: ''`.
Promise<void>Programmatically set the rendered width (in pixels) of a column. Pass `0` to revert to the column's intrinsic width.

Slots

SlotDescription
Component body or child items.