spindle.by jsj
← All products
INERTIA TABLE COMPONENT / IN DEVELOPMENT

spindle/table

Serious tables. No table routes.

Describe a table once in fluent PHP. It arrives as a native Inertia prop, and one Svelte component renders it: sorting, filters, grouping, saved views and bulk actions included.

Read the docs
spindle/table in under a minuteMade with spindle/story, naturally.

Describe it once

A table is described in PHP: the query, the columns, the filters, the actions. It reaches the page as an ordinary Inertia prop, and one component renders it.

return Inertia::render('Projects/Index', [
    'projects' => Table::make('projects')
        ->query(Project::query()->where('team_id', $request->user()->team_id))
        ->columns([
            TextColumn::make('name')->searchable()->sortable(),
            TextColumn::make('status')->badge(),
            TextColumn::make('budget')->money('USD')->sortable(),
        ])
        ->filters([
            SelectFilter::make('status')->options([
                'active' => 'Active', 'completed' => 'Completed',
            ]),
        ])
        ->savedViews('team:'.$request->user()->team_id),
]);
<script lang="ts">
  import { Table } from '@spindle/table-svelte';
  import type { TablePayload } from '@spindle/table-core';
  let { projects }: { projects: TablePayload } = $props();
</script>

<Table table={projects} prop="projects" />

That is the whole integration. The getting-started guide has the full controller.

No table routes

There is no table API to build, secure or keep in sync. Sorting, filtering and paging are ordinary Inertia partial reloads of the page you are already on. Actions, inline edits and saved views post back to that same page, run through its own middleware and policies, and the page answers with fresh props in the same response.

Inline edits are optimistic and only the edited row comes back. Validation errors land in the normal Inertia error bag.

Fast, even with thousands of rows

Rows are virtualized, so 5,000 loaded rows cost about the same as 100. The DOM holds roughly what is on screen.

Desktop (Apple M4) Before Now
First render, 1,000 rows 986 ms 77 ms
First render, 5,000 rows 4,008 ms 85 ms
Select all, 5,000 rows 1,157 ms 13 ms
DOM elements, 5,000 rows 360,137 2,459

Measured side by side with Filament on twin tables, interactions like sorting, paging and search are 25–50% faster, and up to twice as fast with 100 rows per page or a slower CPU. Responses are 5–7× smaller, because only the table data travels, as JSON. The benchmarks have every number, the setup and the caveats, including where Filament wins.

Feels like an app

The table on a phone, with rows shown as stacked cards

  • A sticky, translucent header, hairline rows, segmented views and filter chips.
  • Keyboard first: a filter menu on f or /, and a grid with a single Tab stop and spreadsheet-style arrow keys.
  • Drag or Shift to select ranges, with a floating bar for bulk actions.
  • A spreadsheet-style grid: double-click, Enter or just type to edit a cell, select ranges, and copy as TSV or HTML.
  • Drag a header to group, with per-group totals and a drag-and-drop group panel.
  • Responsive cards and collapsible details on small screens.
  • Every color, size and motion value is a --st-* CSS variable, and every part has a class, a data-slot and an override.
  • Accessible by default: 3:1 control contrast, forced-colors support and screen reader announcements.

Built for real work

  • Columns: text, badges, money and dates, sanitized HTML and Markdown, syntax-highlighted code, images, icons, colors, and editable cells.
  • Filters: select, range, date and custom filters, remote option search, and a nested AND/OR rule builder.
  • Actions: toolbar, row, cell and bulk actions, policy-backed CRUD helpers, and confirmations. The PHP side authorizes and executes; the UI is yours.
  • Saved views: preset tabs, private views, defaults and favorites.
  • Imports and exports: streamed CSV export, queued CSV and XLSX exports, and queued CSV imports with private failure reports.
  • Background jobs: bulk work on queued workers, with progress, cancellation and history.

Headless at the core

Package Purpose
spindle/table Query, schema, authorization, mutation and saved-view layer
@spindle/table-core Headless state, selection, views, formatting and Inertia navigation. Zero runtime dependencies.
@spindle/table-svelte Accessible prebuilt components on shadcn-svelte, bits-ui and Tailwind CSS v4

The core does not know what renders it. Build a completely custom UI on it, or a new adapter on the wire protocol. Vue and React adapters are on the list.

Coming from Filament?

The API is inspired by Filament, so it should feel familiar. It is not a drop-in adapter, and full feature parity is still a work in progress. Coming from Filament shows what maps across and what is left.

Status

In development, and not yet on Packagist or npm. It needs PHP 8.3+, Laravel 12 or 13, Inertia 3.4+ and Svelte 5. Start with getting started.