Modal

Accessible modal dialogs for any use case.

zweiundeins <sb-modal> dialog overlay popup confirm Since 2026-09-210Open in playground Edit on GitHub

Playground

Are you ready to launch?CancelLaunch

A modal built on the native <dialog>. The browser traps focus, closes on Escape and handles the top layer. Open it with show() and close it with close(), or mark footer buttons with data-sb-close.

Examples

Open from Datastar

data-ref gives you the element as a signal, so any Datastar expression can call its methods.

Launch sequence Are you ready to launch? This cannot be undone. Cancel Launch

<div>
  <sb-button data-on:click="$_launch.show()">Launch sequence</sb-button>
  <sb-modal data-ref:_launch heading="Mission Control" data-on:sb-close="$_answer = evt.detail.value">
    Are you ready to launch? This cannot be undone.
    <sb-button slot="footer" variant="ghost" data-sb-close="cancel">Cancel</sb-button>
    <sb-button slot="footer" data-sb-close="launch">Launch</sb-button>
  </sb-modal>
  <p data-signals:_answer="''" data-show="$_answer" data-text="'You chose: ' + $_answer"></p>
</div>

Inline

The inline attribute renders the panel in place, which is handy for docs and previews.

All systems nominal. Welcome aboard.
<sb-modal inline heading="Docking complete" closable="false">
  All systems nominal. Welcome aboard.
</sb-modal>

Opened by the server

In a CQRS app the server owns "is the dialog open?". Render <sb-modal open> and your SSE stream decides. Datastar's morph keeps the element, and its state lives inside the component.

Accessibility

The native modal <dialog> sets aria-modal, traps focus and restores it on close. The heading labels the dialog. Escape and a click on the backdrop both close it, and sb-close reports why in detail.reason.

Installation

Add Datastar with Rocket and the Starbase autoloader once per page, then use the tag. The autoloader imports each component the first time its tag appears, including tags added later by a Datastar morph.

<!-- Once per page: Datastar with Rocket, and the Starbase autoloader.
     It loads every <sb-…> component the first time its tag appears. -->
<script type="importmap">
  { "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" } }
</script>
<script type="module" src="https://rocket.libretto.ch/c/autoloader.js"></script>
<!-- Optional, no flash of undefined elements: class="sb-cloak" on <html>, and -->
<style>.sb-cloak :not(:defined) { visibility: hidden }</style>

<sb-modal inline heading="Mission Control" style="--sb-radius-lg: 8px"><span>Are you ready to launch?</span><sb-button slot="footer" size="sm" variant="outline" data-sb-close>Cancel</sb-button><sb-button slot="footer" size="sm" data-sb-close>Launch</sb-button></sb-modal>

<!-- Or skip the autoloader and load just this component: -->
<!-- <script type="module" src="https://rocket.libretto.ch/c/modal/modal.js"></script> -->

API reference

Props

AttributeTypeDefaultDescription
headingstring"Dialog"Title of the dialog.
openbooleanfalseOpen on first render. Use show() and close() afterwards.
inlinebooleanfalseRender in place, without an overlay (previews, docs).
closablebooleantrueShow the close button.

Slots

NameDescription
defaultDialog body.
footerAction buttons. Elements with data-sb-close close the dialog.

Events

NameDescription
sb-openAfter opening.
sb-closeAfter closing. detail: { reason, value }.