# svelte simple datatables: headless table state for Svelte 5, without the markup

> vincjo/datatables is a Svelte toolkit that owns the logic of a data table, sorting, filtering, pagination, selection and lazy loading, while leaving the markup entirely to you. It suits a Svelte 5 developer who wants table behaviour without a component library dictating the HTML, and it is published as @vincjo/datatables at version 2.8.1.

**vincjo/datatables** — A toolkit for creating data table components with Svelte

- Repository: https://github.com/vincjo/datatables
- Website: https://vincjo.fr/datatables
- Stars: 591 · Forks: 26
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/vincjo-datatables

## Headless means you write the table element yourself

The name of the project is the first clue to the design. The package is called svelte simple datatables, the description calls it a toolkit for creating data table components with Svelte, and the keywords in package.json list filter, headless, lazy-loading, pagination, selection and sort.

Headless is the load-bearing word. The README does not ship a DataTable component that renders a table for you. Instead the library hands you a handler object and a set of rows, and you write the markup around them. The sample in the README is the clearest evidence: it is a plain HTML table with a thead and two column headers, a tbody, and an each block over the handler's rows.

```ts
<script lang="ts">
    import { TableHandler } from '@vincjo/datatables'
    import { someData } from './data'

    const table = new TableHandler(someData, { rowsPerPage: 50 })
</script>

<table>
    <thead>
        <tr>
            <th>First name</th>
            <th>Last name</th>
        </tr>
    </thead>
    <tbody>
        {#each table.rows as row}
            <tr>
                <td>{row.first_name}</td>
                <td>{row.last_name}</td>
            </tr>
        {/each}
    </tbody>
</table>
```

The consequence is worth spelling out, because it is the whole point of the library. There is no styling to fight, no wrapper div with a class you have to override, and no prop names to learn for a column definition. The handler is constructed with data and options, and the table markup is yours, so it inherits your application's CSS, your accessibility attributes and your layout conventions without any work.

The cost is that the library cannot give you the parts of a table that are, in a sense, just markup. Column headers, cell rendering, the empty state, the row template: all of that is on you. The library takes the stateful half and leaves the static half alone.

## The handler object holds the state you would otherwise write yourself

Everything the library does lives in the TableHandler instance created in the sample. It takes the dataset and an options object, and in the sample the only option shown is rowsPerPage: 50.

That option name is the tell for the whole model. Pagination is not a component with a slot for your rows; it is a numeric setting on an object, and the rows you iterate over are already the page you asked for. The same pattern applies to the other keywords: sort, filter, selection and lazy loading are all pieces of state on the handler rather than separate components with props.

The repository name and structure support that reading. The project is built with SvelteKit and the Svelte package tooling, with svelte-kit sync and svelte-package in the package and publish scripts, and the source lives in a single src directory. The svelte.config.js and vite.config.ts files at the top level, along with mdsvex.config.js, indicate that the documentation site is part of the same SvelteKit project, with mdsvex and prism-svelte for rendering Svelte code blocks in the docs.

The documentation site lives at vincjo.fr/datatables, and the README points there for the full API rather than trying to summarise it. That is a reasonable choice for a library whose surface includes several independent features, but it also means the README alone will not tell you everything, and you will be reading the docs site to learn the option names beyond rowsPerPage.

The version in package.json is 2.8.1, and the README carries a badge for Svelte runes v2, which places the current line in the runes era of Svelte rather than the legacy store era.

## Upgrading from v1 is an import path change

The README spends a section on the move from v1 to v2, and the mechanism is unusually gentle. The v1 code is not removed; it is embedded in a legacy namespace, so existing components keep working while you migrate them one at a time.

```bash
- @vincjo/datatables
+ @vincjo/datatables/legacy

- @vincjo/datatables/remote
+ @vincjo/datatables/legacy/remote
```

The README says the point of this is to make the migration easier and to let you upgrade your components progressively by simply modifying imports. Both of the affected import paths are shown, the package root and the remote subpath, so a project using the remote data source needs the same edit applied to two import sites.

The remote subpath is worth a second look, because it is the only part of the API the README names outside the sample. It indicates that fetching data from a server is part of the design rather than something bolted on afterwards, which lines up with the lazy-loading keyword in package.json and with the filter and pagination keywords. A table that pages over a server-side dataset needs a remote source, and the fact that the legacy namespace preserved a /remote entry point implies the v2 line keeps an equivalent.

The practical consequence for a team evaluating the library is that you can install v2 today and leave older components on v1 imports while you rewrite them. That is a real de-risking argument, and it is also a maintenance argument: the compatibility layer only helps if you actually schedule the migration.

## Installing it, and what the first version of your table looks like

Installation is a single npm command, and the README puts it in an apache-tagged code block, which is a quirk of the README formatting rather than a signal about the shell.

```bash
npm i -D @vincjo/datatables
```

The package is installed as a development dependency, which suits a library that is consumed by your application code at build time and has no runtime server component of its own.

Your first step after installing is to construct a handler and read its rows. The sample imports TableHandler from '@vincjo/datatables', imports someData from a local './data' module, and creates the handler with rowsPerPage set to 50. Then you iterate over table.rows in an each block and render each row's fields.

There is nothing else to wire up, and that is both the appeal and the constraint. There is no provider to mount, no plugin to register, and no configuration file. The trade is that the sample gives you a table with two columns and no visible affordances: there is no header button to click for sorting and no pagination control, because the library does not render those either. To make the table interactive you have to bind the handler's state to your own markup, and the exact names for those bindings are documented on the site rather than in the README.

That is the workflow the library is designed around. The handler is a state container, the markup is a projection of that state, and the two are connected by whatever names the documentation specifies.

## Where the boundaries are, and what the repository does not tell you

Two limits are visible from the repository layout and the package metadata, and they are worth naming before you commit.

The first is that the README is a short document for a library with a broad surface. It shows an install command, a migration diff and one sample. Sorting, filtering, selection, lazy loading and the remote data source are named in the keywords and in the diff's import path, but the README does not document their options, their state shapes or their failure behaviour. The documentation site at vincjo.fr/datatables is where that lives, and the README treats it as the primary reference. If you are the kind of reader who wants the whole API on the repository page, this is not that package.

The second is the dependency on Svelte 5's runes model. The README badge marks runes v2, the current version is 2.8.1, and the v1 line survives only in the legacy namespace. A Svelte 4 project cannot use the current line, and one that adopts the legacy import path is on a path the author has already moved away from.

On maintenance, the facts are modest. The last push was on 2026-07-21, the repository has no GitHub releases, and the version number is what tells you where the project stands, since there is no release tag to consult. The licence is MIT, which is permissive and imposes no obligations beyond attribution in distributed copies, and there is no source-available or copyleft restriction to design around. The CHANGELOG.md file is present at the top level and is where the version history is recorded.

The repository also carries CONTRIBUTING.md, an ecosystem.config.cjs for pm2 deployment of the documentation site, and a datatables.code-workspace file, which tells you the maintainer works on the package and its docs in the same editor workspace.

## How this differs from a component table library

The comparison worth making is against the other way to solve the same problem: a Svelte table component that ships its own markup and styling.

A component library table is faster to start with. You drop in a DataTable, pass it data and a column definition, and you get sorting, pagination and selection already rendered and styled. The cost is that the output is the library's HTML. Restyling means overriding classes, adding wrapper elements or writing CSS against another package's selectors, and any accessibility work is limited to whatever props that library exposes. Column definitions are often a separate declarative structure, which means a cell's content has to be expressed as a configuration value rather than as Svelte markup in the place where you want it.

This toolkit takes the opposite position. You get a handler that holds state, and you render from it. A cell can be any Svelte expression or component, because it is your markup. Styling is your stylesheet. The sorting control can be a plain button, a custom component, or anything else you bind to the handler.

The cost of that position is that the library cannot hand you the finished thing. The two rows and two headers in the sample are all the scaffolding you get, and the real work of building an interactive table is still yours. A team that wanted a drop-in table with sorting arrows and a pager will not find one here.

There is also a smaller alternative worth naming: no library at all. A table with client-side sorting and pagination over an in-memory array is a few dozen lines of Svelte, and writing it yourself means zero dependencies and full control. The case for the library is the parts that are tedious rather than hard, which is remote data loading, filtering across several fields, selection state and the bookkeeping that goes with pagination and sorting interacting. If your table is one screen of fifty rows with one sortable column, hand-rolling is reasonable. If it is a paginated, filtered, server-backed table with row selection, that bookkeeping is exactly what this package is for.

## Conclusion

This toolkit fits a Svelte 5 team that wants table state without adopting a component library's markup, and it does not fit a project that wants a finished table UI out of the box, since the sample is plain table elements and every cell is yours to write. Before adopting it, verify the version line you need: v2 is the runes-based line, the v1 API still ships under the /legacy import path, and the migration is a change of import path rather than a rewrite.

## FAQ

### How do I install and use vincjo/datatables in a Svelte 5 project?

Run npm i -D @vincjo/datatables, then import TableHandler from '@vincjo/datatables', construct it with your data and options such as rowsPerPage, and iterate over the handler's rows in an each block inside your own table markup.

### Is vincjo/datatables a table component or a headless library?

It is headless. The README's sample renders a plain HTML table with a thead and tbody and an each block over table.rows, so the library provides the table state and the markup stays with you.

### How do I migrate from v1 to v2 of @vincjo/datatables?

The v1 code is kept in a legacy namespace, so you can upgrade components progressively by changing imports. The affected paths are '@vincjo/datatables' becoming '@vincjo/datatables/legacy' and '@vincjo/datatables/remote' becoming '@vincjo/datatables/legacy/remote'.

### What licence is @vincjo/datatables under?

The package.json lists the licence as MIT, and a LICENSE file is present at the top level of the repository. The package also lists GitHub as the bugs and issues location for the project.

## Sources

- [Issues](https://github.com/vincjo/datatables/issues)
- [License: MIT](https://github.com/vincjo/datatables/blob/main/LICENSE)
- [Project website](https://vincjo.fr/datatables)
- [README](https://github.com/vincjo/datatables/blob/main/README.md)
- [vincjo/datatables on GitHub](https://github.com/vincjo/datatables)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/vincjo-datatables
