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.
- π― Complete Table State Management - Save filters, sort, columns, column order, widths, and more
- ποΈ TanStack DB Persistence - Reactive collections with IndexedDB storage
- π Local-First Architecture - Works offline, syncs when online
- π Search & Filter - Find views quickly with live search
- β¨οΈ Keyboard Navigation - Arrow keys, Enter, Escape support
- βοΈ Inline Rename - Rename views with explicit save/cancel buttons
- π Usage Tracking - Track how often views are used
- π Recent Views - Quick access to last 7 days, top 5 views
- π Duplicate Views - Copy existing views with one click
βοΈ Update vs Save New - Smart split button when view is modified- β Column Validation - Gracefully handles missing columns
- π¨ Tailwind CSS Styled - Beautiful, accessible UI out of the box
- β‘ Reactive Queries - Automatic UI updates via TanStack DB collections
- π¦ TypeScript Support - Full type definitions included
npm install svelte-table-views-tanstackThis package requires:
svelte: ^4.0.0 || ^5.0.0@tanstack/db: ^0.5.0
If 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
}Dropdown component for selecting, searching, renaming, and deleting saved views.
Props:
- None (controlled via stores)
Events:
viewSelected: CustomEvent<{ view: SavedView }>- Fired when user selects a viewdeleteView: CustomEvent<{ id: string }>- Fired when user deletes a view
Features:
- Search views by name (live filtering)
- Recent views section (last 7 days, top 5)
- All views section (alphabetically sorted)
- Inline rename with save/cancel buttons
- Duplicate view
- Delete with confirmation
- Keyboard navigation (Arrow keys, Enter, Escape)
Modal component for saving new table views.
Props:
open: boolean- Controls modal visibility (usebind:open)config: TableConfig- Table configuration to saveoriginalQuery?: string- Optional: original NL query that generated this config
Events:
save: CustomEvent<{ id: string; name: string }>- Fired when view is saved
Features:
- Name input (required, max 100 chars)
- Description input (optional, max 500 chars)
- Duplicate name detection
- Storage limit enforcement (50 views)
- Preview of what's being saved
- Keyboard shortcuts (Esc to cancel, Ctrl+Enter to save)
Writable store containing all saved views (synced with TanStack DB).
import { savedViews } from 'svelte-table-views-tanstack'
$savedViews // SavedView[]Derived store containing recent views (last 7 days, top 5, sorted by lastUsed).
import { recentViews } from 'svelte-table-views-tanstack'
$recentViews // SavedView[]Writable store tracking the currently active view ID.
import { activeViewId } from 'svelte-table-views-tanstack'
$activeViewId // string | nullWritable store tracking whether the active view has been modified.
import { activeViewModified } from 'svelte-table-views-tanstack'
$activeViewModified // booleanDerived store containing the full active view object.
import { activeView } from 'svelte-table-views-tanstack'
$activeView // SavedView | nullSave 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
}
})Load an existing view from TanStack DB. Updates usage statistics and sets as active.
const view = await viewActions.load('view-id-123')Update an existing view in TanStack DB.
await viewActions.update('view-id-123', {
config: updatedConfig,
description: 'Updated description'
})Delete a view from TanStack DB.
await viewActions.delete('view-id-123')Rename a view in TanStack DB.
await viewActions.rename('view-id-123', 'New View Name')Mark the active view as modified (shows split button).
viewActions.markModified()Clear the active view.
viewActions.clearActive()Check if a view name already exists.
const exists = await viewActions.nameExists('My View')Get storage usage statistics.
const stats = await viewActions.getStorageStats()
console.log(`${stats.count}/${stats.limit} views (${stats.percentFull}% full)`)interface TableConfig {
filters: FilterCondition[]
sort: SortConfig | null
columns: string[]
columnOrder: string[]
columnWidths: Record<string, number>
pageSize: number
grouping?: string[]
}interface FilterCondition {
columnId: string
operator: string
value: any
}interface SortConfig {
columnId: string
direction: 'asc' | 'desc'
}interface 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
}type 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 initTarget the component classes in your global CSS:
/* Override ViewSelector styles */
.view-selector button {
/* Your styles */
}
/* Override SaveViewModal styles */
.save-view-modal {
/* Your styles */
}- Modern browsers with
crypto.randomUUID()support - IndexedDB support required (with localStorage fallback)
- TanStack DB browser support
- No IE11 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)
- svelte-table-views - localStorage version (zero dependencies)
- @shotleybuilder/svelte-table-kit - Headless TanStack Table wrapper for Svelte
- TanStack Table - Headless table library
- TanStack DB - Local-first reactive database
- π Report a Bug
- π‘ Request a Feature
- π Read the Docs