Saturday, August 8, 2026
HomeProgrammingUtilizing and Styling the Dialog Factor

Utilizing and Styling the Dialog Factor


Wasn’t it nice — nay, wonderful – the day we bought a local HTML <dialog> factor? It’s, what now, almost ten years previous? Even so, I all the time discover myself trying up the way it works, when to make use of it, concepts for styling it, and whatnot. There’s lots of nuance to this seemingly little piece of net structure and it’s excessive time I give it a correct look… for future Geoff, however perhaps future you as nicely.

Marking up a <dialog>

That is the essential markup:

<button id="dialog-button">Open Dialog</button>

<dialog id="dialog">...</dialog>

It received’t open by default. We might manually set the open attribute:

<dialog id="dialog-button" open>...</dialog>

However when do you ever need to open a dialing by default? I’m certain it’s a uncommon use case. As an alternative, we now have a JavaScript present() technique for that. We are able to arrange variables for the dialog and button, then invoke the strategy:

const dialogButton = doc.querySelector('#dialog-button');
const dialog = doc.querySelect('#dialog');

formButton.addEventListener('click on', () => {
  dialog.present();
})

That works, however purchaser beware — that treats the dialog as extra of a pop-up than a modal, and modal is what I feel you’ll need typically. The important thing variations? A modal features a backdrop, is mechanically positioned within the middle of the web page, and permits the Esc key to shut it.

Discover how present() lacks the backdrop, positioning, and shutting stuff:

So, perhaps invoke the showModal() technique as an alternative:

const dialogButton = doc.querySelector('#dialog-button');
const formDialog = doc.querySelector('#dialog');

formButton.addEventListener('click on', () => {
  formDialog.showModal();
})

Nice! It opens, positions… and closes:

Extra about closing

Now we are able to hit the Esc key when the dialog is in focus (and see the way it’s in focus by default) to shut the dialog. But when we would like some UI that closes the dialog, like a button, we are able to add that contained in the factor:

<button id="dialog-button">Open Dialog</button>

<dialog id="dialog">
  <button id="dialog-close">Shut</button>
  <!-- and many others. -->
</dialog>

That doesn’t work proper out of the field however I guess you’ve already guessed how. There’s a shut() technique for it:

const formButton = doc.querySelector('#dialog-button');
const formDialog = doc.querySelector('#dialog');
const formClose = doc.querySelector('#dialog-close');

formButton.addEventListener('click on', () => {
  formDialog.showModal();
})

formClose.addEventListener('click on', () => {
  formDialog.shut();
})

I feel it’s slightly humorous/bizarre that there isn’t a corresponding closeModal(). No worries, although, as a result of shut() simply works:

And in order for you a JavasScript-less method, we are able to truly do it declaratively straight within the HTML:

<dialog id="dialog">
  <type technique="dialog">
    <button kind="submit">Shut dialog</button>
  </type>
</dialog>

I can’t vouch for the way that impacts semantics, however it’s certainly doable to shut that sucker with out JavaScript.

Invoker Instructions

So long as we’re speaking about declarative closing, we might as nicely point out an evolving characteristic referred to as invoker instructions which might be designed particularly to open and shut dialogs declaratively. It’s all completely experimental as I write this, however verify this out:

<button command="show-modal" commandfor="my-dialog">Present Dialog</button>

<dialog id="my-dialog">...</dialog>

That’s proper! We will hook up the button to the dialog straight in HTML with the command and and commandfor attributes to invoke a particular dialog (#my-dialog) to show-modal.

Cool, and that’s true for a closing button as nicely:

<dialog id="my-dialog">
  <!-- Shut #my-dialog -->
  <button command="shut" commandfor="my-dialog">Shut Dialog</button>
</dialog>

And, as Danny explains right here, we are able to hook all that up with JavasScript if we have to hearken to these instructions and fireplace off some occasion once they occur:

// Choose all dialogs
const dialogs = doc.querySelectorAll("dialog");

// Loop all dialogs
dialogs.forEach(dialog => {

  // Pay attention for shut (as regular)
  dialog.addEventListener("shut", () => {
    // Dialog was closed
  });

  // Pay attention for command
  dialog.addEventListener("command", occasion => {

    // If command is show-modal
    if (occasion.command == "show-modal") {
      // Dialog was proven (modally)
    }

    // One other solution to hear for shut
    else if (occasion.command == "shut") {
      // Dialog was closed
    }

  });
});

Carry on eye on that assist!

About button labeling

You is perhaps tempted to make use of an “X” (or no less than an SVG icon for it) for the shut button’s label:

<button id="dialog-button">Open Dialog</button>

<dialog id="dialog">
  <button id="dialog-close">X</button>
</dialog>

…however that’s not precisely the very best factor for screenreaders to announce. We nonetheless need it to say “Shut Dialog” or one thing to that impact. So, when you’re eager on utilizing an “X” icon, I’d add a <span> containing the textual content we would like learn and visually cover it whereas stopping the icon from being introduced utilizing the aria-hidden attribute:

<button id="form-button">Open Dialog</button>

<dialog id="form-dialog">
  <button id="form-close">
    <span class="visually-hidden">Shut modal</span> 
    <span aria-hidden="true"></span>
  </button>
</dialog>

Yet another accessibility-minded word. See how the shut button will get focus when the dialog opens up?

It’s possible you’ll or might not need that as a result of now the button might unexpectedly shut the dialog if the Area secret is by accident hit. Not the top of the world as a result of closing a modal isn’t precisely a harmful factor and will be opened again up. However if in case you have different focusable components within the dialog — maybe a hyperlink or a type subject — then perhaps think about giving a type of preliminary focus with the tabindex attribute.

Innate inertness

I need to get into styling the backdrop, however earlier than that, I feel it’s value noting that the web page behind an open dialog is inert. In different phrases, any type of interplay — textual content choice, button clicking, focus, inputs, and many others. — are unavailable. That’s most likely what you need anyway, so it’s good that isn’t one thing that needs to be configured by default. You simply received’t truly see the inert attribute within the markup when it occurs.

However that’s solely when the dialog is setup as a modal. Keep in mind the very first demo? We used the present() technique to open the dialog on click on somewhat than the extra express showModal(). Meaning the primary demo is exhibiting off one thing extra like a popover (suppose tooltips) than a real attention-hoarding modal that traps focus and sits on the very prime layer.

You would possibly surprise about competing dialogs, like say a popover and modal which might be open on the similar time. Properly, how did you open them on the similar time within the first place? You’d must open the dialog popover first since that doesn’t set off inert habits. Solely the modal dialog does. And when the modal dialog is open, the popover dialog is just not within the prime layer, making it inaccessible.

Anyway, let’s get to styling!

Styling the backdrop

Let’s begin right here as a result of I feel it’s extremely onerous to even see the backdrop the way in which it’s styled by default. Should you open dialog within the final instance, discover that the web page background is barely tinted. Not a lot, although. That’s the backdrop.

Dialog open and close states side by side. The left side has a khaki colored background and the right side is a slightly darker color.

Very delicate. We are able to type that ourselves with the ::backdrop pseudo-element. For instance, we might go full-on strong shade:

Buuuuut now we’re obscuring the complete web page behind it. That is perhaps OK, however I additionally suppose slightly transparency, maybe with slightly blur() motion can’t harm as a result of, you realize, context:

I truly actually like Mojtaba’s background picture instance within the CSS-Tips Almanac, even when it contradicts my emotions about obscuring the remainder of the web page:

Styling the border and background

Two very apparent defaults are setting the <dialog>’s styling: a vanilla white background and massive ol’ black border. Completely fantastic to go away that as-is when you’d like. Or, not.

DevTools styles panel shoeing the user agent styles for a dialog, dialog backdrop, open dialog, and modal dialog.

You would possibly suppose your customized kinds would go proper on the <dialog> factor:

/* 👎 */
dialog {
  background-color: gold;
  border: 0;
  border-radius: 12px;
}

However you truly need to choose it in its open state:

dialog {
  /* ... */

  &[open] {
    background-color: gold;
    border: 0;
    border-radius: 12px;
  }
}

You’ll have seen within the DevTools screenshot up there that the :modal pseudo-class has even greater specificity than :open. You may completely use that as nicely must you want overrides to the overrides.

Be careful for that :open pseudo-class, although. Safari 26.5 simply gained assist for it the day I’m scripting this. Should you want deeper assist, think about deciding on the [open] attribute as an alternative… or simply utilizing :modal.

Styling the place

A much less apparent default dialog type is the way it’s positioned within the middle of the viewport. Open DevTools and also you’ll see the UA styling that does that:

We might override margin-top to make the dialog slightly extra comfortable with the highest of the viewport:

One factor you most likely don’t need to do is override your dialog’s show. It’s set to show: none in its preliminary closed state. Set that to one thing like block on the factor itself and also you completely lose the entire level of getting a modal — the entire closed by default factor. You continue to get primary opening and shutting, solely with out the useful Esc key affordance.

And see how the customized kinds are solely utilized on the :open state since that’s the place they stay:

Likelihood is that you just don’t need the content material behind the ::backdrop to scroll. It’s a type of conditions the place a person will be taken out of context and positioned someplace completely totally different on the web page than the place they have been when opening the dialog.

It’d be very nice if the underlying content material was caught in place by default, but it surely’s completely comprehensible why it doesn’t: a dialog is just not a scroll container. If it was, we might slap overscroll-behavior: comprise on it and be performed with it.

Properly, seems that Chrome 144 tweaked that up a bit in order that overscroll-behavior works on non-scrollable scroll containers. So, assuming we’re in Chrome 144+, we are able to set that habits on the dialog and its backdrop:

dialog {
  overscroll-behavior: comprise;

  &::backdrop {
    overscroll-behavior: comprise;
  }
}

The final lacking piece is that we have to make the dialog itself a scroll container:

dialog {
  overflow: hidden;
  overscroll-behavior: comprise;

  &::backdrop {
    overscroll-behavior: comprise;
  }
}

Chrome 144 or above wanted:

That’s cool and all, however one other (and extra concise) solution to do it with broad browser assist is to verify if the physique factor :has() a dialog with an open attribute. And if it does, we cover the physique overflow:

physique:has(dialog[open]) {
  overflow: hidden
}

That mentioned, I do just like the overscroll-behavior method as a result of it’s extra declarative and connected to the factor we’re deciding on.

Getting inventive with dialog styling

Andy Clarke has an entire write-up on inventive methods to type dialogs past the essential content-in-box. It’s nicely out of scope of what we’re masking right here, however nicely definitely worth the learn. Right here’s one instance to whet your urge for food:

Animating dialogs

Dialogs kinda “snap” out and in once they’re opened and closed. However we are able to sprinkle in slightly animation for once they enter and exit view.

Like, what if we made the dialog slowly fade in as an alternative. You would possibly suppose this is able to work:

/* Nope! 👎 */
dialog {
  opacity: 0;
  overflow: hidden;
  overscroll-behavior: comprise;
  transition: opacity .5s ease-in-out;
  width: 80vw;

  &:open {
    opacity: 1;
  }
}

However, no. We’ve got to explicitly set a beginning type for components simply as they’re rendered within the DOM. On this case, a dialog is show: none by default and has no opacity set on it when it’s activated. That’s the place the @starting-style at-rule comes into play:

/* Yep! 👍 */
@starting-style {
  dialog:open {
    opacity: 0;
  }
}

dialog {
  overflow: hidden;
  overscroll-behavior: comprise;
  transition: opacity .5s ease-in-out;
  width: 80vw;

  &:open {
    opacity: 1;
  }
}

There we go:

Getting into and exiting view? That really seems like prime View Transitions API territory! However, alas, dialogs usually are not a fantastic use case for them. Why? Modal dialogs stay within the prime layer, and shutting them can take away them in a manner that doesn’t all the time produce a dependable previous/new pair for the named transition.

Right here’s an instance of that the place we now have a view-transition-name set on the dialog factor after which the ::view-transition-new() and ::view-transition-old() states sure to that identify, every calling an animation that slides in and slides out, respectively. Works nicely for the beginning transition, however not a lot for the exiting transition. Discover, too, that the backdrop wants further work because it’s included within the combine:

What you are able to do as an alternative is a few type of hybrid method by setting the view transition on the open state and utilizing a CSS animation on the shut state.

Or perhaps simply use CSS animations/transitions for each states! I’m undecided there’s any actual added worth in utilizing a view transition on one state for the sake of utilizing a view transition.

Anyway, when you’re trying to get extra inventive with in-n-out animations, Chris Coyier has a fairly cool one the place the modal follows a form() path. His demonstrates a dialog configured as a popover, so I forked it and used a modal as an alternative:

Dialog or Popover?

Which one must you use? It’s a very good query as a result of the Dialog API and Popover API are tremendous comparable however designed for various use instances. Zell Liew has a concise reply:

After a bit numerous analysis, I found that the Popover API and Dialog API are wildly totally different when it comes to accessibility. So, when you’re attempting to resolve whether or not to make use of Popover API or Dialog’s API, I like to recommend you:

  • Use Popover API for many popovers.
  • Use Dialog’s API just for modal dialogs.

The “when it comes to accessibility” is what actually issues right here as a result of, popovers lack:

  • computerized focus administration, and
  • computerized ARIA connection.

In the meantime, a dialog:

  • mechanically inerts different components,
  • prevents customers from tabbing into different components, and
  • prevents display readers from reaching different components.

So, when you’re planning to make use of a popover and want accessible affordances for trapping focus and making different components inert, you’ll must deal with these by yourself in JavaScript.

Zell additionally notes that popovers want an express accessible function. And there are a number of to select from so it’s gonna take some thought to decide on the appropriate one.

This isn’t all to say, hey, all the time use a dialog. It’s extra about selecting the best API for the appropriate use case. Quoting Zell once more:

  • Popover is an umbrella time period for any type of on-demand popup.
  • Dialog is one kind of popover — a form that creates a brand new window (or card) to comprise some content material.

Wrapping up

That’s all for now. I’ll replace this if y’all have extra so as to add or higher or extra correct methods to articulate what’s right here. Future us-es (there’s no plural for us, proper?) will thank us later.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments