Accessibility pattern · Overlays

Popover

The popover attribute puts a card in the top layer with light dismiss and Escape built in, and popovertarget wires a button to it with no script. CSS anchor positioning keeps it beside the button; the script keeps aria-expanded in step and moves focus in only when the card has something to use.

WCAG criteria
6
Keyboard rules
4
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Checkout redesign, round 2

Design review · Due Friday

Reviewer

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
Enter or SpaceOn the name button, opens the card and moves focus to its first button; pressed again, closes it.
Tab or ShiftTabMoves through the card's buttons, which come straight after the name button; tabbing on past the card closes it.
EscapeCloses the card and returns focus to the name button.
Enter or SpaceOn Follow, turns following on or off.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Focus reaches the name buttonAsha Rao, button, collapsed, has pop-up dialog
The card opensAsha Rao, dialog. Message, button
Follow is pressedFollow, toggle button, pressed
Escape closes the cardAsha Rao, button, collapsed
Message is pressedOpened a message to Asha Rao

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-popover" data-ap-popover>
  <article class="ap-popover__doc">
    <div class="ap-popover__doc-top">
      <span class="ap-popover__doc-icon" aria-hidden="true">
        <svg viewBox="0 0 24 24" focusable="false"><path d="M6 3h8l4 4v14H6Z"/><path d="M14 3v4h4M9 12h6M9 16h4"/></svg>
      </span>
      <div>
        <h3 class="ap-popover__doc-name">Checkout redesign, round 2</h3>
        <p class="ap-popover__doc-meta">Design review · Due Friday</p>
      </div>
    </div>
    <div class="ap-popover__by">
      <span class="ap-popover__by-k">Reviewer</span>
      <button type="button" class="ap-popover__trigger" popovertarget="popover-card" aria-haspopup="dialog" aria-expanded="false">
        <span class="ap-popover__avatar" aria-hidden="true">AR</span>
        <span>Asha Rao</span>
        <svg class="ap-popover__chev" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"/></svg>
      </button>
      <div class="ap-popover__card" id="popover-card" popover role="dialog" aria-labelledby="popover-name">
        <div class="ap-popover__head">
          <span class="ap-popover__avatar ap-popover__avatar--lg" aria-hidden="true">AR</span>
          <div>
            <p class="ap-popover__name" id="popover-name">Asha Rao</p>
            <p class="ap-popover__role">Product designer, Pune</p>
          </div>
        </div>
        <p class="ap-popover__presence">Available · 4:30 pm her time</p>
        <dl class="ap-popover__facts">
          <div><dt>Team</dt><dd>Checkout and payments</dd></div>
          <div><dt>Reviews this month</dt><dd>12</dd></div>
        </dl>
        <div class="ap-popover__actions">
          <button type="button" class="ap-btn ap-btn--primary" data-ap-message>
            <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 5h16v11H9.5L4 20Z"/></svg>
            Message
          </button>
          <button type="button" class="ap-btn ap-popover__follow" data-ap-follow aria-pressed="false">
            <svg class="ap-btn__icon ap-popover__plus" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 5v14M5 12h14"/></svg>
            <svg class="ap-btn__icon ap-popover__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
            Follow
          </button>
        </div>
      </div>
    </div>
  </article>
  <p class="ap-popover__status" role="status" data-ap-status></p>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.2 Meaningful Sequence Level A

    The card sits straight after its button in the source, so reading order and Tab order match what is on screen.

  • 1.4.10 Reflow Level AA

    The card is never wider than the screen, and anchor positioning flips it above or to the other side when there is no room.

  • 2.1.1 Keyboard Level A

    Opening, closing and every button in the card work from the keyboard; Escape comes with the popover attribute.

  • 2.4.3 Focus Order Level A

    Focus moves to the card's first button on open and back to the name button when the card closes with focus inside.

  • 4.1.2 Name, Role, Value Level A

    The button carries aria-expanded, the card is a dialog named after the person, and Follow reports aria-pressed.

  • 4.1.3 Status Messages Level AA

    Starting a message is confirmed in a status line that is announced without moving focus.

Usage

When to use it

Use it

  • Extra detail or a few actions tied to one thing on the page, like a person's card, quick settings or a share link.
  • Content people dip into and leave, without losing their place on the page.

Use something else

  • A short hint about a control: use a tooltip, or a toggletip if it must be opened on purpose.
  • A list of commands: use a menu button, which has the keys people expect from a menu.
  • A task that must be finished before going on: use a modal dialog.

Common failures

How it usually goes wrong

  • Opening on hover only

    Touch screens, keyboards and screen readers cannot hover. This card opens on click, Enter or Space.

  • A card stranded at the end of the page

    A popup appended to the end of the body is read last and reached last with Tab. Keep it right after its button; the top layer handles stacking.

  • Focus dropped on close

    When a card closes with focus inside it, focus falls back to the start of the page. Here it goes back to the name button.

  • Moving focus for nothing

    Pulling focus into a card that only holds text takes people away from where they were. Focus moves in only when there is a control to use.

  • No expanded state

    Without aria-expanded, a screen reader user cannot tell that the button opened anything or that it is still open.

  • Clipped by its container

    A card positioned inside a box with overflow: hidden gets cut off. The top layer lifts it out, and the fallbacks keep it on screen.

Notes

Building it

  • popover (auto) closes on Escape, on a click outside and when another auto popover opens. Use popover="manual" only when something else must close it.
  • Chromium already reports the expanded state of a popovertarget button; the script sets aria-expanded as well so every browser and screen reader gets it.
  • anchor-name on the button and position-anchor on the card place it; position-try-fallbacks flips it when it would leave the screen. Without anchor positioning, the script places it from getBoundingClientRect.
  • role=dialog fits because the card holds buttons and takes focus. A popover with only text needs no role and should leave focus on the button.

Sources: HTML: the popover attribute · CSS Anchor Positioning

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction