Column layout API
setColumnWidth + setColumnPinning + getColumnWidths + getColumnPinning. Save the snapshot to localStorage, restore on reload, drive widths and pins from buttons.
A live, editable Svelte 5 data grid example from the SvGrid gallery (Columns). See the SvGrid documentation for the full API.
About this example
The column layout API of the Svelte 5 data grid. api.getColumnWidths() and api.getColumnPinning() snapshot the current widths and pins into a plain JSON object, api.setColumnWidth(id, width) and api.setColumnPinning({ left, right }) restore it, and named layouts are saved to localStorage so an analyst's setup survives a reload. Buttons drive widths and pins directly to show the calls.
Realistic pattern: an analyst sets up a layout (widths + pins) for a specific job, saves it under a name, and switches between named views later. Underneath:
api.getColumnWidths()+api.getColumnPinning()snapshot the current layout to a plain JSON object.api.setColumnWidth(id, w)+api.setColumnPinning({left,right})restore it.
Views are auto-persisted to localStorage so they survive a reload.
Imports, features and API used
Imports: @svgrid/grid, ../shared/seed
Table features registered: rowSortingFeature, columnFilteringFeature
Columns: orderId (Order ID), company (Company), product (Product), sellDate (Sell date), quantity (Qty), price (Price), country (Country)
SvGridApi methods called: api.getColumnPinning(), api.getColumnWidths(), api.setColumnPinning(), api.setColumnWidth()
Frequently asked questions
How do I read the current column widths?
api.getColumnWidths() returns an object of column id to pixel width, including widths set by drag-resize. Store it with api.getColumnPinning() to capture the whole layout.
How do I restore a layout?
Loop over the saved widths and call api.setColumnWidth(id, width) for each, then api.setColumnPinning with the saved { left, right } arrays. The demo does this when a named layout is selected.
Is there a higher-level helper?
Yes. createNamedViews wraps api.getState() and api.setState() and includes widths and pins along with sort and filters; use it when you want views rather than layout alone.
Related documentation
Related articles
- Multi-Level (Grouped) Column Headers in SvGrid - Band related columns under a shared parent header using SvGrid's nested column definition - how to nest, pin, combine with sorting and filtering, and when NOT to use grouping.
- A Custom Column Header Menu in SvGrid - Build a per-column header menu for sort, hide, pin, and custom actions using header snippets and your own dropdown component.
- A Column Show/Hide Toggle in SvGrid - Build a column chooser panel that lets users show or hide columns at runtime, with persistence across page loads and a guard against hiding everything.
Source code (63-column-layout-api.svelte)
<script lang="ts">
/**
* 63. Column layout API - named "saved views"
* ------------------------------------------
* Realistic pattern: an analyst sets up a layout (widths + pins) for a
* specific job, saves it under a name, and switches between named views
* later. Underneath:
*
* - `api.getColumnWidths()` + `api.getColumnPinning()` snapshot the
* current layout to a plain JSON object.
* - `api.setColumnWidth(id, w)` + `api.setColumnPinning({left,right})`
* restore it.
*
* Views are auto-persisted to localStorage so they survive a reload.
*/
import {
SvGrid,
tableFeatures,
rowSortingFeature,
columnFilteringFeature,
type GridColumns,
type SvGridApi,
} from '@svgrid/grid'
import { makeOrders, type Order } from '../shared/seed'
const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
const rows = makeOrders(120)
let api = $state<SvGridApi<typeof features, Order> | null>(null)
type View = {
name: string
widths: Record<string, number>
pinning: { left: string[]; right: string[] }
}
const STORAGE_KEY = 'svgrid-demo-63-views'
// Three preset layouts seeded on first load. Users add their own via the
// "Save as" input.
const PRESETS: View[] = [
{ name: 'Default',
widths: { orderId: 140, company: 180, product: 180, sellDate: 130, quantity: 90, price: 130, country: 110 },
pinning: { left: [], right: [] } },
{ name: 'Logistics',
widths: { orderId: 140, company: 220, product: 200, sellDate: 110, quantity: 80, price: 110, country: 130 },
pinning: { left: ['orderId'], right: [] } },
{ name: 'Finance',
widths: { orderId: 120, company: 160, product: 140, sellDate: 110, quantity: 70, price: 160, country: 100 },
pinning: { left: ['orderId', 'company'], right: ['price'] } },
]
let views = $state<View[]>(loadViews())
let activeName = $state<string>(views[0]?.name ?? 'Default')
let newName = $state<string>('')
function loadViews(): View[] {
if (typeof localStorage === 'undefined') return PRESETS
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (raw) return JSON.parse(raw)
} catch { /* fall through */ }
return PRESETS
}
function persist() {
if (typeof localStorage === 'undefined') return
localStorage.setItem(STORAGE_KEY, JSON.stringify(views))
}
function applyView(view: View) {
if (!api) return
for (const [id, w] of Object.entries(view.widths)) api.setColumnWidth(id, w)
api.setColumnPinning(view.pinning)
activeName = view.name
}
function saveAsCurrent() {
if (!api || !newName.trim()) return
const next: View = {
name: newName.trim(),
widths: api.getColumnWidths(),
pinning: api.getColumnPinning(),
}
const i = views.findIndex((v) => v.name === next.name)
if (i >= 0) views[i] = next
else views = [...views, next]
activeName = next.name
newName = ''
persist()
}
function deleteView(name: string) {
if (views.length === 1) return
views = views.filter((v) => v.name !== name)
if (activeName === name) {
activeName = views[0]!.name
applyView(views[0]!)
}
persist()
}
function resetAllViews() {
views = [...PRESETS]
activeName = 'Default'
applyView(PRESETS[0]!)
persist()
}
const columns: GridColumns<Order> = [
{ field: 'orderId', header: 'Order ID', editorType: 'text', width: 140 },
{ field: 'company', header: 'Company', editorType: 'text', width: 180 },
{ field: 'product', header: 'Product', editorType: 'text', width: 180 },
{ field: 'sellDate', header: 'Sell date',editorType: 'date', width: 130,
format: { type: 'date', pattern: 'y-m-d' } },
{ field: 'quantity', header: 'Qty', editorType: 'number', width: 90 },
{ field: 'price', header: 'Price', editorType: 'number', width: 130,
format: { type: 'currency', currency: 'USD' } },
{ field: 'country', header: 'Country', editorType: 'text', width: 110 },
]
</script>
<section class="flex flex-col flex-1 min-h-0 gap-3">
<!-- Saved-views bar -->
<div class="flex flex-wrap items-center gap-2 text-sm shrink-0">
<div class="inline-flex items-center rounded-md border overflow-hidden vw-bar">
{#each views as v (v.name)}
<button
type="button"
onclick={() => applyView(v)}
class="group inline-flex items-center gap-1.5 px-3 py-1.5 border-r last:border-r-0 vw-tab
{v.name === activeName ? 'vw-tab-on font-medium' : ''}"
>
<span>{v.name}</span>
{#if views.length > 1 && v.name !== 'Default'}
<span
onclick={(e) => { e.stopPropagation(); deleteView(v.name) }}
role="button"
tabindex="0"
aria-label="Delete view {v.name}"
class="opacity-0 group-hover:opacity-100 text-xs leading-none vw-x"
>×</span>
{/if}
</button>
{/each}
</div>
<input
type="text"
bind:value={newName}
placeholder="Name and Save current layout…"
class="w-56 rounded-md border px-2 py-1.5 vw-input"
onkeydown={(e) => { if (e.key === 'Enter') saveAsCurrent() }}
/>
<button
type="button"
onclick={saveAsCurrent}
disabled={!newName.trim()}
class="rounded-md border px-3 py-1.5 font-medium disabled:opacity-50 vw-save"
>Save view</button>
<button
type="button"
onclick={resetAllViews}
class="ml-auto text-xs underline-offset-2 hover:underline vw-reset"
>Reset to presets</button>
</div>
<p class="text-xs shrink-0 vw-note">
Resize columns or pin from the header menu, then save - the layout is persisted to <code>localStorage</code> and shows up on the next reload.
Click any saved view to restore it instantly.
</p>
<div class="flex-1 min-h-0">
<SvGrid responsive={true}
columnResize
data={rows}
columns={columns}
features={features}
filterMode="menu"
selectionMode="cell"
showRowNumbers={true}
showPagination={true}
pageSize={25}
enableInlineEditing={false}
enableCellSelection={true}
rowHeight={36}
containerHeight="100%"
fitColumns={false}
onApiReady={(next) => {
api = next
// Apply the active view once the API is available.
const v = views.find((x) => x.name === activeName) ?? views[0]
if (v) applyView(v)
}}
/>
</div>
</section>
<style>
/* Toolbar chrome follows the active grid theme via --sg-* tokens. */
.vw-bar {
border-color: var(--sg-border, #e2e8f0);
background: var(--sg-bg, #ffffff);
}
.vw-tab {
border-color: var(--sg-border, #e2e8f0);
color: var(--sg-fg, #334155);
}
.vw-tab:not(.vw-tab-on):hover { background: var(--sg-row-hover-bg, #f8fafc); }
.vw-tab-on {
background: var(--sg-selection-bg, #eef2ff);
color: var(--sg-accent, #4338ca);
}
.vw-x { color: var(--sg-muted, #94a3b8); }
/* Delete affordance keeps its danger tint - meaning, not theme. */
.vw-x:hover { color: #f43f5e; }
.vw-input {
border-color: var(--sg-input-border, var(--sg-border, #cbd5e1));
background: var(--sg-input-bg, #ffffff);
color: var(--sg-fg, #0f172a);
}
.vw-input::placeholder { color: var(--sg-muted, #94a3b8); }
.vw-save {
border-color: var(--sg-accent, #4f46e5);
background: var(--sg-accent, #4f46e5);
color: var(--sg-on-accent, #ffffff);
}
.vw-save:hover:not(:disabled) { filter: brightness(1.08); }
.vw-reset { color: var(--sg-muted, #64748b); }
.vw-reset:hover { color: var(--sg-fg, #334155); }
.vw-note { color: var(--sg-muted, #64748b); }
</style>More Columns examples
- Column pinning + freezing - Wide 13-column grid. Pin Company left and Price right via the column menu; the middle scrolls under sticky edges.
- Columns hierarchy + manager - Side-panel tree of grouped columns: drag leaves to reorder, click a chevron to collapse a group into one summary column, toggle visibility per leaf or whole group.
- Tool panel (Columns + Filters) - The docked enterprise sidebar, two tabs. Columns: toggle visibility, reorder up/down, group by a column. Filters: an operator + value control per column (numeric operators come free via cellDataType), kept in sync with the column menu. Enable with the toolPanel prop.
- Column reorder - Set enableColumnReorder on <SvGrid> and every header becomes draggable, with a drop indicator. api.setColumnOrder / getColumnOrder + onColumnOrderChange event; persist to restore across reloads.
- Autosize columns - api.autosizeColumn(id) and api.autosizeAllColumns() snap columns to the widest visible cell via canvas-based text measurement. The column header menu has an "Autosize" item that calls the same code. Manual drag-resize still works.