Row Expansion


Useful Links

Actions


Table Demo


NameAgeEmailPhone
Kendra Weissnat-Ziemann41Lucious23@hotmail.com(852) 838-0609
Eleanor Hirthe28Dion38@gmail.com(279) 388-6308
Helen Nolan22Casimir.Stokes@gmail.com(618) 580-1053
Richard Bartell-Conn36Adolf51@gmail.com(359) 280-6211
Hubert Gorczany84Madaline27@hotmail.com(444) 771-0168
Clint Jenkins89Linnie22@hotmail.com(796) 471-3480
Mr. Melvin Leuschke-MacGyver66Ressie.Smith@yahoo.com(383) 241-5706
Eddie Dickens IV32Tad18@yahoo.com(698) 637-5272
Christian Conroy77Irwin.Lubowitz@gmail.com(601) 550-2439
Earnest West83Garrett_Crist@hotmail.com(761) 925-7502

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.