Selecting between Popover API and Dialog API is troublesome as a result of they appear to do the identical job, however they don’t!
After a bit plenty of analysis, I found that the Popover API and Dialog API are wildly totally different when it comes to accessibility. So, in the event you’re attempting to determine 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.
Popovers vs. Dialogs
The connection between Popovers and Dialogs are complicated to most builders, nevertheless it’s truly fairly easy.
Dialogs are merely subsets of popovers. And modal dialogs are subsets of dialogs. Learn this text if you wish to perceive the rationale behind this relationship.
![[popover-accessible-roles.jpg.webp]]
Because of this you can use the Popover API even on a factor.
Stylistically, the distinction between popovers and modals are even clearer:
- Modals ought to present a backdrop.
- Popovers mustn’t.
Subsequently, you need to by no means model a popover’s ::backdrop factor. Doing so will merely point out that the popover is a dialog — which creates a complete can of issues.
You need to solely model a modal’s ::backdrop factor.
Popover API and its accessibility
Constructing a popover with the Popover API is comparatively simple. You specify three issues:
- a
popovertargetattribute on the popover set off, - an
idon the popover, and - a
popoverattribute on the popover.
The popovertarget should match the id.
Discover that I’m utilizing the factor to create a dialog function. That is elective, however really useful. I do that as a result of dialog is a nice default function since most popovers are merely simply dialogs.
This two strains of code comes with a ton of accessibility options already built-in for you:
- Automated focus administration
- Focus goes to the popover when opening.
- Focus goes again to the set off when closing.
- Automated aria connection
- No want to put in writing
aria-expanded,aria-popupandaria-controls. Browsers deal with these natively. Woo!
- No want to put in writing
- Automated mild dismiss
- Popover closes when person clicks exterior.
- Popover closes after they press the Esc key.
Now, with out further styling, the popover appears kinda meh. Styling is a complete ‘nother situation, so we’ll sort out that in a future article. Geoff has a number of notes you possibly can overview within the meantime.
Dialog API and its accessibility
In contrast to the Popover API, the Dialog API doesn’t have many built-in options by default:
- No automated focus administration
- No automated ARIA connection
- No automated mild dismiss
So, we’ve to construct them ourselves with JavaScript. Because of this the Popover API is superior to the Dialog API in virtually each side — apart from one: when modals are concerned.
The Dialog API has a showModal methodology. When showModal is used, the Dialog API creates a modal. It:
- mechanically
inerts different components, - prevents customers from tabbing into different components, and
- prevents display screen readers from reaching different components.
It does this so successfully, we not must entice focus throughout the modal.
However we gotta care for the main target and ARIA stuff once we use the Dialog API, so let’s sort out the naked minimal code you want for a functioning dialog.
We’ll start by constructing the HTML scaffold:
Discover I didn’t add any aria-expanded within the HTML. I do that for a wide range of causes:
- This reduces the complexity of the HTML.
- We are able to write
aria-expanded,aria-controls, and the main target stuff instantly in JavaScript – since these received’t work with out JavaScript. - Doing so makes this HTML very reusable.
Establishing
I’m going to put in writing a few vanilla JavaScript implementation right here. In the event you’re utilizing a framework, like React or Svelte, you’ll have to make a few modifications — however I hope that it’s gonna be simple for you.
Very first thing to do is to loop by way of all dialog-invokers and set aria-expanded to false. This creates the preliminary state.
We may also set aria-controls to the factor. We’ll do that although aria-controls is poop, ’trigger there’s no higher approach to join these components (and there’s no hurt connecting them) so far as I do know.
const modalInvokers = Array.from(doc.querySelectorAll('.modal-invoker'))
modalInvokers.forEach(invoker => {
const dialogId = invoker.dataset.goal
const dialog = doc.querySelector(`#${dialogId}`)
invoker.setAttribute('aria-expanded', false)
invoker.setAttribute('aria-controls', dialogId)
})
Opening the modal
When the invoker/set off is clicked, we gotta:
- change the
aria-expandedfromfalsetotruetopresentthe modal to assistive tech customers, and - use the
showModaloperate to open the modal.
We don’t have to put in writing any code to cover the modal on this click on handler as a result of customers won’t ever get to click on on the invoker when the dialog is opened.
modalInvokers.forEach(invoker => {
// ...
// Opens the modal
invoker.addEventListener('click on', occasion => {
invoker.setAttribute('aria-expanded', true)
dialog.showModal()
})
})
Nice. The modal is open. Now we gotta write code to shut the modal.
Closing the modal
By default, showModal doesn’t have automated mild dismiss, so customers can’t shut the modal by clicking on the overlay, or by hitting the Esc key. This implies we’ve so as to add one other button that closes the modal. This have to be positioned throughout the modal content material.
When customers click on the shut button, we’ve to:
- set
aria-expandedon the opening invoker tofalse, - shut the modal with the
shutmethodology, and - convey focus again to the opening invoker factor.
modalInvokers.forEach(invoker => {
// ...
// Opens the modal
invoker.addEventListener('click on', occasion => {
invoker.setAttribute('aria-expanded', true)
dialog.showModal()
})
})
const modalClosers = Array.from(doc.querySelectorAll('.modal-closer'))
modalClosers.forEach(nearer => {
const dialog = nearer.closest('dialog')
const dialogId = dialog.id
const invoker = doc.querySelector(`[data-target="${dialogId}"]`)
nearer.addEventListener('click on', occasion => {
dialog.shut()
invoker.setAttribute('aria-expanded', false)
invoker.focus()
})
})
Phew, with this, we’re carried out with the fundamental implementation.
After all, there’s superior work like mild dismiss and styling… which we are able to sort out in a future article.
Can you utilize the Popover API to create modals?
Yeah, you possibly can.
However you’ll have to deal with these by yourself:
- Inerting different components
- Trapping focus
I believe what we did earlier (setting aria-expanded, aria-controls, and focus) are simpler in comparison with inerting components and trapping focus.
The Dialog API may grow to be a lot simpler to make use of sooner or later
A proposal about invoker instructions has been created in order that the Dialog API can embody popovertarget just like the Popover API.
That is on the way in which, so we’d be capable to make modals even less complicated with the Dialog API sooner or later. Within the meantime, we gotta do the mandatory work to patch accessibility stuff.
Deep dive into constructing workable popovers and modals
We’ve solely started to scratch the floor of constructing working popovers and modals with the code above — they’re barebone variations which might be accessible, however they undoubtedly don’t look good and may’t be used for skilled functions but.
To make the method of constructing popovers and modals simpler, we are going to dive deeper into the implementation particulars for a professional-grade popover and a professional-grade modal in future articles.
Within the meantime, I hope these offer you some concepts on when to decide on the Popover API and the Dialog API!
Keep in mind, there’s no want to make use of each. One will do.









