@repo/table
Data grid system built on TanStack Table with URL-based state management.
Usage
Query Endpoint
Create a query endpoint that handles pagination, sorting, and filtering:
// users.remote.ts
import { query } from '$app/server'
import { countSelect, overviewToQuery } from '@repo/db'
import { createOverviewQuerySchema } from '@repo/table/remote'
export const getUsersQuery = query(
createOverviewQuerySchema(userFilterSchema),
async (data) => {
const { db, usersTable } = await getTenantDb()
return await overviewToQuery(
db
.select({
id: usersTable.id,
name: usersTable.name,
count: countSelect, // Required for pagination
})
.from(usersTable)
.$dynamic(),
usersTable,
data,
)
},
)
DataGrid Component
<script lang="ts">
import type { QueryDataPoint } from '@repo/table/remote'
import { getUsersQuery } from '$lib/domains/users/database/users.remote'
import { userFilterSchema } from '$lib/domains/users/utils/schemas'
import { DataGrid, useDataGrid } from '@repo/table/remote'
import { createColumnHelper } from '@tanstack/table-core'
const ch = createColumnHelper<QueryDataPoint<typeof getUsersQuery>>()
const { table, dataGrid } = await useDataGrid({
tableOptions: {
columns: [
ch.accessor('name', { header: 'Name' }),
ch.accessor('createdAt', { header: 'Created At' }),
],
state: {
sorting: [{ id: 'name', desc: false }],
},
},
query: getUsersQuery,
filterSchema: userFilterSchema,
getRowLink: row => `users/${row.id}`,
})
</script>
<DataGrid.Root title="Users" {table} {dataGrid} add="/users/add">
<DataGrid.FilterForm schema={userFilterSchema} />
</DataGrid.Root>
Custom Filter Fields
Use DataGrid.FilterField inside DataGrid.FilterForm for custom field rendering (e.g., select dropdowns with dynamic options):
<DataGrid.FilterForm schema={userFilterSchema}>
{#snippet children({ fields })}
<DataGrid.FilterField field={fields.name} />
<DataGrid.FilterField field={fields.status} input={TypedForm.SelectInput} optionItems={statusOptions} />
<DataGrid.FilterField field={fields.roleId} input={TypedForm.SelectInput} optionItems={roleOptions} label="Role" />
{/snippet}
</DataGrid.FilterForm>
FilterField accepts the same props as TypedForm.Field (input, optionItems, label, etc.). Apply/Clear buttons are handled automatically by FilterForm.
Components
| Component | Description |
|---|---|
DataGrid.Root | Main container with title, add button |
DataGrid.FilterForm | Auto-generated filter form from schema |
DataGrid.FilterField | Custom filter field (use inside FilterForm with children snippet) |
Table | Lower-level table component |
Pagination | Pagination controls |
useDataGrid Options
| Option | Description |
|---|---|
tableOptions | TanStack Table options (columns, state) |
query | Remote query function |
filterSchema | Zod schema for filters |
getRowLink | Function to generate row links |
Row Selection
Enable checkbox-based row selection via tableOptions.enableRowSelection (native TanStack option). A checkbox column is automatically prepended.
<script lang="ts">
const { table, dataGrid, selectedRows, resetRowSelection } = await useDataGrid({
tableOptions: {
columns: [...],
enableRowSelection: true,
},
query: getUsersQuery,
})
function handleBulkDelete() {
// selectedRows contains the actual data items
console.log(selectedRows)
resetRowSelection()
}
</script>
A checkbox column is automatically prepended. The header checkbox supports select-all with indeterminate state.
URL State
Sorting, pagination, filters, and column configuration are automatically synced to URL params for shareable links.
Column visibility uses a cols param listing hidden column IDs (e.g. ?cols=email,status). No cols param means all columns visible.
To disable column management entirely, set enableHiding: false in tableOptions. This hides the settings button and disables column toggling.