Overlays: popover, tooltip, modal

SvPopover (anchored floating panel, click/hover/manual), SvTooltip (delayed hover/focus tip, aria-describedby) and SvModal (focus-trapped dialog, Escape / backdrop close, optionally draggable + resizable). All portal to <body>, animate in, and respect reduced-motion.

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 overlay trio of SvGrid UI in Svelte 5: SvPopover, an anchored floating panel opened by click, hover or manually with a minimum width, SvTooltip, a delayed hover and focus tip wired through aria-describedby, and SvModal, a focus-trapped dialog closed by Escape or the backdrop that can be draggable by its header and resizable from the corner. All portal to the body, animate in, share one positioning engine and respect reduced motion.

SvPopover, SvTooltip and SvModal - the overlay trio. All portal to <body> (never clipped), animate in, and share the popover positioning engine. The modal is optionally draggable (by its header) and resizable (bottom-right).

Imports, features and API used

Imports: @svgrid/grid

Frequently asked questions

How is the tooltip announced?

The trigger gets aria-describedby pointing at the tip, so screen readers read the text on focus, and the tip shows after a short delay on hover or focus and hides on blur.

How does the modal manage focus?

Focus moves into the dialog on open, Tab cycles inside it, Escape or a backdrop click closes it and focus returns to the trigger, with page scroll locked while it is open.

When do I use a popover instead of a modal?

For contextual content anchored to a control, such as a filter panel or a quick form, where the page should stay usable; a modal is for tasks that must be completed or dismissed.

Related documentation

Related articles

Source code (288-overlays.svelte)

<script lang="ts">
  /**
   * SvPopover, SvTooltip and SvModal - the overlay trio. All portal to <body>
   * (never clipped), animate in, and share the popover positioning engine. The
   * modal is optionally draggable (by its header) and resizable (bottom-right).
   */
  import { SvPopover, SvTooltip, SvModal, SvButton } from '@svgrid/grid'

  let modalOpen = $state(false)
  let draggable = $state(true)
  let resizable = $state(true)
</script>

<div class="wrap">
  <header>
    <h2>Overlays</h2>
    <p>Popover, tooltip and modal - portalled, animated, keyboard-accessible (Escape to close, focus-trapped modal).</p>
  </header>

  <section>
    <h3>Tooltip</h3>
    <p class="row">
      Hover or focus these:
      <SvTooltip text="Save the current record (Ctrl+S)"><SvButton size="sm">Save</SvButton></SvTooltip>
      <SvTooltip text="This permanently deletes the row" placement="bottom"><SvButton size="sm" variant="danger">Delete</SvButton></SvTooltip>
    </p>
  </section>

  <section>
    <h3>Popover</h3>
    <SvPopover minWidth={240}>
      {#snippet anchor()}<SvButton>Filter options</SvButton>{/snippet}
      <div class="pop">
        <strong>Quick filter</strong>
        <label><input type="checkbox" checked /> Active only</label>
        <label><input type="checkbox" /> Include archived</label>
        <p class="muted">Click outside or press Escape to dismiss.</p>
      </div>
    </SvPopover>
  </section>

  <section>
    <h3>Modal</h3>
    <div class="row">
      <SvButton onclick={() => (modalOpen = true)}>Open modal</SvButton>
      <label class="chk"><input type="checkbox" bind:checked={draggable} /> Draggable</label>
      <label class="chk"><input type="checkbox" bind:checked={resizable} /> Resizable</label>
    </div>

    <SvModal bind:open={modalOpen} title="Edit item" {draggable} {resizable}>
      <p>Drag me by the header{resizable ? ', or resize from the bottom-right corner' : ''}. Focus is trapped inside, and Escape or the backdrop closes me.</p>
      <label class="field">Name<input type="text" value="Acme Corp" /></label>
      <label class="field">Notes<textarea rows="3">A preferred vendor.</textarea></label>
      {#snippet footer()}
        <SvButton variant="ghost" onclick={() => (modalOpen = false)}>Cancel</SvButton>
        <SvButton onclick={() => (modalOpen = false)}>Save</SvButton>
      {/snippet}
    </SvModal>
  </section>
</div>

<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); }
  .pop { display: flex; flex-direction: column; gap: 8px; }
  .pop label { display: flex; align-items: center; gap: 7px; font-size: 13px; }
  .muted { color: var(--sg-muted, #94a3b8); font-size: 12px; margin: 4px 0 0; }
  .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 textarea { 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>

View this example on GitHub

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.