They are different HTML, to begin with. A dialog is like this:
<dialog id="my-dialog">
Content
</dialog>
Code language: HTML, XML (xml)
While a popover is an attribute on some other element:
<aside popover id="my-popover">
Content
</aside>
Code language: HTML, XML (xml)
The reason it’s worth comparing them is that they are quite similar in a lot of ways, both in look and functionality, which can be confusing. It’s worth thinking about which one you really need.
They are both hidden-by-default
If you put either bit of the HTML above onto the page, they will be visually hidden as well as ignored in the accessibility tree by default (but available in the DOM). It isn’t until you specifically show them (via JavaScript or on-page HTML control when available) that they are visible.
You can make a <dialog>
visible by default in HTML alone:
<dialog
id="my-dialog"
open
>
Content
</dialog>
Code language: HTML, XML (xml)
Where you cannot make a popover visible in HTML alone.
Popovers Have HTML-Only Controls
You can make a popover work (open & close) with HTML controls alone:
<!-- This button will open and close the matching popover. No JavaScript required. -->
<button popovertarget="my-popover">
Toggle Popover
</button>
<aside popover id="my-popover">
Content of popover
</aside>
Code language: HTML, XML (xml)
But you cannot build HTML-only controls for a <dialog>
. Opening and closing a dialog requires JavaScript event handlers.
JavaScript APIs
Dialog JavaScript APIs
The dialog APIs in JavaScript are interesting in that there are two different distinct APIs for opening it. This is where the term “modal” comes in. Modal is sometimes used as a term for the UI element itself, but here it essentially means if the modal should trap focus inside of it while open, or not.
.show()
— Open the dialog in a non-modal state, meaning no backdrop is shown and no focus trapping happens. Note that using theopen
attribute in the HTML/DOM to open the dialog is the same (non-modal)..showModal()
— Open the dialog in a modal meaning a backdrop is shown and focus is trapped within the modal..close()
— Closes the dialog (if it’s open).
The showModal()
method can throw if the dialog is already open in a non-modal state.
Uncaught InvalidStateError: Failed to execute 'showModal' on 'HTMLDialogElement': The dialog is already open as a non-modal dialog, and therefore cannot be opened as a modal dialog.
Popover JS APIs
Popovers also have JavaScript APIs, but both the opening and closing APIs are different than with modals and do not overlap. These are pretty self explanatory.
.showPopover()
— Opens the popover..hidePopover()
— Closes the popover.
Calling showPopover
on an already open popover or hidePopover
on an already hidden popover does not throw.
Focus Trapping
The ability of the dialog element to be opened in a modal state and thus trap focus inside of it is a superpower of this element. It is unique to the dialog element, popovers cannot do this (on their own).
Focus trapping, while it sounds kinda bad, is actually an accessibility feature. After all, that’s what a modal is: it forces you to deal with some interaction before anything else can be done. It’s actually also a WCAG requirement to not trap focus when you shouldn’t, but in the case of a modal, you should be trapping focus — as well as providing a standard way to close the dialog and escape the trap.
Focus can change to other focusable elements inside, and when you’re about to move focus forward to the next element when you’re at the last, it circles back to the first focusable element within the dialog. You get all this “for free” with a <dialog>
opened with showModal()
, which is otherwise a huge pain in the ass and you probably won’t even do it right (sorry).
If you need this focus trapping, don’t use a popover as it’s not for this job.
Moving Focus
When a dialog is opened (either modal or non-modal), focus is moved to the first focusable element within it. When it is closed, focus is moved back to the element that opened it.
With a popover, focus remains on the element that opened it even after the popup is opened. However, after the popup is open, the next tab will put focus into the popup’s content if there is any in there, regardless of where it is in the DOM, tab through the focusable elements of the popup, then onto other focusable elements outside the popup after the original element that opened it.
This is all tricky work that you get for free by using the <dialog>
element or popups and frankly a huge reason to use them 👍.
Escape Key
Both modal dialogs and popups, when open, can be closed by pressing the ESC key. Very handy behavior that helps adhere to accessibility adherence, again given for free, which is tricky and error-prone to write yourself.
Non-modal dialogs do not close with the ESC key, so you’ll need to provide your own close functionality, like:
<button onclick="myDialog.close()">Close</button>
Code language: HTML, XML (xml)
They Have the Same Default Styling
Dialogs and popovers look the same by default and have really basic default styling that you’ll almost certainly want to override.
They are essentially position: fixed;
and margin: auto;
which centers them in the viewport. This is a probably a smart default for dialogs. In my opinion, popovers are usually begging for anchor positioning to open the popover near where the element that opened it is, but they work nicely as slide-out drawers as well, particularly on mobile.
You’ll likely want to bring your own padding, border, background, typography, internal structure, etc.
The Top Layer
Another amazing feature of both dialogs and popovers is that, when open, they are placed on what is called the “top layer”. It is literally impossible for any other element to be on top of them. It doesn’t matter where they are in the DOM (could be quite nested) or what containing blocks or z-index
is involved, the top layer is the top no matter what. (Although – it is true that if you open subsequent dialogs/popovers, e.g. a button in a dialog opens another dialog, the second one will beat the first and be on top, as you’d expect.) This top-layer ability is yet another thing you get for free and a fantastic reason to use these native features.
Backdrops
Both (modal) dialogs and popovers use (and share) a backdrop. This is the layer above all content on the page that covers the page (by default), but is still underneath the actual dialog or popover. This backdrop is a very light transparent black by default, but can be styled like this:
::backdrop {
background: color-mix(in srgb, purple, transparent 20%);
}
Code language: CSS (css)
That will apply both to modal dialogs and default popovers. If you wanted to have different backdrops for them, you could scope them like this, as the backdrop is applied to the element that is open:
[popover]::backdrop {
}
dialog::backdrop {
}
.some-very-specific-element::backdrop {
}
Code language: CSS (css)
You don’t have to show a backdrop if you don’t want to, but it’s a good indicator for users particularly when modal behavior is in play (and perhaps an anti-pattern when it’s not, as you may be visually hiding elements in focus).
Non-modal dialogs do not have a backdrop.
Soft Dismiss
This feature is unique to popovers. You can “click outside” the popover to close it, by default (yet another tricky behavior to code yourself). I’ve used the term “default popover” in this article and what I mean is when you don’t provide a value to the popover
attribute. That implies auto
as a value which is what makes soft dismissal work.
<!-- Soft Dismissible -->
<div popover id="myPopover"></div>
<!-- Soft Dismissible -->
<div popover="auto" id="myPopover"></div>
<!-- NOT Soft Dismissible -->
<div popover="manual" id="myPopover"></div>
Code language: HTML, XML (xml)
Multiple Open
Both dialogs and popovers can have multiple open at once. The most recent one to be opened will be the one that is most “on top” and will close the first via soft dismiss or the ESC key. (Also see the CloseWatcher API).
For a modal dialog, note that because the rest of the page is essentially inert
when it is open, the near-only way to open another is via interactivity within the first opened dialog.
For popups, because the default behavior has soft dismissal, the popovers will need to be popover="manual"
or be opened with JavaScript without interaction for multiple of them to be open.
Purpose and Semantics
Popovers likely have more use cases than dialogs. Any time you need a tooltip or to provide more contextual information that has good reason not to be visible by default, a popover is a good choice.
Non modal dialogs are pretty similar to a popup, but are perhaps better suited to situations where there is no other element on the page that is relevant to the messaging. Perhaps something like a “No internet connection detected” message, which could be very important to tell a user, but doesn’t need to 100% stop other activity on the page.
Modal dialogs are show-stoppers, forcing a user to deal with them before anything else can happen. They should be used sparingly (they are reached for far too much, some people say). Perhaps a message like “Are you sure you want to delete this entire document? This cannot be undone.” would be a modal dialog, as any other interaction on the page is moot should the user be deleting.
Animation
This is all very cutting edge right now, so browser support is spotty, but both of these elements can be animated both on the way in and out.
I played around with this Pen while I was thinking and working on all this, which may be helpful to you if you’re doing the same.