Skip to main content

@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

ComponentDescription
DataGrid.RootMain container with title, add button
DataGrid.FilterFormAuto-generated filter form from schema
DataGrid.FilterFieldCustom filter field (use inside FilterForm with children snippet)
TableLower-level table component
PaginationPagination controls

useDataGrid Options

OptionDescription
tableOptionsTanStack Table options (columns, state)
queryRemote query function
filterSchemaZod schema for filters
getRowLinkFunction 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.