Overview

Svelte Headless Table is a headless, composable table library for Svelte. It gives you a powerful table view-model (sorting, filtering, grouping, selection, pagination, etc.) while leaving the DOM and styling entirely up to you.

Why headless?

  • Control: You own the markup, accessibility, animations, and design system.
  • Maintainability: A focused API without UI baggage stays small and predictable.
  • Extensibility: Compose only what you need via plugins and your own components.

Core concepts

  • createTable(data, plugins): Builds a table instance from your data (an array, or a getter for reactive data) and plugin config
  • createColumns([…]): Declares how to read and present fields from your data items
  • createViewModel(columns): Derives header rows, body rows, and attributes to spread on markup, as plain reactive values under vm.current

Installation

Install with your package manager of choice:

npm i -D @humanspeak/svelte-headless-table
npm i -D @humanspeak/svelte-headless-table
yarn add -D @humanspeak/svelte-headless-table
yarn add -D @humanspeak/svelte-headless-table
pnpm add -D @humanspeak/svelte-headless-table
pnpm add -D @humanspeak/svelte-headless-table

Requires Svelte 5.30 or later. Upgrading from 6.x? See Migrating to v7.

Minimal example

<script lang="ts">
  import { createTable, Render } from '@humanspeak/svelte-headless-table'

  const data = [
    { name: 'Ada Lovelace', age: 21 },
    { name: 'Barbara Liskov', age: 52 },
    { name: 'Richard Hamming', age: 38 }
  ]

  const table = createTable(data)
  const columns = table.createColumns([
    table.column({ header: 'Name', accessor: 'name' }),
    table.column({ header: 'Age', accessor: 'age' })
  ])

  const vm = table.createViewModel(columns)
</script>

<table {...vm.current.tableAttrs}>
  <thead>
    {#each vm.current.headerRows as headerRow (headerRow.id)}
      <tr {...headerRow.current.attrs}>
        {#each headerRow.cells as cell (cell.id)}
          <th {...cell.current.attrs}>
            <Render of={cell.render()} />
          </th>
        {/each}
      </tr>
    {/each}
  </thead>
  <tbody {...vm.current.tableBodyAttrs}>
    {#each vm.current.pageRows as row (row.id)}
      <tr {...row.current.attrs}>
        {#each row.cells as cell (cell.id)}
          <td {...cell.current.attrs}>
            <Render of={cell.render()} />
          </td>
        {/each}
      </tr>
    {/each}
  </tbody>
</table>
<script lang="ts">
  import { createTable, Render } from '@humanspeak/svelte-headless-table'

  const data = [
    { name: 'Ada Lovelace', age: 21 },
    { name: 'Barbara Liskov', age: 52 },
    { name: 'Richard Hamming', age: 38 }
  ]

  const table = createTable(data)
  const columns = table.createColumns([
    table.column({ header: 'Name', accessor: 'name' }),
    table.column({ header: 'Age', accessor: 'age' })
  ])

  const vm = table.createViewModel(columns)
</script>

<table {...vm.current.tableAttrs}>
  <thead>
    {#each vm.current.headerRows as headerRow (headerRow.id)}
      <tr {...headerRow.current.attrs}>
        {#each headerRow.cells as cell (cell.id)}
          <th {...cell.current.attrs}>
            <Render of={cell.render()} />
          </th>
        {/each}
      </tr>
    {/each}
  </thead>
  <tbody {...vm.current.tableBodyAttrs}>
    {#each vm.current.pageRows as row (row.id)}
      <tr {...row.current.attrs}>
        {#each row.cells as cell (cell.id)}
          <td {...cell.current.attrs}>
            <Render of={cell.render()} />
          </td>
        {/each}
      </tr>
    {/each}
  </tbody>
</table>

Next steps