I know, super niche, but it could be any loop, really. The challenge is having multiple tooltips on the same page that make use of the Popover API for toggling goodness and CSS Anchor Positioning for attaching a tooltip to its respective anchor element.
There’s plenty of moving pieces when working with popovers:
- A
popover
needs an ID (and an accessible role while we’re at it). - A
popovertarget
needs to reference that ID. - IDs have to be unique for semantics, yes, but also to hook a
popover
into apopovertarget
.
That’s just the part dealing with the Popover API. Turning to anchors:
- An anchor needs an
anchor-name
. - A target element needs to reference that
anchor-name
. - Each
anchor-name
must be unique to attach the target to its anchor properly.
The requirements themselves are challenging. But it’s more challenging working inside a loop because you need a way to generate unique IDs and anchor names so everything is hooked up properly without conflicting with other elements on the page. In WordPress, we query an array of page objects:
$property_query = new WP_Query(array(
'post_type' => 'page',
'post_status' => 'publish',
'posts_per_page' => -1, // Query them all!
'orderby' => 'title',
'order' => "ASC"
));
Before we get into our while()
statement I’d like to stub out the HTML. This is how I want a page object to look inside of its container:
<div class="almanac-group">
<div class="group-letter"><a href="#">A</a></div>
<div class="group-list">
<details id="" class="group-item">
<summary>
<h2><code>accent-color</code></h2>
</summary>
</details>
<!-- Repeat for all properties -->
</div>
</div>
<!-- Repeat for the entire alphabet -->
OK, let’s stub out the tooltip markup while we’re here, focusing just inside the <details>
element since that’s what represents a single page.
<details id="page" class="group-item">
<summary>
<h2><code>accent-color</code></h2>
<span id="tooltip" class="tooltip">
<!-- Popover Target and Anchor -->
<button class="info-tip-button" aria-labelledby="experimental-label" popovertarget="experimental-label">
<!-- etc. -->
</button>
<!-- Popover and Anchor Target -->
<div popover id="experimental-label" class="info-tip-content" role="tooltip">
Experimental feature
</div>
</span>
</summary>
</details>
With me so far? We’ll start with the Popover side of things. Right now we have a <button>
that is connected to a <div popover>
. Clicking the former toggles the latter.
Styling isn’t really what we’re talking about, but it does help to reset a few popover things so it doesn’t get that border and sit directly in the center of the page. You’ll want to check out Michelle Barker’s article for some great tips that make this enhance progressively.
.info-tip {
position: relative; /* Sets containment */
/* Bail if Anchor Positioning is not supported */
[popovertarget] {
display: none;
}
/* Style things up if Anchor Positioning is supported */
@supports (anchor-name: --infotip) {
[popovertarget] {
display: inline;
position: relative;
}
[popover] {
border: 0; /* Removes default border */
margin: 0; /* Resets placement */
position: absolute; /* Required */
}
}
This is also the point at which you’ll want to start using Chrome because Safari and Firefox are still working on supporting the feature.
We’re doing good! The big deal at the moment is positioning the tooltip’s content so that it is beside the button. This is where we can start working with Anchor Positioning. Juan Diego’s guide is the bee’s knees if you’re looking for a deep dive. The gist is that we can connect an anchor to its target element in CSS. First, we register the <button>
as the anchor element by giving it an anchor-name
. Then we anchor the <div popover>
to the <button>
with position-anchor
and use the anchor()
function on its inset properties to position it exactly where we want, relative to the <button>
:
.tooltip {
position: relative; /* Sets containment */
/* Bail if Anchor Positioning is not supported */
[popovertarget] {
display: none;
}
/* Style things up if Anchor Positioning is supported */
@supports (anchor-name: --tooltip) {
[popovertarget] {
anchor-name: --tooltip;
display: inline;
position: relative;
}
[popover] {
border: 0; /* Removes default border */
margin: 0; /* Resets placement */
position: absolute; /* Required */
position-anchor: --tooltip;
top: anchor(--tooltip -15%);
left: anchor(--tooltip 110%);
}
}
}
This is exactly what we want! But it’s also where things more complicated when we try to add more tooltips to the page. Notice that both buttons want to cull the same tooltip.
That’s no good. What we need is a unique ID for each tooltip. I’ll simplify the HTML so we’re looking at the right spot:
<details>
<!-- ... -->
<!-- Popover Target and Anchor -->
<button class="info-tip-button" aria-labelledby="experimental-label" popovertarget="experimental-label">
<!-- ... -->
</button>
<!-- Popover and Anchor Target -->
<div popover id="experimental-label" class="info-tip-content" role="tooltip">
Experimental feature
</div>
<!-- ... -->
</details>
The popover has an ID of #experimental-label
. The anchor references it in the popovertarget
attribute. This connects them but also connects other tooltips that are on the page. What would be ideal is to have a sequence of IDs, like:
<!-- Popover and Anchor Target -->
<div popover id="experimental-label-1" class="info-tip-content" role="tooltip"> ... </div>
<div popover id="experimental-label-2" class="info-tip-content" role="tooltip"> ... </div>
<div popover id="experimental-label-3" class="info-tip-content" role="tooltip"> ... </div>
<!-- and so on... -->
We can make the page query into a function that we call:
function letterOutput($letter, $propertyID) {
$property_query = new WP_Query(array(
'post_type' => 'page',
'post_status' => 'publish',
'posts_per_page' => -1, // Query them all!
'orderby' => 'title',
'order' => "ASC"
));
}
And when calling the function, we’ll take two arguments that are specific only to what I was working on. If you’re curious, we have a structured set of pages that go Almanac → Type → Letter → Feature (e.g., Almanac → Properties → A → accent-color
). This function outputs the child pages of a “Letter” (i.e., A → accent-color
, anchor-name
, etc.). A child page might be an “experimental” CSS feature and we’re marking that in the UI with tooltops next to each experimental feature.
We’ll put the HTML into an object that we can return when calling the function. I’ll cut it down for brevity…
$html .= '<details id="page" class="group-item">';
$html .= '<summary>';
$html .= '<h2><code>accent-color</code></h2>';
$html .= '<span id="tooltip" class="tooltip">';
$html .= '<button class="info-tip-button" aria-labelledby="experimental-label" popovertarget="experimental-label"> ';
// ...
$html .= '</button>';
$html .= '<div popover id="experimental-label" class="info-tip-content" role="tooltip">';
// ...
$html .= '</div>';
$html .= '</span>';
$html .= '</summary>';
$html .= '</details>';
return $html;
WordPress has some functions we can leverage for looping through this markup. For example, we can insert the_title()
in place of the hardcoded post title:
$html .= '<h2><code>' . get_the_title(); . '</code></h2>';
We can also use get_the_id()
to insert the unique identifier associated with the post. For example, we can use it to give each <details>
element a unique ID:
$html .= '<details id="page-' . get_the_id(); . '" class="group-item">';
This is the secret sauce for getting the unique identifiers needed for the popovers:
// Outputs something like `id="experimental-label-12345"`
$html .= '<div popover id="experimental-label-' . get_the_id(); . '" class="info-tip-content" role="tooltip">';
We can do the exact same thing on the <button>
so that each button is wired to the right popover:
$html .= '<button class="info-tip-button" aria-labelledby="experimental-label-' . get_the_id(); . '" popovertarget="experimental-label"> ';
We ought to do the same thing to the .tooltip
element itself to distinguish one from another:
$html .= '<span id="tooltip-' . get_the_id(); . '" class="tooltip">';
I can’t exactly recreate a WordPress instance in a CodePen demo, but here’s a simplified example with similar markup:
The popovers work! Clicking either one triggers its respective popover
element. The problem you may have realized is that the targets are both attached to the same anchor element — so it looks like we’re triggering the same popover
when clicking either button!
This is the CSS side of things. What we need is a similar way to apply unique identifiers to each anchor, but as dashed-idents instead of IDs. Something like this:
/* First tooltip */
#info-tip-1 {
[popovertarget] {
anchor-name: --infotip-1;
}
[popover] {
position-anchor: --infotip-1;
top: anchor(--infotip-1 -15%);
left: anchor(--infotip-1 100%);
}
}
/* Second tooltip */
#info-tip-2 {
[popovertarget] {
anchor-name: --infotip-1;
}
[popover] {
position-anchor: --infotip-1;
top: anchor(--infotip-1 -15%);
left: anchor(--infotip-1 100%);
}
}
/* Rest of tooltips... */
This is where I feel like I had to make a compromise. I could have leveraged an @for
loop in Sass to generate unique identifiers but then I’d be introducing a new dependency. I could also drop a <style>
tag directly into the WordPress template and use the same functions to generate the same post identifiers but then I’m maintaining styles in PHP.
I chose the latter. I like having dashed-idents that match the IDs set on the .tooltip
and popover
. It ain’t pretty, but it works:
$html .= '
<style>
#info-tip-' . get_the_id() . ' {
[popovertarget] {
anchor-name: --infotip-' . get_the_id() . ';
}
[popover] {
position-anchor: --infotip-' . get_the_id() . ';
top: anchor(--infotip-' . get_the_id() . ' -15%);
left: anchor(--infotip-' . get_the_id() . ' 100%);
}
}
</style>'
We’re technically done!
The only thing I had left to do for my specific use case was add a conditional statement that outputs the tooltip only if it is marked an “Experimental Feature” in the CMS. But you get the idea.
Isn’t there a better way?!
Yes! But not quite yet. Bramus proposed a new ident()
function that, when it becomes official, will generate a series of dashed idents that can be used to name things like the anchors I’m working with and prevent those names from colliding with one another.
<div class="group-list">
<details id="item-1" class="group-item">...</details>
<details id="item-2" class="group-item">...</details>
<details id="item-3" class="group-item">...</details>
<details id="item-4" class="group-item">...</details>
<details id="item-5" class="group-item">...</details>
<!-- etc. -->
</div>
/* Hypothetical example — does not work! */
.group-item {
anchor-name: ident("--infotip-" attr(id) "-anchor");
/* --infotip-item-1-anchor, --infotip-item-2-anchor, etc. */
}
Let’s keep our fingers crossed for that to hit the specifications soon!
Working With Multiple CSS Anchors and Popovers Inside the WordPress Loop originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.