Row Expansion


Useful Links

Actions


Table Demo


NameAgeEmailPhone
Tracy Ondricka59Cody.Hahn46@hotmail.com(270) 685-2088
Dr. Delores Greenholt86Jermain22@gmail.com(670) 634-8446
Lena Hagenes90Rosalind.Bins55@hotmail.com(388) 742-7727
Dr. Dallas Bogisich67Will_Orn@yahoo.com(987) 878-2091
Frances Hackett-Ullrich30Morton.Reichert4@hotmail.com(398) 339-5972
Miss Katherine Strosin47Josh_Dare@yahoo.com(522) 311-0039
Kim Ziemann II41Jacquelyn.Zemlak@yahoo.com(442) 360-5473
Al Krajcik63Georgiana.Howe21@yahoo.com(717) 941-5844
Lucille Sporer67Bernard.Purdy12@hotmail.com(706) 648-9699
Elizabeth White88Benny_OKeefe69@yahoo.com(884) 678-5353

Debug


Expanded State

{}

Explanation


In this guide, we'll focus on enhancing a table by adding expandable rows that allow users to show and hide nested data. We'll go through the steps of creating an expansion button component, defining the columns with an expansion control, setting up a reactive state for row expansion, and ensuring that the table properly handles nested data.

You should already know how to make a basic table before proceeding.

🗒️ Note: This process works for tables with hierarchical data structures.

Creating the Expansion Button

We start by creating a simple expansion button component called ExpandButton. This component will handle the expansion control rendering for each row in the table.

<!-- expand-button.svelte -->
<script lang="ts">
  type Props = {
    onclick: () => void;
    canExpand: boolean;
    isExpanded: boolean;
  };

  let { onclick, canExpand, isExpanded }: Props = $props();
</script>

<button disabled={!canExpand} {onclick}>
  {#if canExpand}
    {isExpanded ? '▼' : '▶'}
  {/if}
</button>

Creating an Expansion Column

Next, we define the table columns using a column helper, with a specific focus on adding an expansion column that uses the ExpandButton component.

<!-- +page.svelte -->
<script lang="ts">
  import { createColumnHelper, renderComponent } from '$lib/table';
  import ExpandButton from './_components/expand-button.svelte';
  import { type UserProfile } from '$lib/services/user-profile';

  // Create a column helper for the user profile data.
  const colHelp = createColumnHelper<UserProfile>();

  // Define the columns including a column for row expansion.
  const columnDefs = [
    // Add a column for expansion
    colHelp.display({
      id: 'expansion',
      cell: ({ row }) =>
        renderComponent(ExpandButton, {
          onclick: row.getToggleExpandedHandler(),
          canExpand: row.getCanExpand(),
          isExpanded: row.getIsExpanded()
        })
    }),
    colHelp.accessor('name', { header: 'Name' }),
    colHelp.accessor('age', { header: 'Age' }),
    colHelp.accessor('email', { header: 'Email' }),
    colHelp.accessor('phone', { header: 'Phone' })
  ];
</script>

The expansion button's onclick event toggles the expansion state of the respective row when clicked.

Setting Up Expansion State

To manage the row expansion state reactively, we use a $state rune to create a reactive signal that tracks which rows are expanded.

<script lang="ts">
  import { createColumnHelper, renderComponent } from '$lib/table'; 
  import { type ExpandedState, createColumnHelper, renderComponent } from '$lib/table'; 
  import ExpandButton from './_components/expand-button.svelte';
  import { type UserProfile } from '$lib/services/user-profile';

  const colHelp = createColumnHelper<UserProfile>();

  const columnDefs = [
    colHelp.display({
      id: 'expansion',
      cell: ({ row }) =>
        renderComponent(ExpandButton, {
          onclick: row.getToggleExpandedHandler(),
          canExpand: row.getCanExpand(),
          isExpanded: row.getIsExpanded()
        })
    }),
    colHelp.accessor('name', { header: 'Name' }),
    colHelp.accessor('age', { header: 'Age' }),
    colHelp.accessor('email', { header: 'Email' }),
    colHelp.accessor('phone', { header: 'Phone' })
  ];

  // Define a reactive state to track the row expansion state.
  let expandedState: ExpandedState = $state({}); 
</script>

Setting Up the Table with Sub-Rows

When setting up the table, we need to configure both the expansion state and define how sub-rows are retrieved. This setup enables the hierarchical data display.

<script lang="ts">
   import {
    type ExpandedState,
    createColumnHelper,
    renderComponent,
    createSvelteTable, 
    getCoreRowModel, 
    getExpandedRowModel 
  } from '$lib/table';
  import ExpandButton from './_components/expand-button.svelte';
  import { type UserProfile } from '$lib/services/user-profile'; 
  import { type UserProfile, userProfiles } from '$lib/services/user-profile'; 

  const colHelp = createColumnHelper<UserProfile>();

  const columnDefs = [
    colHelp.display({
      id: 'expansion',
      cell: ({ row }) =>
        renderComponent(ExpandButton, {
          onclick: row.getToggleExpandedHandler(),
          canExpand: row.getCanExpand(),
          isExpanded: row.getIsExpanded()
        })
    }),
    colHelp.accessor('name', { header: 'Name' }),
    colHelp.accessor('age', { header: 'Age' }),
    colHelp.accessor('email', { header: 'Email' }),
    colHelp.accessor('phone', { header: 'Phone' })
  ];

  // Define a reactive state to track the row expansion state.
  let expandedState: ExpandedState = $state({});

  // Create the table with expansion configuration
  const table = createSvelteTable({ 
    data: userProfiles, 
    columns: columnDefs, 
    state: { 
      get expanded() { 
        return expandedState; 
      } 
    }, 
    getSubRows: (row) => row.friends, 
    onExpandedChange(updater) { 
      if (updater instanceof Function) { 
        expandedState = updater(expandedState); 
      } else { 
        expandedState = updater; 
      } 
    }, 
    getCoreRowModel: getCoreRowModel(), 
    getExpandedRowModel: getExpandedRowModel() 
  }); 
</script>

Rendering the Table with Expansion Controls

Finally, we render the table and include a button that allows users to expand or collapse all rows at once.

<div>
  <h2>Actions</h2>
  <hr />
  <button onclick={table.getToggleAllRowsExpandedHandler()}>
    {#if table.getIsAllRowsExpanded()}
      Collapse All
    {:else}
      Expand All
    {/if}
  </button>
</div>

<table>
  <thead>
    <tr>
      {#each table.getHeaderGroups() as headerGroup}
        {#each headerGroup.headers as header}
          <th>
            <FlexRender content={header.column.columnDef.header} context={header.getContext()} />
          </th>
        {/each}
      {/each}
    </tr>
  </thead>
  <tbody>
    {#each table.getRowModel().rows as row}
      <tr>
        {#each row.getVisibleCells() as cell}
          <td>
            <FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
          </td>
        {/each}
      </tr>
    {/each}
  </tbody>
</table>

The expanded rows will automatically be included in the table's row model, thanks to the getExpandedRowModel plugin. The table will show both parent rows and their expanded children when the expansion state changes.