Modern headless datatable engine — TypeScript, plugin-based, full-stack ready.
Trellis is a framework-agnostic data table library with a plugin architecture, immutable state management, and a standardized query protocol for front-end and back-end integration.
- Headless — No UI, no CSS, no opinions. Bring your own components.
- Plugin Architecture — Sort, filter, paginate, fetch remote data — each is an independent plugin.
- TypeScript Native — Full generic support
Trellis<T>, type-safe throughout. - Immutable State — Every state change produces a new frozen snapshot.
- EventBus — Isolated event listeners; one error won't crash others.
- Slot System — Named rendering slots for complete UI customization.
- Full-Stack Protocol —
TrellisQuery / TrellisResponseunifies frontend and backend interfaces. - Server Helpers — Query parsing, SQL construction, and ORM adapters.
- Tree-Shakeable — Install only the plugins you need.
- React Adapter — First-class React support via
@trellisjs/react, including sticky header.
| Package | Description |
|---|---|
@trellisjs/core |
Core engine — state, plugins, slots, events |
@trellisjs/plugin-sort |
Column sorting (asc/desc) |
@trellisjs/plugin-filter |
Global text search and per-column filtering with independent debounce |
@trellisjs/plugin-pagination |
Page navigation and page size |
@trellisjs/plugin-datasource |
Remote API data fetching |
@trellisjs/plugin-selection |
Row selection (single, multi, range) |
@trellisjs/plugin-column-visibility |
Dynamic column show/hide |
@trellisjs/plugin-column-pinning |
Pin columns to left/right with CSS sticky |
@trellisjs/plugin-column-resizing |
Column resizing — drag to resize with min/max constraints |
@trellisjs/plugin-state-save |
Persist UI state to browser storage, auto-restore on reload |
@trellisjs/plugin-row-expansion |
Expand rows to show custom detail content |
@trellisjs/plugin-export-csv |
CSV export — browser download or get string |
@trellisjs/plugin-virtual-scroll |
Virtual scrolling for large datasets |
@trellisjs/react |
React hooks and components |
@trellisjs/server |
Backend query parser and SQL builder |
@trellisjs/server-prisma |
Prisma ORM adapter |
npm install @trellisjs/core @trellisjs/react @trellisjs/plugin-sort @trellisjs/plugin-filter @trellisjs/plugin-pagination @trellisjs/plugin-selection @trellisjs/plugin-column-visibilityimport { useTrellis } from '@trellisjs/react';
import { createSortPlugin } from '@trellisjs/plugin-sort';
import { createFilterPlugin } from '@trellisjs/plugin-filter';
import { createPaginationPlugin } from '@trellisjs/plugin-pagination';
import type { ColumnDef } from '@trellisjs/core';
interface User {
id: number;
name: string;
email: string;
age: number;
}
const columns: ColumnDef<User>[] = [
{ id: 'name', accessor: 'name', header: 'Name' },
{ id: 'email', accessor: 'email', header: 'Email' },
{ id: 'age', accessor: 'age', header: 'Age' },
];
function UserTable({ data }: { data: User[] }) {
const { api } = useTrellis<User>({
data,
columns,
plugins: [
createSortPlugin(),
createFilterPlugin(),
createPaginationPlugin(),
],
});
const state = api.getState();
return (
<div>
<input
placeholder="Search..."
onChange={(e) => api.emit('filter:change', { value: e.target.value })}
/>
<table>
<thead>
<tr>
{columns.map((col) => (
<th key={col.id} onClick={() => api.emit('sort:toggle', { columnId: col.id })}>
{String(col.header)} {state.sort?.columnId === col.id ? (state.sort.direction === 'asc' ? '↑' : '↓') : ''}
</th>
))}
</tr>
</thead>
<tbody>
{state.data.map((row) => (
<tr key={row.id}>
{columns.map((col) => (
<td key={col.id}>{String(row.original[col.accessor as keyof User] ?? '')}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}import { Trellis } from '@trellisjs/core';
import { createSortPlugin } from '@trellisjs/plugin-sort';
const trellis = new Trellis({
data: [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
],
columns: [
{ id: 'name', accessor: 'name', header: 'Name' },
{ id: 'age', accessor: 'age', header: 'Age' },
],
plugins: [createSortPlugin()],
});
// Subscribe to state changes
trellis.api.subscribe((state) => {
console.log('Data updated:', state.data);
});
// Sort by age descending
trellis.api.emit('sort:toggle', { columnId: 'age' });import { parseQuery, buildWhere, buildOrderBy } from '@trellisjs/server';
import { createPrismaAdapter } from '@trellisjs/server-prisma';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const adapter = createPrismaAdapter(prisma);
// In your API handler:
app.get('/api/users', async (req, res) => {
const result = await adapter.query('user', req.query);
res.json(result);
});@trellisjs/core ← State management, plugin system, events, slots
├── plugin-sort ← Independent sorting plugin
├── plugin-filter ← Independent filtering plugin
├── plugin-pagination ← Independent pagination plugin
├── plugin-datasource ← Remote data fetching plugin
├── plugin-selection ← Row selection plugin
├── plugin-column-visibility ← Column visibility plugin
├── plugin-column-pinning ← Column pinning plugin
├── plugin-column-resizing ← Column resizing plugin
├── plugin-state-save ← State persistence plugin
├── plugin-row-expansion ← Row expansion plugin
├── plugin-export-csv ← CSV export plugin
├── plugin-virtual-scroll ← Virtual scrolling plugin
└── react ← React adapter (useTrellis hook)
@trellisjs/server ← Backend query parser
└── server-prisma ← Prisma ORM adapter
Each plugin:
- Implements
TrellisPlugininterface (name,install, optionaldestroy) - Receives
TrellisAPIduring installation - Reads and modifies state via
api.setState() - Communicates via
api.emit()/api.on()events - Can register rendering slots via
api.registerSlot()
Trellis is in alpha (v0.1.0-alpha). Core architecture is stable, but feature coverage is limited to basic sort/filter/pagination. See the roadmap for planned features.
MIT