Tabs, tree & form
Composite controls: SvTabs (line + pill, roving keyboard), SvTree (expand/collapse, single-select + cascading tri-state checkboxes) and a schema-driven SvForm that wires the whole kit (inputs, select, switch, date, textarea) with required + custom validation.
A live, editable Svelte 5 component example from the SvGrid gallery (Layout). Read the SvGrid UI component docs for the full API.
About this example
The composite controls of SvGrid UI in Svelte 5: SvTabs in line and pill styles with a roving keyboard, SvTree with expand and collapse, single selection and cascading tri-state checkboxes, and a schema-driven SvForm that wires the rest of the kit, inputs, select, switch, date and textarea, with required and custom validation in a two-column layout.
Layout & composite - SvTabs, SvTree and SvForm. The form is schema-driven and wires the rest of the UI kit (inputs, select, switch, date, rating).
Imports, features and API used
Imports: @svgrid/grid
Frequently asked questions
How does the form know which component to render?
From each field's type in the schema: text, select, switch, date and textarea map to the kit components, and initial seeds the values.
How do cascading checkboxes work in the tree?
Checking a folder checks its children and partially checked folders show the indeterminate state; onCheck reports the full set of checked ids.
How are the tabs and panels linked?
tabs is an array of { id, label } and value is the active id; the panel for that id renders below, and arrow keys move focus between tabs with automatic activation.
Related documentation
Related articles
- SvGrid UI Components Tips and Tricks: Svelte 5 Buttons, Inputs, Tree, Forms - Practical tips for the SvGrid UI component kit - SvButton states, a Cmd+K command palette, a virtualized searchable tree, schema-driven forms, a parsing date/time field, and the shared field contract - each with a code snippet.
- Avoiding Layout Thrash in Custom Grid Cells - Layout thrash from interleaved DOM reads and writes is the most common cause of scroll jank in grids with custom cells - here is how to find it and design your way out of it.
- Stable Row Identity in SvGrid (and why it matters) - How SvGrid tracks rows across mutations, why object reference stability is the key to correct selection and editing, and the exact patterns to use with live data feeds.
Source code (257-tabs-tree-form.svelte)
<script lang="ts">
/**
* Layout & composite - SvTabs, SvTree and SvForm. The form is schema-driven
* and wires the rest of the UI kit (inputs, select, switch, date, rating).
*/
import { SvTabs, SvTree, SvForm } from '@svgrid/grid'
import type { SvTreeNode, FormField } from '@svgrid/grid'
let tab = $state('form')
const tree: SvTreeNode[] = [
{ id: 'src', label: 'src', children: [
{ id: 'comp', label: 'components', children: [
{ id: 'a', label: 'SvCalendar.svelte' },
{ id: 'b', label: 'SvTree.svelte' },
{ id: 'c', label: 'SvForm.svelte' },
] },
{ id: 'lib', label: 'lib', children: [{ id: 'd', label: 'popover.ts' }, { id: 'e', label: 'date-core.ts' }] },
{ id: 'idx', label: 'index.ts' },
] },
{ id: 'pkg', label: 'package.json' },
]
let treeSel = $state<string | null>('a')
let checked = $state<string[]>([])
const fields: FormField[] = [
{ name: 'name', label: 'Full name', required: true },
{ name: 'email', label: 'Email', type: 'email', required: true, validate: (v) => (v && !String(v).includes('@') ? 'Enter a valid email' : null) },
{ name: 'plan', label: 'Plan', type: 'select', options: [{ value: 'free', label: 'Free' }, { value: 'pro', label: 'Pro' }, { value: 'ent', label: 'Enterprise' }] },
{ name: 'seats', label: 'Seats', type: 'number' },
{ name: 'starts', label: 'Start date', type: 'date' },
{ name: 'newsletter', label: 'Subscribe to the newsletter', type: 'switch', full: true },
{ name: 'notes', label: 'Notes', type: 'textarea', full: true },
]
let submitted = $state<Record<string, any> | null>(null)
</script>
<div class="wrap">
<header>
<h2>Layout & composite</h2>
<p>SvTabs, SvTree and a schema-driven SvForm - the last wires the rest of the kit. All theme from <code>--sg-*</code>.</p>
</header>
<SvTabs
tabs={[{ id: 'form', label: 'Form' }, { id: 'tree', label: 'Tree' }, { id: 'pill', label: 'Pill tabs' }]}
value={tab}
onChange={(id) => (tab = id)}
>
{#snippet panel(active)}
{#if active === 'form'}
<div class="pane">
<SvForm {fields} columns={2} initial={{ plan: 'pro', seats: 5 }} onSubmit={(v) => { submitted = v }} submitLabel="Create account" />
{#if submitted}<pre class="result">{JSON.stringify(submitted, null, 2)}</pre>{/if}
</div>
{:else if active === 'tree'}
<div class="pane trees">
<div>
<h4>Single select</h4>
<SvTree nodes={tree} selected={treeSel} expandedIds={['src', 'comp']} onSelect={(id) => (treeSel = id)} />
<p class="muted">Selected: <strong>{treeSel}</strong></p>
</div>
<div>
<h4>Cascading checkboxes</h4>
<SvTree nodes={tree} checkable checked={checked} expandedIds={['src', 'comp', 'lib']} onCheck={(ids) => (checked = ids)} />
<p class="muted">{checked.length} checked</p>
</div>
</div>
{:else}
<div class="pane">
<SvTabs variant="pill" tabs={[{ id: '1', label: 'Overview' }, { id: '2', label: 'Activity' }, { id: '3', label: 'Settings' }]}>
{#snippet panel(a)}
<p class="muted">Pill-style tab panel: <strong>{a}</strong>. Arrow keys navigate; Home/End jump.</p>
{/snippet}
</SvTabs>
</div>
{/if}
{/snippet}
</SvTabs>
</div>
<style>
.wrap { padding: 22px; max-width: 820px; display: flex; flex-direction: column; gap: 12px; }
header h2 { margin: 0 0 4px; font-size: 20px; font-weight: 700; }
header p { margin: 0; color: var(--sg-muted, #64748b); font-size: 13.5px; }
code { background: var(--sg-row-hover-bg, #eef2ff); padding: 1px 5px; border-radius: 5px; font-size: 12px; }
.pane { padding-top: 6px; }
.trees { display: flex; gap: 40px; flex-wrap: wrap; }
.trees h4 { margin: 0 0 8px; font-size: 13px; }
.muted { color: var(--sg-muted, #64748b); font-size: 12.5px; }
.result { margin-top: 14px; background: var(--sg-header-bg, #f8fafc); border: 1px solid var(--sg-border, #e2e8f0); border-radius: 8px; padding: 10px 12px; font-size: 12px; overflow: auto; }
</style>More Layout examples
- Account & security settings console - A real SaaS settings surface composed from the UI kit: SvMenubar app bar, promise + Undo-action toasts on save, SvPopconfirm on destructive rows, SvHoverCard teammate previews, and frame input adornments (leading icons, prefix affixes, masked API key). Profile / Team / Security tabs over SvCard + SvStat.
- Invoice builder - An adornment-heavy money form: currency prefixes, % suffixes, masked tax IDs (frame adornments), line items with SvPopconfirm delete + Undo action toasts, live SvStat totals, an SvHoverCard tax hint, and a promise toast on Send. Pure UI-kit composition.
- Operations KPI dashboard - A KPI console: SvStat + SvSparkline tiles, an SvGauge SLA dial, SvHoverCard drill-down previews per service, an SvMenubar toolbar (View / Range / Actions) and a promise toast on Refresh. Pure UI-kit composition, no grid dependency.
- Form: rich field types - SvForm reaching the whole input suite from one schema - phone, country, mask, combobox, radio, slider, tags, datetime and file - plus per-field help text, a readonly field, column span, and a promise toast on submit.
- Form: cascading fields - SvForm dependent selects - a child list derives from the parent value via a function `options`, and `dependsOn` clears the child when the parent changes so a stale selection never lingers. Country -> State -> City.