Dialog
An overlay surface that holds focus until the user resolves a task.
Basic
A trigger opens the dialog via data-stisla-dialog-trigger="<id>". The panel carries an optional .dialog__header with the title, a .dialog__body for content, and an optional .dialog__footer for actions. .dialog__close is a floating frosted chip that sits inside .dialog__content so it works whether or not a header is present. Add autofocus to any control inside the panel to land focus there on open.
<button type="button" class="btn btn--primary" data-stisla-dialog-trigger="dialogBasic">
Invite a teammate
</button>
<div class="dialog" id="dialogBasic" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-labelledby="dialogBasicLabel" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close">
<i data-lucide="x"></i>
</button>
<div class="dialog__header">
<h3 class="dialog__title" id="dialogBasicLabel">Invite a teammate</h3>
</div>
<div class="dialog__body">
<p>Send a link by email. The invite expires in seven days.</p>
<label for="dialogBasicEmail" class="form-label">Email</label>
<input type="email" class="input" id="dialogBasicEmail" placeholder="name@example.com" autocomplete="email" autofocus>
</div>
<div class="dialog__footer">
<button type="button" class="btn btn--ghost btn--neutral" data-stisla-dialog-dismiss>Cancel</button>
<button type="button" class="btn btn--primary" data-stisla-dialog-dismiss>Send invite</button>
</div>
</div>
</div>
</div>Sizes
Add .dialog--sm, .dialog--lg, or .dialog--xl on the .dialog root to swap widths. The default sits in the middle of the scale. .dialog--almost-fullscreen keeps a 2.5rem breathing strip around the panel so the page still hints through; .dialog--fullscreen edge-bleeds the panel to the viewport.
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn--outline btn--neutral" data-stisla-dialog-trigger="dialogSm">Small</button>
<button type="button" class="btn btn--outline btn--neutral" data-stisla-dialog-trigger="dialogDefault">Default</button>
<button type="button" class="btn btn--outline btn--neutral" data-stisla-dialog-trigger="dialogLg">Large</button>
<button type="button" class="btn btn--outline btn--neutral" data-stisla-dialog-trigger="dialogXl">Extra large</button>
<button type="button" class="btn btn--outline btn--neutral" data-stisla-dialog-trigger="dialogAlmostFs">Almost fullscreen</button>
<button type="button" class="btn btn--outline btn--neutral" data-stisla-dialog-trigger="dialogFs">Fullscreen</button>
</div>
<div class="dialog dialog--sm" id="dialogSm" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__header"><h3 class="dialog__title">Small</h3></div>
<div class="dialog__body">
<p class="m-0">A compact card for confirmations and lightweight pickers.</p>
</div>
</div>
</div>
</div>
<div class="dialog" id="dialogDefault" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__header"><h3 class="dialog__title">Default</h3></div>
<div class="dialog__body">
<p class="m-0">The canonical width for an invite form, an edit dialog, or a focused capture.</p>
</div>
</div>
</div>
</div>
<div class="dialog dialog--lg" id="dialogLg" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__header"><h3 class="dialog__title">Large</h3></div>
<div class="dialog__body">
<p class="m-0">Room for a settings panel, a property inspector, or a small dashboard.</p>
</div>
</div>
</div>
</div>
<div class="dialog dialog--xl" id="dialogXl" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__header"><h3 class="dialog__title">Extra large</h3></div>
<div class="dialog__body">
<p class="m-0">A canvas for media, detail views, or a side-by-side comparison.</p>
</div>
</div>
</div>
</div>
<div class="dialog dialog--almost-fullscreen" id="dialogAlmostFs" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__header"><h3 class="dialog__title">Almost fullscreen</h3></div>
<div class="dialog__body">
<p class="m-0">A breathing strip around the panel so the page still hints through. Useful when you want immersion without losing the surrounding context entirely.</p>
</div>
</div>
</div>
</div>
<div class="dialog dialog--fullscreen" id="dialogFs" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__header"><h3 class="dialog__title">Fullscreen</h3></div>
<div class="dialog__body">
<p class="m-0">Edge to edge for an immersive flow. Useful for onboarding or a focused editor.</p>
</div>
</div>
</div>
</div>Positioning
The panel centers vertically by default. Add .dialog__panel--top to drop in from above, or .dialog__panel--bottom to anchor it to the lower edge.
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn--outline btn--neutral" data-stisla-dialog-trigger="dialogTop">Top</button>
<button type="button" class="btn btn--outline btn--neutral" data-stisla-dialog-trigger="dialogCenter">Center</button>
<button type="button" class="btn btn--outline btn--neutral" data-stisla-dialog-trigger="dialogBottom">Bottom</button>
</div>
<div class="dialog" id="dialogTop" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel dialog__panel--top">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__header"><h3 class="dialog__title">Pinned to top</h3></div>
<div class="dialog__body">
<p class="m-0">Reads as a notification surface dropping in from above.</p>
</div>
</div>
</div>
</div>
<div class="dialog" id="dialogCenter" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__header"><h3 class="dialog__title">Centered</h3></div>
<div class="dialog__body">
<p class="m-0">The standard choice for confirmations and focused tasks.</p>
</div>
</div>
</div>
</div>
<div class="dialog" id="dialogBottom" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel dialog__panel--bottom">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__header"><h3 class="dialog__title">Pinned to bottom</h3></div>
<div class="dialog__body">
<p class="m-0">Reads as a sheet on mobile, an action drawer on desktop.</p>
</div>
</div>
</div>
</div>Scrollable
Add .dialog__panel--scrollable to cap the panel at the viewport. The body scrolls while the header and footer stay pinned.
<button type="button" class="btn btn--primary" data-stisla-dialog-trigger="dialogScroll">
Read terms
</button>
<div class="dialog" id="dialogScroll" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-labelledby="dialogScrollLabel" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel dialog__panel--scrollable">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__header">
<h3 class="dialog__title" id="dialogScrollLabel">Terms of service</h3>
</div>
<div class="dialog__body">
<p>Welcome. By using this service you agree to the terms below. Please read carefully before continuing.</p>
<p>You may use the service for any lawful purpose. You may not use it to do harm, to interfere with other users, or to break the law where you live or where our servers run.</p>
<p>We collect the minimum data needed to deliver the product. We don't sell it, we don't share it with advertisers, and we delete it when you ask.</p>
<p>You own the content you put in. We get a limited license to store, process, and display it back to you and to anyone you've shared it with.</p>
<p>The service is provided as-is. We work hard to keep it up, but we can't promise zero downtime. If something breaks and costs you money, our liability is capped at what you've paid us in the last twelve months.</p>
<p>We may update these terms. If we do, we'll tell you in-app and give you a chance to read the new version before it takes effect.</p>
<p>Questions go to <a href="#" class="link">support@example.com</a>. We try to respond within one business day.</p>
<p>Last updated 12 May 2026.</p>
<p class="m-0">Thanks for being here.</p>
</div>
<div class="dialog__footer">
<button type="button" class="btn btn--ghost btn--neutral" data-stisla-dialog-dismiss>Decline</button>
<button type="button" class="btn btn--primary" data-stisla-dialog-dismiss>Accept</button>
</div>
</div>
</div>
</div>Static backdrop
Set data-stisla-dialog-backdrop="static" and data-stisla-dialog-keyboard="false" to force a deliberate dismiss. Clicking the backdrop wobbles the panel; Esc stays inert. Explicit dismiss controls still close.
<button type="button" class="btn btn--primary" data-stisla-dialog-trigger="dialogStatic">
Set a password
</button>
<div class="dialog" id="dialogStatic" data-stisla-dialog data-stisla-dialog-backdrop="static" data-stisla-dialog-keyboard="false" data-state="closed" role="dialog" aria-modal="true" aria-labelledby="dialogStaticLabel" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<div class="dialog__header">
<h3 class="dialog__title" id="dialogStaticLabel">Choose a password</h3>
</div>
<div class="dialog__body">
<label for="staticPwd" class="form-label">New password</label>
<input type="password" class="input" id="staticPwd" autocomplete="new-password">
<p class="form-text mt-1">At least twelve characters. Mix letters, numbers, and a symbol.</p>
</div>
<div class="dialog__footer">
<button type="button" class="btn btn--ghost btn--neutral" data-stisla-dialog-dismiss>Cancel</button>
<button type="button" class="btn btn--primary" data-stisla-dialog-dismiss>Save password</button>
</div>
</div>
</div>
</div>Media hero
Drop the header when the leading row is media. The floating close still sits at the top right and reads cleanly against the image.
<button type="button" class="btn btn--primary" data-stisla-dialog-trigger="dialogHero">
Open
</button>
<div class="dialog" id="dialogHero" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-labelledby="dialogHeroLabel" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close">
<i data-lucide="x"></i>
</button>
<img class="d-block w-100" src="https://images.unsplash.com/photo-1500382017468-9049fed747ef?w=1200&q=80" alt="" style="height: auto;">
<div class="dialog__body">
<h4 class="m-0 mb-1 fw-semibold" id="dialogHeroLabel" style="font-size: 1.0625rem;">Autumn release</h4>
<p class="text-muted-foreground m-0">A round-up of what's new this season, with notes on what's still in flight.</p>
</div>
</div>
</div>
</div>Lightbox
Override the surface vars on a single dialog to turn it into a frame for media. The blurred backdrop and the floating .dialog__close carry the affordance.
<a class="d-inline-block" href="#" data-stisla-dialog-trigger="dialogLightbox" style="cursor: zoom-in;">
<img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&q=80" alt="" width="240" height="160" style="border-radius: var(--st-radius); object-fit: cover;">
</a>
<div class="dialog dialog--xl" id="dialogLightbox" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1" style="--dialog-bg: transparent; --dialog-border-color: transparent; --dialog-shadow: none;">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close">
<i data-lucide="x"></i>
</button>
<img class="d-block w-100" src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=2000&q=85" alt="" style="height: auto; max-height: calc(100dvh - 5rem); object-fit: contain; border-radius: var(--st-radius);">
</div>
</div>
</div>Confirmation
An alert-dialog pattern for destructive actions. A tinted icon sets the tone, the heading names the action, the description explains the consequence.
<button type="button" class="btn btn--outline btn--danger" data-stisla-dialog-trigger="dialogConfirm">
Delete workspace
</button>
<div class="dialog dialog--sm" id="dialogConfirm" data-stisla-dialog data-state="closed" role="alertdialog" aria-modal="true" aria-labelledby="dialogConfirmLabel" aria-describedby="dialogConfirmDesc" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close"><i data-lucide="x"></i></button>
<div class="dialog__body text-center pt-6">
<span class="icon-box icon-box--danger icon-box--round mb-3" style="--icon-box-size: 3rem; --icon-box-icon-size: 1.25rem;">
<i data-lucide="trash-2"></i>
</span>
<h3 class="dialog__title m-0 mb-1" id="dialogConfirmLabel">Delete this workspace?</h3>
<p class="text-muted-foreground m-0" id="dialogConfirmDesc">This removes every project, file, and member. The action can't be undone.</p>
</div>
<div class="dialog__footer justify-content-center">
<button type="button" class="btn btn--ghost btn--neutral" data-stisla-dialog-dismiss>Cancel</button>
<button type="button" class="btn btn--danger" data-stisla-dialog-dismiss>Delete</button>
</div>
</div>
</div>
</div>Success
A celebratory state for the end of a flow. The icon sits on top, the heading and description fill the middle, and a single block button takes the user to the next step.
<button type="button" class="btn" data-stisla-dialog-trigger="dialogSuccess" style="--btn-tone: var(--st-success); --btn-color: var(--st-success-foreground);">
Submit order
</button>
<div class="dialog dialog--sm" id="dialogSuccess" data-stisla-dialog data-state="closed" role="dialog" aria-modal="true" aria-labelledby="dialogSuccessLabel" aria-hidden="true" tabindex="-1">
<div class="dialog__backdrop" data-stisla-dialog-dismiss></div>
<div class="dialog__panel">
<div class="dialog__content">
<button type="button" class="dialog__close" data-stisla-dialog-dismiss aria-label="Close">
<i data-lucide="x"></i>
</button>
<div class="dialog__body text-center pt-6">
<span class="icon-box icon-box--success icon-box--round mb-3" style="--icon-box-size: 3.5rem; --icon-box-icon-size: 1.5rem;">
<i data-lucide="check"></i>
</span>
<h3 class="dialog__title m-0 mb-1" id="dialogSuccessLabel">Order placed</h3>
<p class="text-muted-foreground m-0">We've emailed the receipt and a tracking link. Delivery lands in two to three business days.</p>
</div>
<div class="dialog__footer">
<button type="button" class="btn w-100" data-stisla-dialog-dismiss style="--btn-tone: var(--st-success); --btn-color: var(--st-success-foreground);">View order</button>
</div>
</div>
</div>
</div>Customization
Twenty-two variables retune .dialog without touching component CSS. Override on the .dialog element, on a parent scope, or on :root. Size modifiers --sm / --lg / --xl retune --dialog-width in their own scope. The dark-mode block flips the close chip from a solid dark overlay to a subtle light translucent overlay so it reads against the deep dark surface.
Geometry
| Variable | Default | Use |
|---|---|---|
--dialog-width |
28rem | Max panel width. Retuned by .dialog--sm / --lg / --xl in modifier scope. |
--dialog-margin |
1rem | Breathing space between panel and viewport edge. |
--dialog-padding |
calc(1.25rem * var(--st-density)) | Header, body, and footer inset. |
--dialog-radius |
var(--st-radius-lg) | Content corner radius. Large-surface tier, matches card and accordion. |
--dialog-z-index |
1055 | Stack level. Sits above sidebar and navbar. |
Surface
| Variable | Default | Use |
|---|---|---|
--dialog-bg |
var(--st-surface) | Content card background. |
--dialog-color |
var(--st-foreground) | Body and title text. |
--dialog-border-color |
var(--st-border) | Subtle content rim. |
--dialog-shadow |
0 10px 40px -10px color-mix(in oklch, black 35%, transparent) | Lift off the page. Set to none for a flat dialog. |
Backdrop
| Variable | Default | Use |
|---|---|---|
--dialog-backdrop-bg |
oklch(0 0 0 / 0.55) | Translucent dim layer. Solid dark in both themes so the dialog reads as the foreground. |
--dialog-backdrop-blur |
12px | Backdrop blur radius. Set to 0 for a flat dim. |
Title
| Variable | Default | Use |
|---|---|---|
--dialog-title-font-size |
1.125rem | Title text size. Independent of the heading tag wrapping it. |
--dialog-title-font-weight |
600 | Title weight. |
Close chip
| Variable | Default | Use |
|---|---|---|
--dialog-close-size |
1.75rem | Width and height of the dismiss chip. |
--dialog-close-bg |
oklch(0 0 0 / 0.35) light, oklch(1 0 0 / 0.1) dark | Frosted chip background. Translucent dark over light surface; subtle light translucent over dark surface. |
--dialog-close-bg-hover |
oklch(0 0 0 / 0.55) light, oklch(1 0 0 / 0.18) dark | Hover fill. Same inversion logic as the rest state. |
--dialog-close-color |
oklch(1 0 0 / 0.92) light, oklch(1 0 0 / 0.85) dark | Icon glyph color. Stays white in both themes so the chip reads over any surface. |
--dialog-close-color-hover |
oklch(1 0 0) | Icon glyph color on hover. Pure white in both themes. |
Footer
| Variable | Default | Use |
|---|---|---|
--dialog-footer-padding-y |
calc(0.875rem * var(--st-density)) | Vertical padding inside the footer. Slightly tighter than the body padding so the seam reads as an action row. |
--dialog-footer-bg |
var(--st-surface-2) | Footer background. The alt-surface tier sets the action row apart from the body without a bold paint. Set to transparent for a flat footer. |
--dialog-footer-border-color |
var(--st-border) | Top divider line. Set to transparent to drop the seam entirely. |
Motion
| Variable | Default | Use |
|---|---|---|
--dialog-transition-duration |
0.2s | Open and close fade plus panel slide. Zeroed under prefers-reduced-motion. |