Banner blocks

Messages that sit on top of or inside a page: an announcement bar, inline status banners, cookie consent and a failed-payment warning. Each block is one .svelte file built from SvAlert, SvButton, SvBadge, SvModal, SvSwitchButton and SvMaskedInput, themed from the --sg-* tokens. Every block draws a mock page around its banner and has a control to show the banner again after you dismiss it.

Installation

Add the whole group, or one block by its name (shown under each demo):

See them first with npx @svgrid/ui try blocks/banners. No project needed.

Announcement bar

A slim bar above the app header: a New badge, one line of news, a link to the changelog and a dismiss button. The bar uses --sg-fg as its background, so it stays neutral in light and dark themes. Dismissing shows a toast with Undo. Below 640 px the badge is hidden and the message wraps left-aligned.

Open the live example: Announcement bar block (Blocks)

npx @svgrid/ui add banners-announcement

Inline info banners

Four SvAlert banners inside a settings page: failed webhooks (danger), an expiring API key (warning), a finished export (success) and a note about refresh timing (info). All four have a dismiss button. Retry now and Rotate key show a loading state and then clear their banner; Show hidden banners brings everything back.

Open the live example: Inline info banners block (Blocks)

npx @svgrid/ui add banners-info

A consent bar along the bottom of a site with Accept all, Reject non-essential and Customize. Customize opens an SvModal with one SvSwitchButton per category; Necessary is on and disabled. After a choice the bar becomes one line listing the allowed categories, with a Change settings link that reopens the modal from the saved choice.

Open the live example: Cookie consent block (Blocks)

npx @svgrid/ui add banners-cookie-consent

Billing warning

A failed-payment banner across the top of the app: the declined invoice, a badge with the days left before the workspace goes read-only, and Update payment method. The modal holds a name field and three SvMaskedInputs: card number (16 digits plus a Luhn checksum), expiry (a real month, not in the past) and CVC. Errors show after the first save; a valid card shows the loading state and turns the banner into a "Payment received" state. Try 4242 4242 4242 4242.

Open the live example: Billing warning banner block (Blocks)

npx @svgrid/ui add banners-billing-warning

Remembering a dismissed banner

Each block's core keeps visibility in memory, so a dismissed banner comes back on the next page load. To remember it per user, store a key made of the banner id and a version. Bump the version when the message changes and everyone sees the new one once.

Read storage in onMount, not at the top of <script>: during server rendering there is no localStorage, and in private windows the accessor can throw. Create the announcement core hidden (open: false) so a dismissed bar does not flash in after hydration, then show it if the key is not set:

<script lang="ts">
  import { onMount } from 'svelte'
  import { toast } from '@svgrid/grid'
  import { createAnnouncement } from './banners-announcement.svelte.js'

  const KEY = 'banner:exports-weekend:v1'
  const remember = (dismissed: boolean) => {
    try {
      if (dismissed) localStorage.setItem(KEY, 'dismissed')
      else localStorage.removeItem(KEY)
    } catch { /* storage blocked: hide for this visit only */ }
  }

  const bar = createAnnouncement({
    open: false,
    onDismiss: (undo) => {
      remember(true)
      toast('Announcement dismissed', { action: { label: 'Undo', onClick: () => { remember(false); undo() } } })
    },
  })

  onMount(() => {
    let dismissed = false
    try { dismissed = localStorage.getItem(KEY) === 'dismissed' } catch { /* show it */ }
    if (!dismissed) bar.show()
  })
</script>

For the billing warning, do not store anything in the browser: the banner should show while the server says the invoice is unpaid. Load the invoice in +layout.server.ts and start the core from it: createBillingWarning({ invoice: data.invoice, status: data.invoice ? 'failed' : 'hidden' }). Its onSave option is where your payment provider's tokenize call and the invoice retry go; throw from it to keep the form open.

Consent belongs in a cookie the server can read, so it can leave analytics scripts out of the HTML before the page renders. Start the core from that cookie with createCookieConsent({ saved: data.consent }): null shows the bar, a saved choice shows the summary line. Then wrap the settings in a form that posts to a SvelteKit action. SvSwitchButton takes a name and submits "true" or "false" through a hidden input:

<form method="POST" action="?/consent">
  <SvSwitchButton name="analytics" ariaLabel="Analytics cookies" checked={consent.draft.analytics} onChange={(v) => consent.setDraft('analytics', v)} />
  <SvButton type="submit" variant="primary">Save settings</SvButton>
</form>
// +page.server.ts
import type { Actions, PageServerLoad } from './$types'

const CATEGORIES = ['preferences', 'analytics', 'marketing'] as const

export const load: PageServerLoad = ({ cookies }) => {
  const raw = cookies.get('consent')
  return { consent: raw ? JSON.parse(raw) : null }
}

export const actions: Actions = {
  consent: async ({ request, cookies }) => {
    const form = await request.formData()
    const consent = { necessary: true, ...Object.fromEntries(CATEGORIES.map((c) => [c, form.get(c) === 'true'])) }
    cookies.set('consent', JSON.stringify(consent), { path: '/', maxAge: 60 * 60 * 24 * 180, sameSite: 'lax', httpOnly: false })
  },
}

httpOnly: false lets client code read the choice too, for example to load an analytics script only when consent.analytics is true. For Accept all and Reject non-essential, which do not post the form, write the same cookie from the core's onDecide(consent, how) callback.

Headless

The banners' state lives in the <script module> of each file: createAnnouncement(), createInfoBanners(), createCookieConsent() and createBillingWarning() own visibility, dismiss and undo, consent, and the card form with its validation. The styled markup is one render of that core. Take the cookie consent core alone with npx @svgrid/ui add banners-cookie-consent --headless and draw your own bar:

<script lang="ts">
  import { createCookieConsent } from './banners-cookie-consent.svelte.js'

  const consent = createCookieConsent({
    onDecide: (choice) => { document.cookie = `consent=${JSON.stringify(choice)}; path=/; max-age=15552000; samesite=lax` },
  })
</script>

{#if consent.saved}
  <p>Cookies allowed: {consent.summary} <button onclick={consent.customize}>Change</button></p>
{:else}
  <div role="region" aria-label="Cookie consent">
    <button onclick={consent.rejectAll}>Reject non-essential</button>
    <button onclick={consent.acceptAll}>Accept all</button>
    <button onclick={consent.customize}>Customize</button>
  </div>
{/if}

{#if consent.settingsOpen}
  {#each consent.categories as c (c.id)}
    <label><input {...consent.switchProps(c.id)} /> {c.name}</label>
  {/each}
  <button onclick={consent.save}>Save settings</button>
{/if}

See also

Live examples

  • Announcement bar block - Slim bar above a mock app header with an SvBadge, one line of news, a changelog link and dismiss. Dismissing fires an SvToaster toast with Undo, and an SvButton brings the bar back.
  • 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.
  • Cookie consent block - Consent bar with Accept all, Reject non-essential and Customize. Customize opens an SvModal with an SvSwitchButton per category (Necessary locked on). The saved choice becomes a one-line summary with Change settings.
  • Billing warning banner block - Failed-payment banner with an SvBadge counting days left. Update payment method opens an SvModal card form: SvTextInput plus SvMaskedInput for card number (Luhn check), expiry and CVC. Saving resolves the banner to success.