Dialogs

Stylised native HTML <dialog> modals with light-dismiss and transition animation styles.

Examples (anchor)

Please note. The dialogs will inherit whatever normalize/reset styles are present requiring only minor adjustments to suit base font property values, but as demonstrated they've been designed to work seamlessly with the StyleMods content styles.

The default button styles used in the examples below are also not included with the dialogs but are provided with the forms and buttons and/or the buttons module if required.

Default (anchor)

To open and close the dialogs a script (see below) adapted from Mark Otto's Fun with the dialog element is used but if required a <form method="dialog"> button can be used for closing dialogs containing form content.

Basic

Uses the dialog script for the open and close buttons plus also closes using keyboard Esc or Enter buttons.

Form method

Uses the script to open the dialog and form method for closing it plus also closes using keyboard Esc or Enter buttons.

Example HTML
<button data-dialog="#dialog">Basic</button>
<dialog id="dialog" style="--dialog-width:30rem;">
  <h3>Basic</h3>
  <p>Uses the dialog script for the open and close buttons plus also closes using keyboard <kbd>Esc</kbd> or <kbd>Enter</kbd> buttons.</p>
  <button class="close-dialog">Close by script</button>
</dialog>

<button data-dialog="#dialog-form">Form method</button>
<dialog id="dialog-form" style="--dialog-width:30rem;">
  <h3>Form method</h3>
  <p>Uses the script to open the dialog and form method for closing it plus also closes using keyboard <kbd>Esc</kbd> or <kbd>Enter</kbd> buttons.</p>
  <form method="dialog">
    <button>Close by form method</button>
  </form>
</dialog>

Light-dismiss (anchor)

The script's close dialog functionality can also be applied to the backdrop to enable clicking outside the dialog to close it mimicking the light-dismiss functionality of the popover API but retaining the default structure of dialog styles.

Light-dismiss

Uses the dialog script for the open and close buttons and light-dismiss behaviour, or also closes using the keyboard Esc or Enter buttons.

Form method + light-dismiss

Uses the script to open the dialog and light-dismiss behaviour and the form method for the closing button. Also closes using the keyboard Esc or Enter buttons.

Example HTML
<button data-dialog="#dialog-light-dismiss">Light-dismiss</button>
<dialog id="dialog-light-dismiss" style="--dialog-width:30rem;">
  <h3>Light-dismiss</h3>
  <p>Uses the dialog script for the open and close buttons and light-dismiss behaviour, or also closes using the keyboard <kbd>Esc</kbd> or <kbd>Enter</kbd> buttons.</p>
  <button class="close-dialog">Close by script</button>
  <div class="close-dialog"></div>
</dialog>

<button data-dialog="#dialog-form-light-dismiss">Form method + light-dismiss</button>
<dialog id="dialog-form-light-dismiss" style="--dialog-width:30rem;">
  <h3>Form method + light-dismiss</h3>
  <p>Uses the script to open the dialog and light-dismiss behaviour and the form method for the closing button. Also closes using the keyboard <kbd>Esc</kbd> or <kbd>Enter</kbd> buttons.</p>
  <form method="dialog">
    <button>Close by form method</button>
  </form>
  <div class="close-dialog"></div>
</dialog>

Close buttons (anchor)

Icon styles are also provided for the close buttons but please note these only include the styles for the icons, the default button property values (e.g. colors, active states) will be inherited from any reset/normalize or custom styles included.

Icon before

The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.

Icon after

The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.

Example HTML
<button data-dialog="#dialog-close-button-1">Icon before</button>
<dialog id="dialog-close-button-1" style="--dialog-width:30rem;">
  <h3>Icon before</h3>
  <p>The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.</p>
  <button class="close-dialog close-dialog-ico-b">Close</button>
  <div class="close-dialog"></div>
</dialog>

<button data-dialog="#dialog-close-button-2">Icon after</button>
<dialog id="dialog-close-button-2" style="--dialog-width:30rem;">
  <h3>Icon after</h3>
  <p>The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.</p>
  <button class="close-dialog close-dialog-ico-a">Close</button>
  <div class="close-dialog"></div>
</dialog>

An icon only style button designed to stay fixed to the top right corner of dialogs is also provided. The text is visually hidden but available to assistive browsing so the button meets accessibility requirements.

Icon only

The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.

The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.

Example HTML
<button data-dialog="#dialog-close-button-3">Icon only</button>
<dialog id="dialog-close-button-3" style="--dialog-width:30rem;">
  <h3>Icon only</h3>
  <p>The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.</p>
  <button class="close-dialog close-dialog-ico"><span>Close</span></button>
  <div class="close-dialog"></div>
</dialog>

<button data-dialog="#dialog-close-button-4">Icon only + text only</button>
<dialog id="dialog-close-button-4" style="--dialog-width:30rem;">
  <p>The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.</p>
  <button class="close-dialog close-dialog-ico"><span>Close</span></button>
  <div class="close-dialog"></div>
</dialog>

Dialog headers (anchor)

Header and body containers are provided to create dialogs with card-like title headings.

Header

The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.

Header + close icon

The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.

Example HTML
<button data-dialog="#dialog-header-1">Header</button>
<dialog id="dialog-header-1" style="--dialog-width:30rem;">
  <div class="dialog-header">
  <h3>Header</h3>
  </div>
  <div class="dialog-body">
  <p>The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.</p>
  <button class="close-dialog">Close</button>
  </div>
  <div class="close-dialog"></div>
</dialog>

<button data-dialog="#dialog-header-close-button-2">Header + close icon</button>
<dialog id="dialog-header-close-button-2" style="--dialog-width:30rem;">
  <div class="dialog-header">
  <h3>Header + close icon</h3>
  <button class="close-dialog close-dialog-ico btn-ico"><span class="vis-hidden">Close</span></button>
  </div>
  <div class="dialog-body">
  <p>The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.</p>
  <button class="close-dialog">Close</button>
  </div>
  <div class="close-dialog"></div>
</dialog>

Offcanvas (anchor)

Inline offcanvas dialogs are also provided:

Offcanvas start

The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.

Offcanvas end

The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.

Example HTML
<button data-dialog="#dialog-offcanvas-start">Offcanvas start</button>
<dialog id="dialog-offcanvas-start" class="dialog-offcanvas-start">
  <h2>Offcanvas start</h2>
  <p>The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.</p>
  <button class="close-dialog">Close</button>
  <div class="close-dialog"></div>
</dialog>

<button data-dialog="#dialog-offcanvas-end">Offcanvas end</button>
<dialog id="dialog-offcanvas-end" class="dialog-offcanvas-end">
  <h2>Offcanvas end</h2>
  <p>The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.</p>
  <button class="close-dialog">Close</button>
  <div class="close-dialog"></div>
</dialog>

These can also use the close button icons and/or dialog header styles:

Close icon

The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.

Header

The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.

Example HTML
<button data-dialog="#dialog-offcanvas-icons">Offcanvas + close icon</button>
<dialog id="dialog-offcanvas-icons" class="dialog-offcanvas-start">
  <h2>Close icon</h2>
  <p>The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.</p>
  <button class="close-dialog close-dialog-ico"><span>Close</span></button>
  <div class="close-dialog"></div>
</dialog>

<button data-dialog="#dialog-offcanvas-header">Offcanvas header</button>
<dialog id="dialog-offcanvas-header" class="dialog-offcanvas-end">
  <div class="dialog-header">
  <h3>Header</h3>
  <button class="close-dialog close-dialog-ico btn-ico"><span class="vis-hidden">Close</span></button>
  </div>
  <div class="dialog-body">
  <p>The quick brown fox jumps over the lazy dog followed by five boxing wizards jumping quickly.</p>
  <button class="close-dialog close-dialog-ico-b">Close</button>
  </div>
  <div class="close-dialog"></div>
</dialog>

Image dialogs (anchor)

Image (or figure) only dialog styles are provided and include an image styled button for gallery style dialogs:

Snowy forest clearing with sunlight filtering through tall trees
Snowy forest clearing with sunlight filtering through tall trees
Example HTML
<button class="dialog-btn-img" data-dialog="#dialog-img-1"><img src="/img/winter-forest-300.webp" alt="Snowy forest thumbnail" width="200"><span>View image</span></button>
<dialog id="dialog-img-1" class="dialog-img"> 
  <img src="/img/winter-forest-1024.webp" alt="Snowy forest clearing with sunlight filtering through tall trees" width="1024">
  <button class="close-dialog close-dialog-ico btn-ico"><span class="vis-hidden">Close</span></button>
  <div class="close-dialog"></div>
</dialog>

<button class="dialog-btn-img" data-dialog="#dialog-img-2"><img src="/img/winter-forest-300.webp" width="200"><span>View image</span></button>
<dialog id="dialog-img-2" class="dialog-figure">
  <button class="close-dialog close-dialog-ico btn-ico"><span class="vis-hidden">Close</span></button>
  <figure>
    <img src="/img/winter-forest-1024.webp" alt="" width="1024">
  <figcaption>Snowy forest clearing with sunlight filtering through tall trees</figcaption>
  </figure>
  <div class="close-dialog"></div>
</dialog>

The dialog headers can also be used with the image and/or figure image styles:

Header with image.

Snowy forest clearing with sunlight filtering through tall trees

Header with figure

Snowy forest clearing with sunlight filtering through tall trees.
Example HTML
<button class="dialog-btn-img" data-dialog="#dialog-img-3"><img src="/img/winter-forest-300.webp" alt="Snowy forest thumbnail" width="200"><span>View image</span></button>
<dialog id="dialog-img-3" class="dialog-img"> 
  <div class="dialog-header">
  <h3>Header with image.</h3>
  <button class="close-dialog close-dialog-ico btn-ico"><span class="vis-hidden">Close</span></button>
  </div>
  <img src="/img/winter-forest-1024.webp" alt="Snowy forest clearing with sunlight filtering through tall trees" width="1024">
  <div class="close-dialog"></div>
</dialog>

<button class="dialog-btn-img" data-dialog="#dialog-img-4"><img src="/img/winter-forest-300.webp" width="200"><span>View image</span></button>
<dialog id="dialog-img-4" class="dialog-figure">
  <div class="dialog-header">
  <h3>Header with figure</h3>
  <button class="close-dialog close-dialog-ico btn-ico"><span class="vis-hidden">Close</span></button>
  </div>
  <figure>
  <img src="/img/winter-forest-1024.webp" alt="" width="1024">
  <figcaption>Snowy forest clearing with sunlight filtering through tall trees.</figcaption>
  </figure>
  <div class="close-dialog"></div>
</dialog>

Transition (anchor)

The default .25s dialog opening transition timing can be customized inline as shown below:

Heading basic

The quick brown fox jumps over the lazy dog.

Heading basic

The quick brown fox jumps over the lazy dog.

Heading basic

The quick brown fox jumps over the lazy dog.

Examples HTML
<button data-dialog="#default-transition">Default (.25s)</button>
<dialog id="default-transition">
  <h2>Heading basic</h2>
  <p>The quick brown fox jumps over the lazy dog.</p>
  <button class="close-dialog">Close</button>
  <div class="close-dialog"></div>
</dialog>

<button data-dialog="#custom-transition">Custom (1.5s)</button>
<dialog id="custom-transition" style="--dialog-transition: 1.5s;">
  <h2>Heading basic</h2>
  <p>The quick brown fox jumps over the lazy dog.</p>
  <button class="close-dialog">Close</button>
  <div class="close-dialog"></div>
</dialog>

<button data-dialog="#no-transition">No transition (0s)</button>
<dialog id="no-transition" style="--dialog-transition: 0s;">
  <h2>Heading basic</h2>
  <p>The quick brown fox jumps over the lazy dog.</p>
  <button class="close-dialog">Close</button>
  <div class="close-dialog"></div>
</dialog>

The style is compiled within the media query (prefers-reduced-motion: no-preference) to respect user preferences but if preferred the dialogs can also be compiled without the transition styles (see below).

Fixed light and dark (anchor)

Light dialog

The quick brown fox jumps over the lazy dog.

Dark dialog

The quick brown fox jumps over the lazy dog.

Example HTML
<button data-dialog="#dialog-light" class="btn-light">Light dialog</button>
<dialog id="dialog-light" class="dialog-light">
  <h2>Light dialog</h2>
  <p>The quick brown fox jumps over the lazy dog.</p>
  <button class="close-dialog btn-light">Close</button>
  <div class="close-dialog"></div>
</dialog>

<button data-dialog="#dialog-dark" class="btn-dark">Dark dialog</button>
<dialog id="dialog-dark" class="dialog-dark">
  <h2>Dark dialog</h2>
  <p>The quick brown fox jumps over the lazy dog.</p>
  <button class="close-dialog btn-dark">Close</button>
  <div class="close-dialog"></div>
</dialog>

Using the module (anchor)

To use the module load the StyleMods scss directory as follows (changing the path to suit the source files location as required) then include the Sass mixin(s) as demonstrated below.

Default with transition styles:

custom.scss
@use "stylemods/scss" as *;
@include dialogs-css;

With no transition:

custom.scss
@use "stylemods/scss" as *;
@include dialogs-no-transition-css;

Or individually:

custom.scss
@use "stylemods/scss" as *;
@include dialog-styles-css;
@include dialog-transition-css;

Source code (anchor)

See customizing for information about using the Sass and CSS variables in the source code to customize the styles, and Sass functionality (on the using StyleMods page) for other ways to use the variables to create custom styles.

dialogs.scss
// ---------------------------------------------------------- 
// Dialogs
// ----------------------------------------------------------
$dialog-text-color:               var(--dialog-text, CanvasText) !default;
$dialog-margin-top:               var(--dialog-mt, 1rem) !default;
$dialog-padding-block:            var(--dialog-py, 1rem) !default;
$dialog-padding-inline:           var(--dialog-px, 1rem) !default;
$dialog-border-color:             var(--dialog-bd-color, color-mix(in srgb, CanvasText 15%, Canvas)) !default;
$dialog-radius:                   var(--dialog-radius, 0.188rem) !default;
$dialog-inner-radius:             calc($dialog-radius - 1px) !default;
$dialog-background-color:         var(--dialog-bg, Canvas) !default; 
$dialog-transition:               var(--dialog-transition, 0.25s) !default;
$dialog-backdrop:                 color-mix(in srgb, black 50%, transparent) !default;
$dialog-title-background-color:   var(--dialog-title-bg, color-mix(in srgb, CanvasText 4%, Canvas)) !default;
$dialog-focus-shadow:             var(--dialog-focus-shadow, 0 0 0 0.12rem) !default;
$dialog-focus-color:              var(--dialog-focus-color, color-mix(in srgb, CanvasText 22%, Canvas)) !default;

// Close button
$dialog-close-icon:               url("data:image/svg+xml,<svg viewBox='0 0 16 16' fill='currentColor' xmlns='http://www.w3.org/2000/svg'><path d='m2.47 3.53 1.06-1.06 4.47 4.47 4.47-4.47 1.06 1.06-4.47 4.47 4.47 4.47-1.06 1.06-4.47-4.47-4.47 4.47-1.06-1.06 4.47-4.47z'/></svg>") !default;

@mixin dialog-styles-css {

:where(dialog) {
  color: $dialog-text-color;
  block-size: fit-content;
  max-block-size: calc(100vh - 2rem);
  inline-size: calc((100% - 6px) - 2em);
  max-inline-size: var(--dialog-width, fit-content);
  margin: auto;
  margin-block-start: $dialog-margin-top;
  padding-block: $dialog-padding-block;
  padding-inline: $dialog-padding-inline;
  border: 1px solid $dialog-border-color;
  border-radius: $dialog-radius;
  background-color: $dialog-background-color; 
  overflow: auto;
  overscroll-behavior: contain; 
}

dialog:focus-visible {
  outline: none;
  box-shadow: $dialog-focus-shadow $dialog-focus-color;
}

dialog[open]::backdrop {
  background-color: $dialog-backdrop;
}

dialog div[class="close-dialog"] {
  position: fixed;
  inset: 0;
  display: block;
  content: "";
}

dialog :last-of-type:is(p, ol, ul, dl, figure, address) {
  margin-block-end: 0;
}

dialog :last-of-type:is(p, ol, ul, dl, figure, address):has(+ [method="dialog"], + button:not(dialog .close-hidden, .close-dialog-ico)) {
  margin-block-end: 1rem;
}

dialog:not(.dialog-img, .dialog-figure, :has(.dialog-header)):has(.close-dialog-ico) :first-child {
  margin-inline-end: 3rem;
}

// Offcanvas
.dialog-offcanvas-start, .dialog-offcanvas-end {
  --dialog-width: 24rem;
  --dialog-radius: 0;
  margin: 0;
  block-size: 100dvh;
  max-block-size: 100%;
  border: none;
}

.dialog-offcanvas-start {
  margin-inline-end: auto;
  border-inline-end: 1px solid $dialog-border-color;
}

.dialog-offcanvas-end {
  margin-inline-start: auto;
  border-inline-start: 1px solid $dialog-border-color;
}

// Dialog header, image and figure padding reset
dialog:has(.dialog-header), .dialog-img, .dialog-figure {
  padding: 0;
}

// Dialog header
.dialog-header {
  position: sticky;
  align-self: start;
  inset-block-start: 0;
  display: flex;
  align-items: center;
  align-content: start;
  gap: 1rem;
  padding-block: calc($dialog-padding-block * 0.66);
  padding-inline: $dialog-padding-inline;
  border-block-end: 1px solid $dialog-border-color;
  border-start-start-radius: $dialog-inner-radius;
  border-start-end-radius: $dialog-inner-radius;
  background-color: $dialog-title-background-color;
}

.dialog-header:has(h1, h2, h3, h4, h5, h6) {
  padding-block: calc($dialog-padding-block * 0.85);
}

.dialog-header .close-dialog-ico {
  align-self: start;
  margin-inline-start: auto;
}

.dialog-header :first-child {
  margin-block-end: 0;
}

.dialog-body {
  padding-block: $dialog-padding-block;
  padding-inline: $dialog-padding-inline;
}

// Dialog images and figures
.dialog-img img {
  border-radius: $dialog-inner-radius;
}

.dialog-figure img {
  border-start-start-radius: $dialog-inner-radius;
  border-start-end-radius: $dialog-inner-radius;
}

:is(.dialog-img, .dialog-figure):has(.dialog-header) img {
  border-start-start-radius: 0;
  border-start-end-radius: 0;
}

.dialog-figure figcaption {
  padding-block: 0.5rem;
  padding-inline: 0.75rem;
}

// Dialog image button
.dialog-btn-img {
  padding: 0;
}

.dialog-btn-img img {
  border-radius: calc($dialog-radius - 1px);
}

.dialog-btn-img:hover {
  cursor: zoom-in;
}

// Dialog icon close buttons
.close-dialog-ico {
  padding-block: 0.25rem;
  padding-inline: 0.55rem;
}

.close-dialog-ico:not(.dialog-header .close-dialog-ico) {
  position: absolute;
  inset-block-start: calc($dialog-padding-block * 0.66);
  inset-inline-end: calc($dialog-padding-block * 0.75);
}

.close-dialog-ico:is(.dialog-offcanvas-start .close-dialog-ico, .dialog-offcanvas-end .close-dialog-ico) {
  inset-block-start: $dialog-padding-block;
  inset-inline-end: $dialog-padding-block;
}

.close-dialog-ico:before, .close-dialog-ico-b:before, .close-dialog-ico-a:after {
  --ico: var(--dialog-text, CanvasText);
  display: inline-block;
  content: "";
  block-size: 1em;
  inline-size: 1em;  
  vertical-align: -.115em;
  background-color: var(--ico);
  mask-image: #{$dialog-close-icon};
  mask-repeat: no-repeat;
}

.close-dialog-ico-b:before {
  vertical-align: -.15em;
  margin-inline-end: 0.33rem;
}

.close-dialog-ico-a:after {
  vertical-align: -.175em;
  margin-inline-start: 0.33rem;
}

dialog .close-hidden, .close-dialog-ico span, .dialog-btn-img span {
  position: absolute;
  block-size: 1px;
  inline-size: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

// Color-scheme
.dialog-light {
  color-scheme: light;
}

.dialog-dark {
  color-scheme: dark;
}

}

@mixin dialog-transition-css {
  @media screen and (prefers-reduced-motion: no-preference) {
    dialog[open] {
      transition: $dialog-transition;
      opacity: 1; 
    }
  }
  
  @starting-style {
    dialog[open] {
      opacity: 0;    
    }
  }
}

@mixin dialogs-css {
  @include dialog-styles-css;
  @include dialog-transition-css;
}

@mixin dialogs-no-transition-css {
  @include dialog-styles-css;
}
dialog.js
let toggler = document.querySelectorAll("[data-dialog]"),
    closers = document.querySelectorAll(".close-dialog");
toggler &&
  (toggler.forEach(function (e) {
    let l = e.getAttribute("data-dialog"),
        dt = document.querySelectorAll(l);
    e.addEventListener("click", (e) => {
      t.forEach(function (e) {
      e.showModal();
    });
  });
}),
closers.forEach(function (e) {
  e.addEventListener("click", (l) => {
    e.closest("dialog").close();
  });
}));