Dialog

A Tailwind CSS dialog component for modals, sheets, and non-modal dialogs.

Dialog Title

This is a modal dialog. Click the backdrop or press Escape to close.

<button class="btn" data-sp-toggle="dialog" data-sp-target="#demo-modal">
  Open Dialog
</button>
 
<dialog
  id="demo-modal"
  class="dialog"
  aria-labelledby="demo-title"
  aria-describedby="demo-desc"
>
  <div class="dialog-backdrop"></div>
  <div class="dialog-panel">
    <button
      class="btn btn-ghost btn-icon absolute top-2 right-2"
      aria-label="Close"
      data-sp-dismiss="dialog"
    >
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
    </button>
    <h2 id="demo-title" class="text-lg font-semibold mb-2">Dialog Title</h2>
    <p id="demo-desc" class="text-muted-foreground mb-4">
      This is a modal dialog. Click the backdrop or press Escape to close.
    </p>
    <div class="flex gap-2 justify-end">
      <button class="btn btn-outline" data-sp-dismiss="dialog">Cancel</button>
      <button class="btn" data-sp-dismiss="dialog">Confirm</button>
    </div>
  </div>
</dialog>

This component uses dialog.show() instead of dialog.showModal(), so the backdrop is what creates the modal behavior. Add .dialog-backdrop to block interaction with the page behind it, or omit it for a non-modal dialog.

Non-modal

You can interact with the page behind this dialog.

<button
  class="btn btn-outline"
  data-sp-toggle="dialog"
  data-sp-target="#modal-example"
>
  Open Modal
</button>
<button
  class="btn btn-outline"
  data-sp-toggle="dialog"
  data-sp-target="#nonmodal-example"
>
  Open Non-modal
</button>
 
<dialog
  id="modal-example"
  class="dialog"
  aria-labelledby="modal-title"
  aria-describedby="modal-desc"
>
  <div class="dialog-backdrop"></div>
  <div class="dialog-panel">
    <h2 id="modal-title" class="text-lg font-semibold mb-2">Modal</h2>
    <p id="modal-desc" class="text-muted-foreground mb-4">
      Background is blocked. Click backdrop or press Escape to close.
    </p>
    <button class="btn" data-sp-dismiss="dialog">Close</button>
  </div>
</dialog>
 
<dialog
  id="nonmodal-example"
  class="dialog"
  aria-labelledby="nonmodal-title"
  aria-describedby="nonmodal-desc"
>
  <div class="dialog-panel">
    <h2 id="nonmodal-title" class="text-lg font-semibold mb-2">Non-modal</h2>
    <p id="nonmodal-desc" class="text-muted-foreground mb-4">
      You can interact with the page behind this dialog.
    </p>
    <button class="btn" data-sp-dismiss="dialog">Close</button>
  </div>
</dialog>

Static backdrop

Use data-sp-backdrop="static" to prevent closing when clicking the backdrop or pressing Escape.

Are you sure?

This action cannot be undone. Backdrop clicks and Escape are disabled.

<button
  class="btn btn-destructive"
  data-sp-toggle="dialog"
  data-sp-target="#static-modal"
>
  Delete
</button>
 
<dialog
  id="static-modal"
  class="dialog"
  data-sp-backdrop="static"
  aria-labelledby="static-title"
  aria-describedby="static-desc"
>
  <div class="dialog-backdrop"></div>
  <div class="dialog-panel">
    <h2 id="static-title" class="text-lg font-semibold mb-2">Are you sure?</h2>
    <p id="static-desc" class="text-muted-foreground mb-4">
      This action cannot be undone. Backdrop clicks and Escape are disabled.
    </p>
    <div class="flex gap-2 justify-end">
      <button class="btn btn-outline" data-sp-dismiss="dialog">Cancel</button>
      <button class="btn btn-destructive" data-sp-dismiss="dialog">
        Delete
      </button>
    </div>
  </div>
</dialog>

Customization

Override positioning, sizing, and styles to create custom dialog variants like sheets, fullscreen modals, or custom backdrops.

Sheet

This sheet slides in from the left. Great for navigation menus or sidebars.

Fullscreen Modal

This modal takes up the entire screen.

Custom Styles

Override the backdrop and panel with gradients, blur, or any custom styles.

<button
  class="btn btn-outline"
  data-sp-toggle="dialog"
  data-sp-target="#sheet-example"
>
  Sheet
</button>
<button
  class="btn btn-outline"
  data-sp-toggle="dialog"
  data-sp-target="#fullscreen-example"
>
  Fullscreen
</button>
<button
  class="btn btn-outline"
  data-sp-toggle="dialog"
  data-sp-target="#gradient-example"
>
  Gradient Backdrop
</button>
 
<dialog
  id="sheet-example"
  class="dialog"
  aria-labelledby="sheet-title"
  aria-describedby="sheet-desc"
>
  <div class="dialog-backdrop"></div>
  <div
    class="dialog-panel no-animation top-0 left-0 h-dvh w-72 max-h-none rounded-none translate-x-0 translate-y-0 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left duration-300"
  >
    <button
      class="btn btn-ghost btn-icon absolute top-2 right-2"
      aria-label="Close"
      data-sp-dismiss="dialog"
    >
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
    </button>
    <h2 id="sheet-title" class="text-lg font-semibold mb-2">Sheet</h2>
    <p id="sheet-desc" class="text-muted-foreground">
      This sheet slides in from the left. Great for navigation menus or
      sidebars.
    </p>
  </div>
</dialog>
 
<dialog
  id="fullscreen-example"
  class="dialog"
  aria-labelledby="fullscreen-title"
  aria-describedby="fullscreen-desc"
>
  <div class="dialog-backdrop"></div>
  <div class="dialog-panel max-w-none h-screen">
    <div class="flex justify-between items-center mb-4">
      <h2 id="fullscreen-title" class="text-lg font-semibold">
        Fullscreen Modal
      </h2>
      <button
        class="btn btn-ghost btn-icon"
        aria-label="Close"
        data-sp-dismiss="dialog"
      >
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
      </button>
    </div>
    <p id="fullscreen-desc" class="text-muted-foreground">
      This modal takes up the entire screen.
    </p>
  </div>
</dialog>
 
<dialog
  id="gradient-example"
  class="dialog"
  aria-labelledby="gradient-title"
  aria-describedby="gradient-desc"
>
  <div
    class="dialog-backdrop bg-linear-to-br from-pink-500/60 via-purple-500/60 to-cyan-500/60 backdrop-blur-sm"
  ></div>
  <div
    class="dialog-panel bg-linear-to-br from-purple-950 to-slate-900 text-white border border-purple-500/30"
  >
    <button
      class="btn btn-ghost btn-icon absolute top-2 right-2 text-white hover:bg-white/10"
      aria-label="Close"
      data-sp-dismiss="dialog"
    >
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
    </button>
    <h2 id="gradient-title" class="text-lg font-semibold mb-2">
      Custom Styles
    </h2>
    <p id="gradient-desc" class="text-purple-200 mb-4">
      Override the backdrop and panel with gradients, blur, or any custom
      styles.
    </p>
    <button
      class="btn w-full bg-purple-600 hover:bg-purple-700 text-white"
      data-sp-dismiss="dialog"
    >
      Got it
    </button>
  </div>
</dialog>

How it works

This component uses the native HTML <dialog> element with a small JavaScript module that handles opening, closing, animations, and focus management.

Structure

A dialog consists of three parts:

  1. <dialog class="dialog"> - The native dialog element, styled to be a fixed fullscreen container
  2. .dialog-backdrop - An overlay that blocks interaction with the page (optional, omit for non-modal)
  3. .dialog-panel - The content panel that holds your dialog content
<dialog id="my-dialog" class="dialog">
  <div class="dialog-backdrop"></div>
  <div class="dialog-panel">
    <!-- Your content here -->
  </div>
</dialog>

Opening and closing

Use data attributes to control the dialog without writing JavaScript.

To open a dialog, add data-sp-toggle="dialog" and data-sp-target to a button:

<button data-sp-toggle="dialog" data-sp-target="#my-dialog">Open Dialog</button>

To close from inside the dialog, add data-sp-dismiss="dialog" to any button. The JavaScript will find the closest parent <dialog> element and close it:

<button data-sp-dismiss="dialog">Close</button>

Clicking the backdrop or pressing Escape also closes the dialog. To prevent this, add data-sp-backdrop="static" to the dialog element:

<dialog id="confirm-dialog" class="dialog" data-sp-backdrop="static">
  <!-- User must click a button to close -->
</dialog>

The component uses dialog.show() rather than dialog.showModal(), which allows more flexible backdrop styling. Modal behavior (blocking page interaction) comes from the .dialog-backdrop element instead.

For programmatic control, use the global StartingPointUI.dialog module:

const dialog = document.querySelector("#my-dialog");
 
StartingPointUI.dialog.open(dialog);
StartingPointUI.dialog.close(dialog);
StartingPointUI.dialog.toggle(dialog);

Animation

The dialog includes default fade and zoom animations. When opened, the JavaScript sets data-state="open" on the backdrop and panel. When closing, it sets data-state="closed" and waits for animations to complete before calling dialog.close().

To customize animations, add no-animation to disable the defaults and use your own classes with data-[state=open]: and data-[state=closed]: selectors:

<div
  class="dialog-panel no-animation
  data-[state=open]:animate-in data-[state=open]:slide-in-from-bottom
  data-[state=closed]:animate-out data-[state=closed]:slide-out-to-bottom"
>
  <!-- Slides in from bottom instead of zooming -->
</div>

Default Animation

The backdrop fades while the panel fades and zooms.

No Animation

Add the `no-animation` class to disable the default animations.

Slide from Bottom

Use `no-animation` and add your own animation classes.

Slow Animation

Override the duration with utility classes like `duration-500`.

<button
  class="btn btn-outline"
  data-sp-toggle="dialog"
  data-sp-target="#anim-default"
>
  Default
</button>
<button
  class="btn btn-outline"
  data-sp-toggle="dialog"
  data-sp-target="#anim-none"
>
  No Animation
</button>
<button
  class="btn btn-outline"
  data-sp-toggle="dialog"
  data-sp-target="#anim-slide"
>
  Slide from Bottom
</button>
<button
  class="btn btn-outline"
  data-sp-toggle="dialog"
  data-sp-target="#anim-slow"
>
  Slow (500ms)
</button>
 
<dialog
  id="anim-default"
  class="dialog"
  aria-labelledby="anim-default-title"
  aria-describedby="anim-default-desc"
>
  <div class="dialog-backdrop"></div>
  <div class="dialog-panel">
    <h2 id="anim-default-title" class="text-lg font-semibold mb-2">
      Default Animation
    </h2>
    <p id="anim-default-desc" class="text-muted-foreground mb-4">
      The backdrop fades while the panel fades and zooms.
    </p>
    <button class="btn" data-sp-dismiss="dialog">Close</button>
  </div>
</dialog>
 
<dialog
  id="anim-none"
  class="dialog"
  aria-labelledby="anim-none-title"
  aria-describedby="anim-none-desc"
>
  <div class="dialog-backdrop no-animation"></div>
  <div class="dialog-panel no-animation">
    <h2 id="anim-none-title" class="text-lg font-semibold mb-2">
      No Animation
    </h2>
    <p id="anim-none-desc" class="text-muted-foreground mb-4">
      Add the `no-animation` class to disable the default animations.
    </p>
    <button class="btn" data-sp-dismiss="dialog">Close</button>
  </div>
</dialog>
 
<dialog
  id="anim-slide"
  class="dialog"
  aria-labelledby="anim-slide-title"
  aria-describedby="anim-slide-desc"
>
  <div class="dialog-backdrop"></div>
  <div
    class="dialog-panel no-animation data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-bottom duration-300"
  >
    <h2 id="anim-slide-title" class="text-lg font-semibold mb-2">
      Slide from Bottom
    </h2>
    <p id="anim-slide-desc" class="text-muted-foreground mb-4">
      Use `no-animation` and add your own animation classes.
    </p>
    <button class="btn" data-sp-dismiss="dialog">Close</button>
  </div>
</dialog>
 
<dialog
  id="anim-slow"
  class="dialog"
  aria-labelledby="anim-slow-title"
  aria-describedby="anim-slow-desc"
>
  <div class="dialog-backdrop duration-500"></div>
  <div class="dialog-panel duration-500">
    <h2 id="anim-slow-title" class="text-lg font-semibold mb-2">
      Slow Animation
    </h2>
    <p id="anim-slow-desc" class="text-muted-foreground mb-4">
      Override the duration with utility classes like `duration-500`.
    </p>
    <button class="btn" data-sp-dismiss="dialog">Close</button>
  </div>
</dialog>

The default animations use tw-animate-css utilities. You can customize or replace these with your own animations.

Accessibility

This component uses the native <dialog> element for proper screen reader announcements. The dialog JavaScript module provides Escape key handling for all dialogs, and focus trapping (Tab cycles through focusable elements) for modal dialogs.

For proper screen reader support, add these attributes to your dialogs:

AttributeDescription
aria-labelledby="title-id"Add to dialog to reference the title element
aria-describedby="desc-id"Add to dialog to reference the description
aria-label="Close"Add to icon-only buttons for screen reader labels
<dialog aria-labelledby="title" aria-describedby="desc">
  <div class="dialog-backdrop"></div>
  <div class="dialog-panel">
    <button aria-label="Close" data-sp-dismiss="dialog">
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
    </button>
    <h2 id="title">Title</h2>
    <p id="desc">Description</p>
  </div>
</dialog>

Class reference

All available classes for the dialog component.

ClassDescription
dialogBase class for the dialog element
dialog-backdropOverlay that blocks interaction with the page behind it
dialog-panelThe content panel inside the dialog
no-animationDisables default animations on backdrop or panel
<dialog class="dialog">
  <div class="dialog-backdrop"></div>
  <div class="dialog-panel">
    <!-- Content -->
  </div>
</dialog>

Data attributes

All data attributes for the dialog component.

AttributeDescription
data-sp-toggle="dialog"Add to a button to open the dialog in data-sp-target
data-sp-target="#id"Specifies which dialog to open
data-sp-dismiss="dialog"Add to a button inside the dialog to close it
data-sp-backdrop="static"Add to dialog to prevent closing on backdrop or Escape
data-stateSet to open or closed on backdrop and panel
<button data-sp-toggle="dialog" data-sp-target="#my-dialog">Open</button>
 
<dialog id="my-dialog" class="dialog" data-sp-backdrop="static">
  <div class="dialog-backdrop"></div>
  <div class="dialog-panel">
    <button data-sp-dismiss="dialog">Close</button>
  </div>
</dialog>