Save and restore table view configurations with TanStack DB persistence for Svelte applications
Save and restore table view configurations (filters, sorting, columns) with TanStack DB persistence. Built for Svelte 5 apps using the TanStack stack. Features reactive queries, local-first storage with IndexedDB, and seamless integration with TanStack Table. Perfect for data-heavy applications that need user-customizable table views with robust persistence.
Note: This is the TanStack DB version. For a zero-dependency version using browser localStorage, see svelte-table-views.
npm install svelte-table-views-tanstack
This package requires:
svelte: ^4.0.0 || ^5.0.0@tanstack/db: ^0.5.0If you don't have TanStack DB installed:
npm install @tanstack/db
<script lang="ts">
import { ViewSelector, SaveViewModal, viewActions, activeViewId, activeViewModified } from 'svelte-table-views-tanstack'
import type { TableConfig, SavedView } from 'svelte-table-views-tanstack'
let showSaveModal = false
let capturedConfig: TableConfig | null = null
// Your table state
let filters = []
let sort = null
let columns = ['id', 'name', 'email']
let columnOrder = ['id', 'name', 'email']
</script>
<div class="flex items-center justify-between gap-4 mb-4">
<!-- View Selector Dropdown -->
<ViewSelector on:viewSelected={handleViewSelected} />
<!-- Save/Update Button -->
{#if $activeViewId && $activeViewModified}
<!-- Split button when view is modified -->
<div class="inline-flex">
<button on:click={handleUpdateView}>Update View</button>
<button on:click={openSaveModal}>Save New</button>
</div>
{:else}
<button on:click={openSaveModal}>Save View</button>
{/if}
</div>
<!-- Your Table Component -->
<YourTable {filters} {sort} {columns} {columnOrder} />
<!-- Save View Modal -->
{#if showSaveModal && capturedConfig}
<SaveViewModal
bind:open={showSaveModal}
config={capturedConfig}
on:save={handleViewSaved}
/>
{/if}
function openSaveModal() {
capturedConfig = {
filters,
sort,
columns,
columnOrder,
columnWidths: {},
pageSize: 25
}
showSaveModal = true
}
async function handleViewSelected(event: CustomEvent<{ view: SavedView }>) {
const view = event.detail.view
// Apply saved config to your table
filters = view.config.filters
sort = view.config.sort
columns = view.config.columns
columnOrder = view.config.columnOrder
}
async function handleUpdateView() {
if ($activeViewId) {
await viewActions.update($activeViewId, {
config: {
filters,
sort,
columns,
columnOrder,
columnWidths: {},
pageSize: 25
}
})
}
}
function handleViewSaved(event: CustomEvent<{ id: string; name: string }>) {
console.log('View saved:', event.detail.name)
}
This package uses TanStack DB Collections for storage:
UI Components (Svelte)
↕
Svelte Stores (reactive bridge)
↕
TanStack DB Collection
↕
IndexedDB (localStorage fallback)
Views are stored in a TanStack DB collection with key: 'svelte-table-views-saved-views'
The package uses dynamic imports and browser guards to ensure safe server-side rendering:
// Collection initialized only in browser
if (browser) {
const { createCollection, localStorageCollectionOptions } = await import('@tanstack/db')
// ... collection setup
}
<ViewSelector>Dropdown component for selecting, searching, renaming, and deleting saved views.
Props:
Events:
viewSelected: CustomEvent<{ view: SavedView }> - Fired when user selects a viewdeleteView: CustomEvent<{ id: string }> - Fired when user deletes a viewFeatures:
<SaveViewModal>Modal component for saving new table views.
Props:
open: boolean - Controls modal visibility (use bind:open)config: TableConfig - Table configuration to saveoriginalQuery?: string - Optional: original NL query that generated this configEvents:
save: CustomEvent<{ id: string; name: string }> - Fired when view is savedFeatures:
savedViewsWritable store containing all saved views (synced with TanStack DB).
import { savedViews } from 'svelte-table-views-tanstack'
$savedViews // SavedView[]
recentViewsDerived store containing recent views (last 7 days, top 5, sorted by lastUsed).
import { recentViews } from 'svelte-table-views-tanstack'
$recentViews // SavedView[]
activeViewIdWritable store tracking the currently active view ID.
import { activeViewId } from 'svelte-table-views-tanstack'
$activeViewId // string | null
activeViewModifiedWritable store tracking whether the active view has been modified.
import { activeViewModified } from 'svelte-table-views-tanstack'
$activeViewModified // boolean
activeViewDerived store containing the full active view object.
import { activeView } from 'svelte-table-views-tanstack'
$activeView // SavedView | null
viewActions.save(input: SavedViewInput): Promise<SavedView>Save a new view to TanStack DB collection.
const newView = await viewActions.save({
name: 'High Priority Items',
description: 'Items with priority > 5',
config: {
filters: [{ columnId: 'priority', operator: 'greaterThan', value: 5 }],
sort: { columnId: 'createdAt', direction: 'desc' },
columns: ['id', 'name', 'priority'],
columnOrder: ['priority', 'name', 'id'],
columnWidths: {},
pageSize: 25
}
})
viewActions.load(id: string): Promise<SavedView | undefined>Load an existing view from TanStack DB. Updates usage statistics and sets as active.
const view = await viewActions.load('view-id-123')
viewActions.update(id: string, updates: Partial<SavedView>): Promise<void>Update an existing view in TanStack DB.
await viewActions.update('view-id-123', {
config: updatedConfig,
description: 'Updated description'
})
viewActions.delete(id: string): Promise<void>Delete a view from TanStack DB.
await viewActions.delete('view-id-123')
viewActions.rename(id: string, newName: string): Promise<void>Rename a view in TanStack DB.
await viewActions.rename('view-id-123', 'New View Name')
viewActions.markModified(): voidMark the active view as modified (shows split button).
viewActions.markModified()
viewActions.clearActive(): voidClear the active view.
viewActions.clearActive()
viewActions.nameExists(name: string, excludeId?: string): Promise<boolean>Check if a view name already exists.
const exists = await viewActions.nameExists('My View')
viewActions.getStorageStats(): Promise<{ count: number; limit: number; percentFull: number }>Get storage usage statistics.
const stats = await viewActions.getStorageStats()
console.log(`${stats.count}/${stats.limit} views (${stats.percentFull}% full)`)
TableConfiginterface TableConfig {
filters: FilterCondition[]
sort: SortConfig | null
columns: string[]
columnOrder: string[]
columnWidths: Record<string, number>
pageSize: number
grouping?: string[]
}
FilterConditioninterface FilterCondition {
columnId: string
operator: string
value: any
}
SortConfiginterface SortConfig {
columnId: string
direction: 'asc' | 'desc'
}
SavedViewinterface SavedView {
// Identity
id: string
name: string
description?: string
// Configuration
config: TableConfig
// Optional: original NL query for reference
originalQuery?: string
// Metadata
createdAt: number
updatedAt: number
usageCount: number
lastUsed: number
}
SavedViewInputtype SavedViewInput = Omit<SavedView, 'id' | 'createdAt' | 'updatedAt' | 'usageCount' | 'lastUsed'>
The package automatically validates columns when loading a view:
function handleViewSelected(event: CustomEvent<{ view: SavedView }>) {
const view = event.detail.view
const availableColumns = ['id', 'name', 'email', 'created_at']
// Filter out missing columns
const validColumns = view.config.columns.filter(col =>
availableColumns.includes(col)
)
// Warn user if columns are missing
const missingColumns = view.config.columns.filter(col =>
!availableColumns.includes(col)
)
if (missingColumns.length > 0) {
alert(`Some columns no longer exist: ${missingColumns.join(', ')}`)
}
// Apply valid config
columns = validColumns
// ... rest of config
}
The default TanStack DB storage key is 'svelte-table-views-saved-views'. To customize it, modify src/lib/stores/saved-views.ts:
viewsCollection = createCollection(
localStorageCollectionOptions<SavedView, string>({
storageKey: 'my-app-saved-views', // Change this
getKey: (item) => item.id
})
)
The default limit is 50 views. To change it, modify the getStorageStats function:
async getStorageStats() {
const views = get(savedViews)
const count = views.length
const limit = 100 // Change this
return { count, limit, percentFull: Math.round((count / limit) * 100) }
}
The package uses Tailwind CSS classes. If you're not using Tailwind, you have two options:
npm install -D tailwindcss
npx tailwindcss init
Target the component classes in your global CSS:
/* Override ViewSelector styles */
.view-selector button {
/* Your styles */
}
/* Override SaveViewModal styles */
.save-view-modal {
/* Your styles */
}
crypto.randomUUID() support| Feature | svelte-table-views | svelte-table-views-tanstack |
|---|---|---|
| Storage | Browser localStorage | TanStack DB (IndexedDB) |
| Dependencies | Zero | @tanstack/db |
| Reactivity | Svelte stores | TanStack DB Collections + Stores |
| Architecture | Simple, direct | Local-first, reactive |
| Use Case | Standalone apps | Apps using TanStack stack |
| Bundle Size | Smaller | Larger (includes TanStack DB) |
Contributions are welcome! Please read CONTRIBUTING.md before submitting PRs.
MIT © Jason (Shotley Builder)