App overlays: drawer, context menu, toasts
SvDrawer (edge side-sheet), SvContextMenu (right-click menu) and the toast() API + SvToaster - all built on the shared focus-trap, scroll-lock and dismissable-layer primitives, so nested overlays close top-first and every toast is announced to screen readers.
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 app-chrome overlays of SvGrid UI in Svelte 5: SvDrawer, an edge side-sheet with a configurable side and title, SvContextMenu for a right-click menu from an items array, and the toast() API with SvToaster for notifications. All are built on the shared focus-trap, scroll-lock and dismissable-layer primitives, so nested overlays close top-first and every toast is announced to screen readers.
SvDrawer, SvContextMenu and the toast API - app-chrome overlays that share the grid's a11y primitives (focus trap, scroll lock, dismissable layer stack). All portal to <body>, animate in, and respect reduced-motion.
Imports, features and API used
Imports: @svgrid/grid
Frequently asked questions
How do I show a toast?
Render SvToaster once, then call toast('Saved') from anywhere, or toast.success, toast.error, toast.warning and toast.info with an optional duration where 0 keeps it sticky; toast.promise wraps an async call with loading, success and error messages.
What does the dismissable layer stack do?
It tracks open overlays in order so Escape or an outside click closes only the topmost one, which keeps a context menu inside a drawer behaving correctly.
How is the drawer opened?
Bind open and set it true; side chooses the edge, the drawer traps focus, locks scroll and closes on Escape or the backdrop.
Related documentation
Related articles
- A Right-Click Context Menu for Your Svelte Data Grid - Build a right-click context menu that wires row actions, handles multi-row selection, and stays out of the way of SvGrid's own event handling.
- Saved Views - Persist Grid Layout and Filters - Give users named, switchable snapshots of column order, sorting, filters, and grouping - persisted to localStorage or a server adapter - using SvGrid's createNamedViews API.
- 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.
Source code (331-app-overlays.svelte)
<script lang="ts">
/**
* SvDrawer, SvContextMenu and the toast API - app-chrome overlays that share
* the grid's a11y primitives (focus trap, scroll lock, dismissable layer
* stack). All portal to <body>, animate in, and respect reduced-motion.
*/
import { SvDrawer, SvContextMenu, SvToaster, toast, SvButton, type MenuItem } from '@svgrid/grid'
let drawerOpen = $state(false)
let side = $state<'right' | 'left' | 'top' | 'bottom'>('right')
const menu: MenuItem[] = [
{ label: 'Copy', shortcut: 'Ctrl+C' },
{ label: 'Duplicate' },
{ separator: true },
{ label: 'Move to', children: [{ label: 'Inbox' }, { label: 'Archive' }] },
{ label: 'Delete', shortcut: 'Del' },
]
</script>
<div class="wrap">
<header>
<h2>App overlays</h2>
<p>Drawer, right-click context menu and toasts - built on the shared focus-trap, scroll-lock and dismissable-layer primitives, so nested overlays close top-first and screen readers hear every toast.</p>
</header>
<section>
<h3>Drawer</h3>
<div class="row">
<SvButton onclick={() => (drawerOpen = true)}>Open drawer</SvButton>
<label class="chk">Side
<select bind:value={side}>
<option value="right">right</option>
<option value="left">left</option>
<option value="top">top</option>
<option value="bottom">bottom</option>
</select>
</label>
</div>
<SvDrawer bind:open={drawerOpen} {side} title="Filters">
<p>An edge-anchored side sheet. Focus is trapped, body scroll is locked, and Escape or the backdrop closes it.</p>
<label class="field">Search<input type="text" placeholder="Keyword" /></label>
<label class="field">Status
<select><option>Any</option><option>Active</option><option>Archived</option></select>
</label>
{#snippet footer()}
<SvButton variant="ghost" onclick={() => (drawerOpen = false)}>Cancel</SvButton>
<SvButton onclick={() => (drawerOpen = false)}>Apply</SvButton>
{/snippet}
</SvDrawer>
</section>
<section>
<h3>Context menu</h3>
<SvContextMenu items={menu} onSelect={(i) => toast(`${i.label} clicked`)}>
<div class="zone">Right-click anywhere in this box</div>
</SvContextMenu>
</section>
<section>
<h3>Toasts</h3>
<div class="row">
<SvButton size="sm" onclick={() => toast.info('Heads up - background sync started')}>Info</SvButton>
<SvButton size="sm" variant="ghost" onclick={() => toast.success('Saved to the server')}>Success</SvButton>
<SvButton size="sm" variant="ghost" onclick={() => toast.warning('Connection is slow')}>Warning</SvButton>
<SvButton size="sm" variant="danger" onclick={() => toast.error('Could not save', { duration: 0 })}>Error (sticky)</SvButton>
</div>
</section>
</div>
<!-- Mount once near the app root; call toast() from anywhere. -->
<SvToaster position="bottom-right" />
<style>
.wrap { padding: 20px; max-width: 820px; display: flex; flex-direction: column; gap: 20px; }
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; line-height: 1.5; }
section h3 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--sg-muted, #64748b); }
.row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 13.5px; }
.chk { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--sg-muted, #64748b); }
.chk select { font: inherit; font-size: 13px; padding: 5px 8px; border: 1px solid var(--sg-input-border, #cbd5e1); border-radius: 7px; background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); }
.zone { display: grid; place-items: center; height: 120px; border: 1.5px dashed var(--sg-border, #cbd5e1); border-radius: 10px; color: var(--sg-muted, #64748b); font-size: 13.5px; user-select: none; }
.field { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; font-weight: 600; color: var(--sg-muted, #64748b); margin-top: 12px; }
.field input, .field select { font: inherit; font-size: 13px; padding: 7px 10px; border: 1px solid var(--sg-input-border, #cbd5e1); border-radius: 7px; background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); font-weight: 400; }
</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.