Written by Rahul Chhodde✏️
One of the most recent additions to web standards is the Popover API, which appears somewhat similar to the HTMLDialogElement
API. In certain instances, the distinction between these two can be rather unclear, making it confusing for developers to choose the right one for a given task.
This article aims to address the key similarities and differences between the Popover and HTMLDialogElement
APIs. We will briefly describe each API, cover some of their different use cases, implement the APIs to create some common utilities seen on the web, and discuss when to use each API.
Jump ahead:
- Understanding how dialogs work
- Diving into the
HTMLDialogElement
API - Understanding how popovers work
- Exploring the Popover API
- When to use dialogs vs. popovers
- Checking browser support for the Popover and
HTMLDialogElement
APIs
Understanding how dialogs work
A dialog UI pattern can serve as a means to display information to users and gather their input. "Dialog" here describes the communication process where the app requests user input using this particular element. The user may need to respond before proceeding with the app, or they can choose to dismiss the dialog.
To craft dialogs that are semantically rich and accessible for the web, the HTML standard supports the HTMLDialogElement
API. This API enables the creation of purposeful dialogs with improved accessibility.
The <dialog>
element provided by the API is specifically designed for components that require user interaction or input, and it can operate in both modal and non-modal manners.
Exploring modal dialogs
In a modal dialog, users need to handle the dialog first before they can continue using the app. This means they can't do anything else in the app until they close or cancel the dialog.
Modal dialogs are usually used for important tasks like confirming actions or showing errors, which need the user's complete focus.
A common example of modal dialogs is a newsletter subscription prompt. The modal behavior is particularly useful in this case for grabbing the user's full attention, as shown in this example from Dribbble:
Another excellent example, though less common, is when a website presents a dialog asking the user for permission to show ads. Here’s an example from the How-To Geek website:
In this scenario, the user's access to the app or website is restricted until they include it in their ad blocker program's whitelist.
Exploring non-modal dialogs
Non-modal dialogs allow users to keep using the app even when the dialog is open. This makes them less bothersome for users, but it also means they might not immediately notice the dialog.
They’re typically used for features like tooltips or menus that don't need all of the user's focus.
Interestingly, dialogs with non-modal behavior are frequently categorized as popovers. This is where the distinction between dialogs and popovers starts to blur, making it difficult for developers to choose between the two.
For a clearer perspective, you may think of the dialog vs. popover debate as modals vs. non-modals.
A frequent situation where non-modal dialogs come into play is with the cookie-consent banners often seen on websites and apps these days:
The recommended way to create and control dialogs in HTML is to use the <dialog>
element in conjunction with its HTMLDialogElement
API. This approach gives the element an inherent dialog role, which allows the browser to accurately interpret the element and provide appropriate accessibility features.
In the upcoming segment, we will go over the specifics of the dialog API and see how to use it to create comprehensive and functional dialogs.
Diving into the HTMLDialogElement
API
Constructing a functional HTML dialog requires a solid understanding of how to structure the markup correctly and utilize the HTMLDialogElement
API for various dialog-related tasks.
Furthermore, there are certain new CSS features available that can be employed to style, optimize, and enhance accessibility for HTML dialogs.
We will address the process of declaring a dialog through markup, adding functionality to it using JavaScript, and enhancing its appearance with CSS, one step at a time.
Structuring your markup for dialogs
When you add the <dialog>
element to your HTML, it will not appear immediately on the frontend as a proper dialog. To make it visible, you can add the open
attribute to it:
<dialog id="dialog" open>
<p>...</p>
</dialog>
The result, which you can see in the CodePen here, doesn’t quite look like a dialog at the moment. It currently operates as a non-modal dialog and also lacks a close button.
To enhance the functionality of this dialog, you can employ the HTMLDialogElement
API to programmatically manage its display, closure, and interaction using JavaScript.
Leveraging JavaScript with the HTMLDialogElement
API
We can create both modal and non-modal dialogs using the JavaScript capabilities of the HTMLDialogElement
API. The JavaScript support within this API offers various methods, events, and properties to effectively handle the behaviors and functionalities of the dialog element.
Here are some JavaScript methods supported by the API to manage different states of the dialog element:
-
show()
: Opens the dialog non-modally -
showModal()
: Opens the dialog modally -
close()
: Closes the dialog and optionally returns a value to the caller
It also supports specific events that you can listen to whenever the dialog closes:
- The
close
event fires when the dialog closes - The
cancel
event fires when the user cancels the dialog by pressing theEscape
key
Some instance properties are also available with the API, which are listed below:
-
open
: A boolean value that indicates whether the dialog is open (true) or closed (false) -
returnValue
: A property that can be used to set or get the return value for the dialog element
Styling your dialog with CSS pseudo-selectors
A modal dialog is supposed to perform critical tasks and therefore appears on the top layer of the stacking context, which means you can't manage its stacking using the CSS z-index
property. Instead, it will always appear on top of every element that is not in the top layer.
Note that the stacking of the elements in the top layer is managed in last-in, first-out (LIFO) order. This means that the last element that is added to the top layer will be the topmost element.
On the other hand, non-modal dialogs let you control stacking with CSS. The comparison is shown in the CodePen example here.
With the HTMLDialogElement
API, we have access to dialog-specific CSS pseudo-selectors, which can be used to apply styles or target specific dialog components.
When a dialog behaves modally, it should display a full-screen click trap in the background. This indicates that the rest of the application is currently inactive.
The ::backdrop
pseudo-element, which is also used in user-agent styles to provide a pre-existing default click trap for modal dialogs, can be used to customize the click trap appearance:
.my-modal::backdrop {
background: hsl(240deg 50% 50% / 25%);
}
We can also use the :modal
CSS class to ensure that the dialog we are targeting exhibits modal behavior and style it accordingly:
.my-dialog:modal {
...
}
Implementing the HTMLDialogElement
API
You can start implementing the HTMLDialogElement
API by getting a reference to a button element that triggers the dialog. When the user interacts with the button, you can execute the Dialog.show()
method. This will cause the dialog to show up when the button is clicked:
const openDialogBtn = document.querySelector("#open-dialog-btn");
const dialog = document.querySelector("#dialog");
openDialogBtn?.addEventListener("click", () => {
dialog?.show();
});
At this point, the dialog is non-modal, which means that the user can still interact with the rest of the application while the dialog is open.
If you want to make the dialog modal, you can use the Dialog.showModal()
method instead of Dialog.show()
. This will cause the dialog to operate in a modal behavior, which means that the user will not be able to interact with the rest of the application until the dialog is closed:
openDialogBtn?.addEventListener("click", () => {
dialog?.showModal();
});
Closing dialog elements can be readily achieved by utilizing Dialog.close()
, as shown below. This method constitutes an explicit dismissal:
closeDialogBtn?.addEventListener("click", () => {
dialog?.close();
});
Soft dismissal of dialogs is not natively supported by the HTMLDialogElement
API. The Popover API provides a superior way to create non-modal dialogs with soft dismissal capabilities.
Both triggering and dismissing, whether softly or explicitly, are much simpler using the Popover API than the dialog API. We will discuss this topic further in the next section.
Here is a CodePen demonstration of both modal and non-modal dialogs, implemented based on the explanations given earlier.
The modal dialog is neatly centered thanks to the applied user agent styles. The non-modal counterpart, on the other hand, does not quite achieve centered alignment and is visible around its anchor or trigger.
Feel free to fork this code, try to fix the non-modal positioning, or create your own variations of dialog elements.
As we previously discussed, dialog elements are suitable for presenting users with important action-related information. However, for less crucial scenarios, we now have the newly introduced Popover API. Let’s explore it in the next section.
Understanding how popovers work
A popover can be any transient window-like component that emerges when triggered within a webpage or app. Usually, this happens when a user interacts with an element that serves as the trigger for the popover. It is typically used to provide additional information or functionality to the user.
The Popover API provides the advantage of utilizing in-browser features to create accessible and native popover utilities. We will discuss more about the API later on.
Popovers exhibit non-modal behavior and are not suitable for elements displaying modal properties. They are frequently used for tasks such as:
- Displaying tooltips or help text
- Opening action or context menus featuring a range of choices
- Initiating non-modal dialogs for actions like toast notifications, hover cards, fly-outs, etc.
- Providing context-related information during application tours or onboarding processes
Tooltips that appear when hovering over buttons or icons, or are added when a new feature is introduced, are good examples of non-modal popovers. These unobtrusive popovers provide quick insights without disrupting the user's flow, like this Unsplash popover shown below: Another practical use case of popovers is providing context-specific information. For instance, when a user clicks on a term or phrase within an article, a popover might appear, offering a brief definition or explanation.
These popovers are designed to enhance the user's understanding without taking them away from the main content, like this example from the Grammarly website: Now, let’s delve further into the Popover API, exploring its capabilities and functionalities in more detail.
Exploring the Popover API
To handle non-modal behavior beyond dialogs in web browsers, the HTML standard has introduced popover attributes with the corresponding Popover API.
Unlike the HTMLDialogElement
API, the Popover API doesn't involve creating a new element tag in the markup. Instead, it utilizes different attributes that can be added to various elements. These attributes establish a relationship between the popover and its triggering element.
The HTMLDialogElement
API is specifically linked to dialogs. In contrast, the Popover API can be attached to any element to provide it with a non-modal behavior.
This new approach may eliminate the need for an external library in the future to achieve non-modal behavior. It also enhances the user experience by incorporating native semantics and providing relevant information in a non-modal manner.
By default, popovers created using this approach have a light-dismissing behavior that can also be controlled through API features.
As mentioned before, popovers exhibit non-modal behavior. Popovers generated using the Popover API strictly adhere to this non-modal behavior and are placed at the top-most layer in the stacking context. This implies that they will consistently appear above other elements, irrespective of their z-index
property.
Using the popover
attribute
The popover
attribute is a global attribute that designates an element as a popover element. It can take one of two values: auto
or manual
.
The default value of this attribute is auto
, which indicates that the popover will automatically display when the user interacts with the triggering element.
The manual
value indicates that the popover will only be shown when the user explicitly clicks on the triggering component.
Using the popovertarget
attribute
The popovertarget
attribute is used to specify which element will trigger the popover. The ID of the trigger element must be in the same tree as the element with the popover
attribute. The popover will be displayed relative to the triggering element, as you can see in the CodePen example here.
Using the popovertargetaction
attribute
The popovertargetaction
attribute is used to specify the action that will be performed when the popover is triggered. It can take one of three values:
-
show
— Interacting with the triggering element will display the popover -
hide
— Interacting with the triggering element will hide the popover -
toggle
— Interacting with the triggering element will display the popover if it is hidden and hide it if it is displayed
Below, you can interact with two triggering elements. The Show popover button uses the show
value, while the Hide popover button uses the hide
value. See the CodePen example here.
Exploring JavaScript support in the Popover API
In terms of JavaScript support, there are three instance properties that prove useful for acquiring the attribute values discussed earlier:
-
popover
— Facilitates retrieving and setting an element's popover state using JavaScript, which can be eitherauto
ormanual
. It serves not only for functional purposes but also for feature detection, reflecting the value of the popover global HTML attribute -
popoverTargetElement
— Enable getting and setting the popover element controlled by the corresponding control triggers. They correspond to the JavaScript counterparts of thepopovertarget
HTML attribute -
popoverTargetAction
— Employed to obtain and modify the action to be executed (hide
,show
, ortoggle
) on the popover element under the control of the respective button. They mirror the value of thepopovertargetaction
HTML attribute
In addition, the API supports three instance methods:
-
hidePopover()
— Conceals a popover element by removing it from the top layer and applying thedisplay: none
style -
showPopover()
— Reveals a popover element by adding it to the top layer -
togglePopover()
— Toggles a popover element between its visible and hidden states
Furthermore, two instance events are available for use with the API:
-
beforetoggle
— Triggered just prior to a popover element's state transition between showing and being hidden, or vice versa -
toggle
— Fired just after a popover element's state changes between showing and being hidden, or vice versa
The popover-specific CSS
Popover elements — specifically, those that use the popover
attribute — are intended to be displayed above other elements. Thus, like modal dialog elements, they are placed in the top layer of the stacking context.
Since we've previously addressed the finer points of stacking with top-layer elements, there's no need to reiterate them here.
With the Popover API, we have the :popover-open
CSS pseudo-class that enables you to target a popover element when it's in an open
state:
.tooltip:popover-open {
/* Do something */
}
As popover elements reside at the topmost level of the stacking context, they also support the ::backdrop
pseudo-element. This feature can be highly advantageous for emulating effects such as lightbox image galleries, introduction tooltips during app tours, walkthroughs, onboarding, and more:
.lightbox::backdrop {
/* Do something */
}
Implementing the Popover API
The Popover API imparts popover behavior through its attributes. However, unlike the HTMLDialogElement
API, it does not inherently include the associated semantics. Thus, it becomes your responsibility to incorporate semantics into your popovers by utilizing distinct role
attributes.
For example, when creating a custom <select>
list box, you would include the role
attribute with the value listbox
to indicate that the element functions as a list box. You should also apply appropriate ARIA states and properties, which will signal the browser to identify it as such:
<div
role="listbox"
aria-labelledby="..."
aria-activedescendant="..."
popover>
...
</div>
Alternatively, if an element possesses sufficient inherent semantics, you can simply add the popover
attribute. Let's take the <dialog>
element as an example to illustrate how easily we can trigger and dismiss it using the Popover API.
We can easily establish a functional dialog using the aforementioned popover attributes, as discussed earlier, to mimic a cookie-consent box:
<dialog id="cookie-box" popover="manual">
...
<button
id="accept-cookies"
popovertarget="cookie-box"
popovertargetaction="hide">Accept Cookies</button>
<button
id="close-cookie-box"
popovertarget="cookie-box"
popovertargetaction="hide">Close</button>
</dialog>
<button
id="open-cookie-box"
popovertarget="cookie-box"
popovertargetaction="show">Open Cookie box</button>
After tweaking the position and appearance of the dialog element with CSS, we are good to add more functionality to our cookie-consent box. Check how flawlessly the trigger and dismissal of the dialog popover work in the demo below. See the CodePen here.
Typically, websites display the cookie-consent box in an initially opened state. You can achieve this in JavaScript by obtaining a reference to the dialog box and subsequently utilizing the Popover.showPopover()
method:
const cookieBox = document.querySelector("#cookie-box");
cookieBox.showPopover();
You may now take it a step further by defining actions and setting up event listeners to incorporate additional functionalities into the cookie-consent box. Here's how the final demonstration looks.
As an assignment, consider expanding on this example to create a comprehensive cookie-consent feature.
With your current understanding of dialogs and popovers, you may venture into developing something new too. Some ideas include a basic image lightbox, a simple tooltip, or any other idea that comes to mind when considering a popover-like interaction.
When to use dialogs vs. popovers
You now possess a sufficient technical understanding of dialogs and popovers. However, there are a few additional non-technical aspects you should consider when choosing between dialogs and popovers. Take the following factors into account:
- User context: If a user is already engaged in a complex task, a dialog may cause less disruption than a popover. However, if the user is starting a task, a popover may be a better way to provide additional information without interrupting their flow
- User experience: If the user is acquainted with the task or interaction, a popover might suffice. Conversely, if the user is unfamiliar with the task or interaction, a dialog could be a superior method for delivering lucid instructions and gathering essential input
- Platform: Certain platforms, such as mobile devices, are more sensitive to modal windows. In such cases, opting for a popover over a dialog might be a better idea
Keeping these in mind can help you determine whether a dialog or popover is best for your needs.
Checking browser support for the Popover and HTMLDialogElement
APIs
The Popover API is presently in an experimental phase across most browsers, except for Chrome 116+ and the Safari 17 technical preview. However, it is expected to be available in most major browsers soon. You can refer to CanIUse for more details about its support.
The HTMLDialogElement
API is now well-supported and available in nearly all modern web browsers. You can find additional information about support for <dialog>
elements on CanIUse.
Conclusion
The difference between dialogs and popovers can be subtle and challenging to discern. In this guide, we talked about both their differences and similarities. For example, non-modal dialogs can work like popovers, and the Popover API can be used flexibly with <dialog>
elements.
We also explored some UI patterns that are similar to these two concepts and completed some coding exercises. As the popover feature is still in its early stages, it may continue to evolve, potentially making the distinction between these two APIs clearer.
I hope you found the explanations helpful. Feel free to share any thoughts, suggestions, or ideas you might have.
Get set up with LogRocket's modern error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side.
npm:
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Script tag:
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
3.(Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
Top comments (1)
Worth mentioning that
popovertarget
isn't that well supported yet :-(