Popover pattern and CSS

As you may have observed in part 1, default popover styles are pretty bare bones. Browsers display them as a white box with a black order, positioned in the center of the viewport.
In this tutorial — part two of the series — we'll look at how to style popovers. You'll learn:
- where popovers sit in the document stacking context;
- about the
::backdrop
pseudo-element class and:popover-open
pseudo-class; - how to transition popovers with
transition-behavior: allow-discrete
and@start-style
; and - how to position popovers with anchor positioning.
Not all of these features have landed in every user agent. Anchor positioning, in particular, requires a polyfill or fallback as of publication.
Popovers and stacking context
A popover, when open, becomes the top layer in the browser. Popovers also create a new stacking context. Children of the popover are stacked relative to their immediate ancestor instead of to the root element.
The top layer is a newer browser concept introduced with the Fullscreen API and dialog
element. As as MDN explains, it is an internal browser concept and cannot be directly manipulated from code.
Here's what this means for your layouts.
- When open, the popover element is always at the top of the stack.
- An open popover's
::backdrop
pseudo-element is the next layer down. - Objects can't be positioned between the popover and its backdrop because they form a single stacking context.
- Elements within the popover that have a
position
value ofabsolute
are contained by the popover.
It also means that you can't use the z-index
property to adjust the popover's position in the document's stacking order. The popover is always the top-most layer.
Styling popovers with the :popover-open
pseudo-class
You can use the attribute selector ([popover]
) to select popover elements. But in most cases, you'll want to add styles to opened popovers. That's easy with the :popover-open
pseudo-class.
[popover]:popover-open {
background: pink;
border-color: transparent;
border-radius: 1rem;
}
Here's that CSS in action.
Adding the :popover-open
pseudo-class increases the selector's specificity. It ensures that popovers are shown only when the user or a script requests it. Using [popover]
alone would override the default browser styles. If you added display: grid
or display: flex
to that rule set, all of the popovers in a document would be visible, regardless of user interaction.
Adding a ::backdrop
The popover pattern also has a ::backdrop
pseudo-element. It makes your popover look more like a modal.
Unlike a true modal, however, other elements in the document do not become inert.
Notice in the preceding demo that when the popover is showing, you can still click on the Popover trigger button to close it. Opening a dialog modal, on the other hand, prevents you from interacting with other elements in the document.
Positioning popovers: in the near future
Because popovers occupy the top layer, positioning works a bit differently than with other elements. Popovers are initially positioned relative to the root element.
With the introduction of anchor positioning, however, we can anchor a popover to the position of another element.
Before we go any further, let me add a caveat: anchor positioning is experimental. Safari 18.3.1 and older do not support it. Neither does any version of Firefox as of this writing (version 138 or prior). Use a polyfill or add fallback styles using @supports not (anchor-name: --projects){}
.
Anchor positioning is a blog post unto itself, but let's look at a quick example of how to use it to position an element relative to its trigger. First the markup.
<button popovertarget="moreinfo">Popover trigger</button>
<p popover id="moreinfo">
This is the popover anchor panel. Its position is anchored to that of its trigger.
</p>
That's pretty straightforward. We have a popover trigger and the popover element. Now the CSS.
[popovertarget=moreinfo] {
anchor-name: --anc;
}
[id=moreinfo] {
position-anchor: --anc;
left: anchor(start);
top: anchor(end);
margin: 0;
}
The anchor-name
property for the [popovertarget=moreinfo]
rule set declares a name for the anchor.
In the [id=moreinfo]
rule set, the position-anchor
property sets [popovertarget=moreinfo]
as the anchoring element and --anc
as its name. We can then use the anchor()
function to anchor the left
edge of the popover to the start of the anchoring element, and the top
edge to the end of the anchoring element. Setting margin
to 0
overrides the browser's default popover rule, margin: auto
;
Popovers and transitions
Remember that showing and hiding a popover toggles between display: none
and display: hidden
. Because these are discrete values, CSS previously did not allow transitions between them. That's since been resolved by the transition-behavior: allow-discrete
property and the @starting-style
at rule. Here's an example.
[popover] {
font-size: 4rem;
transform: skew(-45deg, -45deg);
/*
Can also use
transition: overlay 1s allow-discrete,
transform 1s;
*/
transition: all 1s allow-discrete;
}
[popover]:popover-open {
opacity: 1;
transform: skew(0);
&::backdrop {
background: #f60a;
transition: inherit;
}
}
@starting-style {
[popover]:popover-open {
opacity: 0;
transform: skew(-45deg, -45deg);
&::backdrop {
background: #f0aa;
transition: inherit;
}
}
}
I've used the all
keyword here because I want all of the transitions to happen at once. However, some browsers also support the overlay
property, in combination with transition-behavior: allow-discrete
.
Transitioning between discrete values requires setting start values for the transition once display: none
becomes display: block
. That's what the @starting-style
at rule does. Once the :popover-open
pseudo-class is available, the browser will transition the transform and opacity properties of the popover element. It will also transition the backdrop background color from hot pink to orange.
Browser support is for these features is pretty solid. Firefox, however, doesn't yet transition popovers from their showing state back to their hidden one. Compare the following video to the previous one.
You'll see similar behavior in Safari versions 18.3.1 and earlier.
Key takeaways
HTML popovers simplify a few common front-end patterns. They also introduce a few CSS challenges. Here's what to remember.
- Popovers are always the top-most layer in the stacking context. Use CSS anchor positioning to create 'help' or 'tool-tip' style components.
- Popovers create a new stacking context and containing block.
- Unlike dialog modals, popovers do not make other elements on the page inert.
- Use
transition-behavior: allow-discrete
and@starting-style
to transition between showing and hidden states.
Happy popping!