Inline info banners block
SvAlert in danger, warning, success and info tones inside a settings page, all dismissable. Retry now and Rotate key show a loading state and then clear their banner; Show hidden banners restores them.
A live, editable Svelte 5 component example from the SvGrid gallery (Blocks). Read the SvGrid UI component docs for the full API.
About this example
Inline info banners for Svelte 5 built from the SvGrid UI kit: SvAlert in danger, warning, success and info tones inside a workspace settings page. Each is dismissable, and the actions are SvButtons that do work: Retry now and Rotate key show a loading state and resolve their banner, Download fires a toast. A Show hidden banners button restores everything dismissed or resolved.
Inline info banners block - the four SvAlert tones (info, success, warning, danger) placed inside a workspace settings page, each with a dismiss button and most with SvButton actions that do real work: rotate an expiring API key, retry failed webhooks, download an export. Results land in an SvToaster toast, and a "Show hidden banners" SvButton brings dismissed and resolved banners back. Pure UI-kit, --sg-* themed.
Headless: createInfoBanners() below owns which banners are shown, the key-rotation and webhook-retry flows with their loading flags, and the restore action with no markup. The component under it is one styled render of that core; import createInfoBanners from this file (or npx @svgrid/ui add banners-info --headless for the core alone) to draw your own.
Imports, features and API used
Imports: @svgrid/grid
Frequently asked questions
How does dismissing work with SvAlert?
SvAlert hides itself when its x is pressed and calls onDismiss. The block also wraps each alert in an {#if} on banners.shown and calls banners.dismiss(id) from onDismiss, so the core knows which banners are gone and banners.restore() can render them again.
Which tone should I use for what?
Danger for something already broken that needs action (failed webhooks), warning for something that will break (an expiring key), success for a finished background job, and info for context that needs no action. Danger and warning render with role alert, the others with role status.
How do I hook the actions to my API?
createInfoBanners() takes rotateKey and retryHooks options. banners.rotateKey() and banners.retryHooks() set a loading flag, await your function, then hide their banner and call onRotated or onRetried, which show the success toast. The block passes timers; replace them with your requests. If your function throws, the banner stays up, so catch the error there and show toast.error.
Related documentation
Related articles
- Inside SvGrid: The Inline Editing Engine - How SvGrid handles inline cell editing - typed editors, an event-based commit model, undo/redo, and why the grid never touches your data directly.
- Inline Editing with Validation in SvGrid - How to wire up typed cell editors, reject bad input before it reaches your data, and keep per-cell error state without a form library.
Source code (583-block-banners-info.svelte)
<script lang="ts" module>
/**
* Inline info banners block - the four SvAlert tones (info, success,
* warning, danger) placed inside a workspace settings page, each with a
* dismiss button and most with SvButton actions that do real work: rotate
* an expiring API key, retry failed webhooks, download an export. Results
* land in an SvToaster toast, and a "Show hidden banners" SvButton brings
* dismissed and resolved banners back. Pure UI-kit, --sg-* themed.
*
* Headless: createInfoBanners() below owns which banners are shown, the
* key-rotation and webhook-retry flows with their loading flags, and the
* restore action with no markup. The component under it is one styled
* render of that core; import createInfoBanners from this file (or
* `npx @svgrid/ui add banners-info --headless` for the core alone) to draw
* your own.
*/
export type InfoBannerId = 'usage' | 'export' | 'key' | 'hooks'
export type InfoBannersOptions = {
/** Last characters of the API key that is about to expire. */
keySuffix?: string
/** Rotate the key; resolve the new key's suffix. Throw to keep the banner up. */
rotateKey?: () => Promise<string>
onRotated?: (suffix: string) => void
/** Re-send the failed deliveries. Throw to keep the banner up. */
retryHooks?: () => Promise<void>
onRetried?: () => void
}
const ALL_SHOWN: Record<InfoBannerId, boolean> = { usage: true, export: true, key: true, hooks: true }
export function createInfoBanners(opts: InfoBannersOptions = {}) {
const initialSuffix = opts.keySuffix ?? '9f3c'
let shown = $state<Record<InfoBannerId, boolean>>({ ...ALL_SHOWN })
let keySuffix = $state(initialSuffix)
let rotating = $state(false)
let retrying = $state(false)
const hidden = $derived(Object.values(shown).filter((v) => !v).length)
async function rotateKey() {
if (rotating || !opts.rotateKey) return
rotating = true
try {
keySuffix = await opts.rotateKey()
shown.key = false
opts.onRotated?.(keySuffix)
} finally {
rotating = false
}
}
async function retryHooks() {
if (retrying || !opts.retryHooks) return
retrying = true
try {
await opts.retryHooks()
shown.hooks = false
opts.onRetried?.()
} finally {
retrying = false
}
}
return {
get shown() { return shown },
get keySuffix() { return keySuffix },
get rotating() { return rotating },
get retrying() { return retrying },
/** How many banners are dismissed or resolved. */
get hidden() { return hidden },
dismiss: (id: InfoBannerId) => { shown[id] = false },
restore: () => { shown = { ...ALL_SHOWN }; keySuffix = initialSuffix },
rotateKey,
retryHooks,
/** Spread on a plain dismiss <button> for one banner. */
dismissProps: (id: InfoBannerId) => ({ type: 'button' as const, 'aria-label': 'Dismiss', onclick: () => { shown[id] = false } }),
}
}
</script>
<script lang="ts">
import { SvAlert, SvButton, SvToaster, toast } from '@svgrid/grid'
const banners = createInfoBanners({
// Replace both with your API calls.
rotateKey: () => new Promise((res) => setTimeout(() => res(Math.random().toString(16).slice(2, 6)), 1000)),
onRotated: (s) => toast.success(`New key ending ${s} is active. The old key stops working in 24 hours.`),
retryHooks: () => new Promise((res) => setTimeout(res, 1100)),
onRetried: () => toast.success('2 deliveries sent to https://hooks.northwind.io/orders'),
})
</script>
<main class="sv-bn-info">
<div class="sv-bn-info-head">
<div>
<h2>Workspace settings</h2>
<p class="sv-bn-info-muted">Northwind Ops, 14 members</p>
</div>
{#if banners.hidden > 0}
<SvButton variant="outline" size="sm" onclick={banners.restore}>Show hidden banners ({banners.hidden})</SvButton>
{/if}
</div>
<div class="sv-bn-info-stack">
{#if banners.shown.hooks}
<SvAlert variant="danger" title="2 webhook deliveries failed" dismissible onDismiss={() => banners.dismiss('hooks')}>
The endpoint returned 503 for order.created at 14:02 and 14:09. We stop retrying after 24 hours.
{#snippet actions()}
<SvButton size="sm" variant="secondary" loading={banners.retrying} onclick={banners.retryHooks}>Retry now</SvButton>
{/snippet}
</SvAlert>
{/if}
{#if banners.shown.key}
<SvAlert variant="warning" title="API key ending {banners.keySuffix} expires in 5 days" dismissible onDismiss={() => banners.dismiss('key')}>
Integrations using it will start getting 401 errors on Oct 20.
{#snippet actions()}
<SvButton size="sm" variant="secondary" loading={banners.rotating} onclick={banners.rotateKey}>Rotate key</SvButton>
<SvButton size="sm" variant="ghost" onclick={() => toast('Key usage: 3 integrations, last call 2 min ago')}>See usage</SvButton>
{/snippet}
</SvAlert>
{/if}
{#if banners.shown.export}
<SvAlert variant="success" title="Export ready" dismissible onDismiss={() => banners.dismiss('export')}>
orders-2026-q3.csv, 12,480 rows. The link expires in 7 days.
{#snippet actions()}
<SvButton size="sm" variant="secondary" onclick={() => toast.success('Downloading orders-2026-q3.csv')}>Download</SvButton>
{/snippet}
</SvAlert>
{/if}
{#if banners.shown.usage}
<SvAlert variant="info" dismissible onDismiss={() => banners.dismiss('usage')}>
Usage figures on this page refresh every 15 minutes.
</SvAlert>
{/if}
</div>
<section class="sv-bn-info-mock" aria-hidden="true">
{#each [55, 40, 65] as w (w)}
<div class="sv-bn-info-field"><i class="sv-bn-info-lbl"></i><i class="sv-bn-info-box" style:--w="{w}%"></i></div>
{/each}
</section>
</main>
<SvToaster position="bottom-right" />
<style>
.sv-bn-info {
width: 100%; box-sizing: border-box; max-width: 820px; margin: 0 auto; min-height: 560px;
padding: 32px 24px; display: flex; flex-direction: column; gap: 20px;
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); font-family: var(--sg-font, inherit);
}
.sv-bn-info-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
h2 { margin: 0 0 4px; font-size: 22px; font-weight: 700; letter-spacing: -.01em; }
.sv-bn-info-muted { margin: 0; font-size: 13px; color: var(--sg-muted, #64748b); }
.sv-bn-info-stack { display: flex; flex-direction: column; gap: 12px; }
.sv-bn-info-stack :global(.sv-alert__actions) { flex-wrap: wrap; }
.sv-bn-info-mock { display: flex; flex-direction: column; gap: 18px; padding: 20px; border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius-lg, 12px); }
.sv-bn-info-field { display: flex; flex-direction: column; gap: 8px; }
.sv-bn-info-field i { display: block; border-radius: 4px; background: color-mix(in srgb, var(--sg-fg, #0f172a) 8%, var(--sg-bg, #fff)); }
.sv-bn-info-field i.sv-bn-info-lbl { width: 90px; height: 8px; }
.sv-bn-info-field i.sv-bn-info-box { width: var(--w); height: 32px; border-radius: var(--sg-radius, 8px); }
@media (max-width: 640px) {
.sv-bn-info { padding: 20px 12px; }
.sv-bn-info-field i.sv-bn-info-box { width: 100%; }
}
</style>More Blocks examples
- Login block - A two-column authentication page: a branded gradient panel beside a sign-in SvCard with social SvButtons, SvTextInput + SvPasswordInput (reveal), remember-me SvCheckBox, an "or" SvDivider and forgot-password link. Drops into any route as-is.
- Sign-up block - A centered account-creation card: two-up name fields, work email, a password with the live strength meter, a plan SvSegmented switch, terms SvCheckBox and a benefits rail. Submit fires a promise toast. Pure UI-kit.
- Two-factor / OTP block - The verification step of a sign-in flow: SvOtpInput (6 digits, auto-advance + onComplete), a resend cooldown timer, and the waiting -> verifying -> verified states. Enter 000000 to see the error path.
- Analytics dashboard block - A full overview page: a range SvSegmented + actions header, a KPI grid (SvStat + SvSparkline), a revenue bar panel, a recent-activity SvTimeline and a top-products list with SvProgress bars. No grid dependency.
- App shell block - A complete application layout: a SvNavPane sidebar with sections + badges, a top bar with SvBreadcrumb, search and an account SvMenu, and a routed content area. Nav swaps the page; the toggle collapses the rail to an icon rail.