Recipes / Cookbook

Quick patterns for the questions that come up over and over. Each recipe is paired with a live demo or a minimal snippet that runs against the shipping library.

Sort, filter, paginate at the same time

Three of the most-asked features wired together against a 5k-row dataset. Click any header to sort, open the filter icon to filter, use the pager at the bottom to walk pages:

The implementation: register all three features in one tableFeatures(...) call and enable the matching props on <SvGrid>. See Filtering overview and Row sorting.

Select rows + copy / paste a range

Click + drag selects a rectangular cell range; Ctrl/Cmd+C copies as TSV; pasting back from Excel writes through the same cell-write pipeline as inline edit:

The pattern: enable enableCellSelection={true} and let the grid handle the keyboard + paste plumbing. Cell writes go through onCellValueChange so your validator runs on every pasted value.

Bulk actions toolbar

The Gmail / Linear pattern: tick row checkboxes, a sticky toolbar reveals "Mark / Delete / Copy as TSV":

The trick: subscribe to onRowSelectionChange and render your toolbar whenever the count is > 0. The grid doesn't ship a bulk-action component on purpose - your design system's button + toast UI is better than anything we could embed.

Cascade editing (formula-like dependencies)

Editing qty / price cascades into line totals + a summary card:

In onCellValueChange, after writing the cell, recompute the dependent cells in the same row (or in the summary state) and let Svelte 5's reactivity carry the update through. The pattern is essentially "editable cells + a $derived summary".

Validation while editing

Per-column rules: invalid commits get rolled back via setCellValue, and a sidebar logs the rejection so users can audit what was tried:

Use onCellValueChange: validate, and if invalid, call api.setCellValue(rowIndex, columnId, oldValue) to undo. The grid emits a fresh onCellValueChange for the undo write that your validator can recognise and skip.

Column pinning + freezing

Wide 13-column grid: pin Company on the left and Price on the right; the middle scrolls under sticky edges:

The user pins via the column menu - no extra wiring needed if columnFilteringFeature is registered (the menu lives on the same header button). Or call api.pinColumn(columnId, 'left' | 'right' | null) programmatically.

List + chips editors

The two built-in multi-select editors:

Add editorType: 'list' for a single <select> or editorType: 'chips' with editorMultiple={true} for removable tokens. Pass static editorOptions or a (row) => options callback for cascading lists (e.g. City options depend on Country).

Spreadsheet ribbon

An Excel-style Ribbon UI driving the grid via SvGridApi: bold + colour

The ribbon is a regular Svelte component; every button calls api.setCellValue, api.addRow, api.setSort, etc. Status bar reads getDisplayedRows() for the live aggregate.

Master / detail

A hierarchical row that expands to reveal a child grid:

Same flatten pattern as tree rows: a flat visibleRows derivation, plus a custom cell snippet that renders the detail grid inline when the row is expanded.

Forms-in-grid (master grid + side form)

Click a row to load it into a tabbed detail form on the right; edits flow back to the grid live:

Pattern: onActiveCellChange -> set activeRowId -> draft = $state.snapshot(activeRow) -> form binds against draft. On save, write draft back into the rows array. Use $state.snapshot(), not structuredClone(), on Svelte 5 state proxies - the latter throws DataCloneError.

Saved views / persistence

Pivot-lite with chips + saved views stored in localStorage:

The pattern: a view: { groupBy, sortBy, filters, columns } object that you serialise into localStorage under a versioned key ('my-app:view:v1'). On load, hydrate the state from the saved view and call the matching api.set* methods.

Theming studio

Live token playground: brand color, density, radius, font, dark/light, zebra. The CSS snippet at the bottom is copy-ready:

Theming is --sg-* custom properties - see Tailwind integration for the full token list.

Localisation + RTL switching

Six locales (en, de, fr-CA, ja, ar, he); headers, currencies, dates, and the grid's own scrollbar flip:

Set <html dir="..."> and <html lang="..."> based on the locale. Per-column format: { type: 'currency', locales } overrides the document locale for specific columns.

Quarterly P&L print pack: cover page, repeat-on-page headers, page- size + orientation, CSV / HTML download, browser-native PDF:

api.print({ title, columns, rows, orientation, pageSize }) opens a sandbox window with an isolated HTML document and calls .print() on it. The popup pattern is what isolates your print stylesheet from your app's CSS.

Healthcare EMR (role-based editing)

Inpatient board with vitals sparklines, risk score, code status, allergy chips, role-based editing (viewer / nurse / physician / admin):

The role gate uses editable: (ctx) => roleCanEdit(currentRole, ctx.column.id). That lambda runs per-cell, so you get column + role granularity without a separate config table.

Live shipment / fleet board

Streaming-style live update at 2-3 Hz with cell-level flash on change, ETA delta colors, alert chips:

The pattern: a $state rows array that you mutate from a WebSocket/SSE handler. The render component diffs at the data-array level, so reassigning the array (rows = updated) is what triggers the visible repaint.

Compliance / approval queue

L1 / L2 / L3 approval chain with live SLA timer, role-gated approve / return / reject buttons, immutable case-history audit panel:

This is the pattern for any "this row needs a decision; some users can make it, some can't" workflow. Status changes go through a state machine you own; the grid only renders the current state.

Field service dispatch

Dispatcher board: priority + status + tech editable inline, SLA tone gauge, today-timeline cell, tech capacity panel, live status stream:

Same editable: (ctx) => ... per-cell pattern for tech/status. The today-timeline cell is a positioned-bar in a single wide cell - see the pivot doc for that pattern.

Gantt chart in a grid

Project plan with a wide custom Schedule cell: bars positioned by start/end %, phase coloring, progress fill, today line, overdue glow:

No special Gantt mode - it's a single wide cell per row whose snippet positions absolute bars by (start - projectStart) / projectSpan * 100. Axis above the grid uses the same math.

Scheduler (single-day appointments)

Providers as rows, an hour axis, click any appointment to edit it in the side panel. Now-line ticks live:

Same single-wide-cell pattern as the Gantt. The axis-wrap inside the grid wrapper uses ResizeObserver so the axis width matches the schedule column - that's what keeps the now-line aligned between the header and the cells.

Trash truck timeline (animated)

Public-works dispatcher: each truck glides along its day-long route with spinning wheels, stops, and live fill levels:

The mover uses transition: left 220ms linear so the new position animates in. The bounce + wheel spin are CSS keyframes; no JS animation loop required.

CRM - sales pipeline

Deal board with stage chips, weighted forecast bar, inline stage + probability editing, deal detail aside with activity feed:

This is the canonical example of mixing inline edit + a side panel that mirrors the active row.

Enterprise admin dashboard

CRUD-heavy users board: inline role / status / MFA edit, bulk activate / deactivate / delete, invite dialog, permissions matrix, live audit log:

The audit log is the recurring pattern: every mutation appends an entry to a $state array; the right-side panel renders the array. Delete + Invite use simple <dialog>-style modals.

E-commerce seller panel

Tabbed Amazon-seller view: catalog with SVG thumbnails, inventory bars vs reorder threshold, live orders pipeline, pricing rules:

The tab strip is just chip buttons that swap which dataset / columns the grid receives. One grid component, four tabs - share styling and behavior without four separate mounts.

Long-form recipes

The recipes above pair a paragraph + a demo. These deeper write-ups have their own pages:

See also