Accessible Alerts made easy by the Popover API

Alerts are a common pattern on the web. They show an important message in a way that attracts the user's attention without interrupting the current workflow. For example: “Item was added to your cart”, or “Login has failed: Check username/password”.

Creating accessible and nicely animated alerts will become a lot easier in the near future, thanks to the new Popover API and new capabilities of CSS transitions. I've prepared a demo that you can try out.

Man speaking into a megaphone Photo: © Thirdman /

If you're not familiar with the Popover API, go read my article “Make your content pop with the Popover API and CSS Anchor Positioning” first. I won't repeat the basics of the API and instead focus on the relevant parts of this specific use case.

Accessible Alerts Demo

Here's my demo of accessible alerts using the Popover API. You can adapt the message as well as the duration and position of the alert. Give it a try:

The alert appears above all other elements of the page and is automatically announced by screen readers. I've tested this behavior with JAWS, NVDA, TalkBack and VoiceOver. The alert doesn't interrupt the user's current task and disappears after some time.

Implementation Details

Now let's examine the different building blocks that I used to create the accessible, animated alerts.

Generating dynamic HTML

I've implemented an Angular service that dynamically generates the popover element using JavaScript methods like createElement() and appendChild(). Check out the demo code for more details. You don't need to be familiar with the Angular framework to understand the important parts.

The following HTML code is appended to a container at the end of the body element:

<div popover="manual" role="alert" class="alert top-right"> This is a very important message </div>

The popover attribute turns the div element into a popover that is initially hidden by the browser. We show the popover alert by calling the element's showPopover() method.

Setting popover="manual" on the element defines a manual popover state. This prevents light-dismiss, meaning, interacting with other elements on the page won't close the popover. We close the popover programmatically using the hidePopover() method.

Semantic Markup

Set role="alert" on the popover element to define it as an alert. The document Accessible Rich Internet Applications (WAI-ARIA) 1.2 describes the alert role as follows:

A type of live region with important, and usually time-sensitive, information. [...] Alerts are assertive live regions, which means they cause immediate notification for assistive technology users.

In general, we use ARIA live regions to communicate dynamic content changes. Setting role="alert" is actually equivalent to setting aria-live="assertive" and aria-atomic="true". This means, as soon as our popover alert is displayed, screen readers immediately announce the alert's message.


Now let's put the icing on the cake. Let's add some animation to the popover element. There's only one problem: Popovers are initially hidden via display: none. What a nightmare! This creates two obstacles:

  1. An element with the display property value of none is not rendered. Therefore, it has no computed values for the different CSS properties like, e.g., opacity. If there is no computed value, you have no starting point for the transition.
  2. The same problem exists the other way around: When the display property is set to none, it immediately takes the element out of the flow of the rendered HTML document without triggering the transition effect.

Lucky for us, the CSS Transitions Level 2 specification adds new capabilities to CSS transitions. It defines the new @starting-style rule:

The @starting-style rule is a grouping rule. The style rules inside it are used to establish styles to transition from, if the previous style change event did not establish a before-change style for the element whose styles are being computed.

This allows us to define a starting state for our transition when the display type changes from none to another type.

The second piece of the puzzle is the new transition-behavior property and its allow-discrete value:

The transition-behavior property specifies whether transitions will be started or not for discrete properties.

Discrete properties have values that can't be interpolated. This is the case, e.g., for the display and overlay properties. To allow transitions for them, we need to set the allow-discrete value. You can also include the transition-behavior value as part of a shorthand transition declaration.

How does it all fit together? My demo includes the "top-right" position, where the alert fades in and slides in from the right. Here's the CSS code for the transition:

.alert[popover].top-right { /* Final state of the exit animation */ opacity: 0; transform: translateX(100%); transition: opacity 0.5s, transform 0.5s, overlay 0.5s allow-discrete, display 0.5s allow-discrete; } .alert[popover].top-right:popover-open { /* Final state of the show animation */ opacity: 1; transform: translateX(0); } /* Needs to be after the previous [popover]:popover-open rule to take effect, as the specificity is the same */ @starting-style { .alert[popover].top-right:popover-open { opacity: 0; transform: translateX(100%); } }

In the example above, the opacity and transform properties are transitioned for a duration of half a second. We want the browser to show the transitioned content and keep the popover in the top layer for the entire animation duration. To achieve this, we also include overlay 0.5s allow-discrete and display 0.5s allow-discrete in the transition.

Warning: These features are experimental and might change in the future!

Browser Support

The popover attribute is already supported in all major browsers. At the time of writing this article, Firefox had only enabled the feature in its experimental Nightly builds. But I expect Firefox to release the feature soon.

The new transition capabilities are only supported by Chrome and Edge at the moment. I'm hopeful that they'll be part of Interop 2024 and reach cross-browser compatibility until the end of the year.

Useful Resources

Posted on