Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Wed, 20 Aug 2025 04:03:13 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 What is popover=hint? https://frontendmasters.com/blog/what-is-popoverhint/ https://frontendmasters.com/blog/what-is-popoverhint/#respond Wed, 20 Aug 2025 04:03:12 +0000 https://frontendmasters.com/blog/?p=6816 If you know a bit about the popover API in HTML, you might know it’s basically 1) click a button 2) toggle visibility of another element. Una has a great article explaining that there is a bit more to it.

First, there are actually three kinds of popovers. There is the normal kind, which close when you click away (or ESC). If you don’t want that, there is the “manual” kind which requires very explicit closing and lives an isolated life, not affecting other popups. The new kind, “hint”, is an in-betweener where it can be opened without closing other normal popups, yet retains the light dismissal properties.

Second, it doesn’t have to be a click to open a popover. You could bind whatever you want in JavaScript naturally, but with a new interestfor attribute (and CSS friends) you can cause a trigger on essentially a hover (with a mouse) or focus (with a keyboard). All experimental stuff, but ✨cool✨.

]]>
https://frontendmasters.com/blog/what-is-popoverhint/feed/ 0 6816
inset: auto; for popovers https://frontendmasters.com/blog/inset-auto-for-popovers/ https://frontendmasters.com/blog/inset-auto-for-popovers/#respond Fri, 02 May 2025 14:21:38 +0000 https://frontendmasters.com/blog/?p=5759
]]>
https://frontendmasters.com/blog/inset-auto-for-popovers/feed/ 0 5759
Examples of Why The Web Needs Anchored Popovers https://frontendmasters.com/blog/examples-of-why-the-web-needs-anchored-popovers/ https://frontendmasters.com/blog/examples-of-why-the-web-needs-anchored-popovers/#respond Thu, 27 Feb 2025 01:18:44 +0000 https://frontendmasters.com/blog/?p=5243 With popovers, you click a <button>, it opens a <whatever>. No JavaScript is required. It opens on the “top layer” so it will always be visible no matter what. You can click away and it closes and/or offer your own close mechanics. The two elements can be wherever makes the most sense in the DOM without restriction. With CSS anchor positioning, the popover that opens can be positioned perfectly and safely next to whatever it needs to be again without DOM restrictions. It’s the best.

What might use this potent combo? Everything.

These type of menus on GitHub would be great.

A similar type of header dropdown menu on our new version of CodePen we’re working on would love it, which sometimes have nested tooltips I’m crossing my fingers work well.

For flyout menus like this it will be nice to have particularly for the save positioning. Wouldn’t it be nice to have that menu open more “upwards” instead if there wasn’t room before the bottom of the browser window below?

Maybe the DOM positioning matters less above as perhaps it actually makes sense that the submenu is, say, a nested list, which already helps with positioning. That’s OK, sometimes you need extra help and sometimes you don’t, it still nice to use a unified system.

There is about a dozen popups like this in the Riverside.fm interface that would all benefit from positioned popovers. Wouldn’t it be nice to move elements around, even dramatically in response to screen sizes and aspect ratios, and never worry that menu popup positioning would get donked up?

Really anything app-like, like Figma, would benefit from being able to show menus without additional JavaScript having to run calculations and always be watching for window size changes in order to position things correctly, to say nothing of z-index battles that, when lost, can render menus entirely hidden.

Designers will surely enjoy having being able to think in terms of alignment. Where should this popup hang off of? Straight off the right side? Pushed down then to the right? Centered below it? We’ll have lots of positioning options that work great with pretty simple keywords that have smart side effects.

What’s inside a popup can be complex sometimes, involving forms, multiple lists, lots of interactive elements, etc. Not being forced to put that in the same exact DOM area as the button that triggers is might be beneficial for accessibility, not to mention the help handling moving focus in and out of that area properly.

There are a lot of menu examples above, but I think there are just as many examples out there of “styled tooltips” which is something we’ve never properly been able to do without lots of fancy dancing JavaScript.

It’s telling you, the web is chock-a-block with these things.

]]>
https://frontendmasters.com/blog/examples-of-why-the-web-needs-anchored-popovers/feed/ 0 5243
What’s the Difference Between HTML’s Dialog Element and Popovers? https://frontendmasters.com/blog/whats-the-difference-between-htmls-dialog-element-and-popovers/ https://frontendmasters.com/blog/whats-the-difference-between-htmls-dialog-element-and-popovers/#respond Mon, 30 Sep 2024 16:38:20 +0000 https://frontendmasters.com/blog/?p=4069 They are different HTML, to begin with. A dialog is like this:

<dialog id="my-dialog">
  Content
</dialog>

While a popover is an attribute on some other element:

<aside popover id="my-popover">
  Content
</aside>

The reason it’s worth comparing them is that they are quite similar in a lot of ways, both in look and functionality, which can be confusing. It’s worth thinking about which one you really need.

They are both hidden-by-default

If you put either bit of the HTML above onto the page, they will be visually hidden as well as ignored in the accessibility tree by default (but available in the DOM). It isn’t until you specifically show them (via JavaScript or on-page HTML control when available) that they are visible.

Accessibility tree with a hidden dialog and popover in it.
When dialog is open, it’s a part of the accessibility tree.

You can make a <dialog> visible by default in HTML alone:

<dialog
  id="my-dialog"
  open
>
  Content
</dialog>

Where you cannot make a popover visible in HTML alone.

Popovers Have HTML-Only Controls

You can make a popover work (open & close) with HTML controls alone:

<!-- This button will open and close the matching popover. No JavaScript required. -->
<button popovertarget="my-popover">
  Toggle Popover
</button>

<aside popover id="my-popover">
  Content of popover
</aside>

But you cannot build HTML-only controls for a <dialog>. Opening and closing a dialog requires JavaScript event handlers.

JavaScript APIs

Dialog JavaScript APIs

The dialog APIs in JavaScript are interesting in that there are two different distinct APIs for opening it. This is where the term “modal” comes in. Modal is sometimes used as a term for the UI element itself, but here it essentially means if the modal should trap focus inside of it while open, or not.

  • .show() — Open the dialog in a non-modal state, meaning no backdrop is shown and no focus trapping happens. Note that using the open attribute in the HTML/DOM to open the dialog is the same (non-modal).
  • .showModal() — Open the dialog in a modal meaning a backdrop is shown and focus is trapped within the modal.
  • .close() — Closes the dialog (if it’s open).

The showModal() method can throw if the dialog is already open in a non-modal state.

Uncaught InvalidStateError: Failed to execute 'showModal' on 'HTMLDialogElement': The dialog is already open as a non-modal dialog, and therefore cannot be opened as a modal dialog.

Popover JS APIs

Popovers also have JavaScript APIs, but both the opening and closing APIs are different than with modals and do not overlap. These are pretty self explanatory.

  • .showPopover() — Opens the popover.
  • .hidePopover() — Closes the popover.

Calling showPopover on an already open popover or hidePopover on an already hidden popover does not throw.

Focus Trapping

The ability of the dialog element to be opened in a modal state and thus trap focus inside of it is a superpower of this element. It is unique to the dialog element, popovers cannot do this (on their own).

Focus trapping, while it sounds kinda bad, is actually an accessibility feature. After all, that’s what a modal is: it forces you to deal with some interaction before anything else can be done. It’s actually also a WCAG requirement to not trap focus when you shouldn’t, but in the case of a modal, you should be trapping focus — as well as providing a standard way to close the dialog and escape the trap.

Focus can change to other focusable elements inside, and when you’re about to move focus forward to the next element when you’re at the last, it circles back to the first focusable element within the dialog. You get all this “for free” with a <dialog> opened with showModal(), which is otherwise a huge pain in the ass and you probably won’t even do it right (sorry).

If you need this focus trapping, don’t use a popover as it’s not for this job.

Moving Focus

When a dialog is opened (either modal or non-modal), focus is moved to the first focusable element within it. When it is closed, focus is moved back to the element that opened it.

With a popover, focus remains on the element that opened it even after the popup is opened. However, after the popup is open, the next tab will put focus into the popup’s content if there is any in there, regardless of where it is in the DOM, tab through the focusable elements of the popup, then onto other focusable elements outside the popup after the original element that opened it.

This is all tricky work that you get for free by using the <dialog> element or popups and frankly a huge reason to use them 👍.

Escape Key

Both modal dialogs and popups, when open, can be closed by pressing the ESC key. Very handy behavior that helps adhere to accessibility adherence, again given for free, which is tricky and error-prone to write yourself.

Non-modal dialogs do not close with the ESC key, so you’ll need to provide your own close functionality, like:

<button onclick="myDialog.close()">Close</button>

They Have the Same Default Styling

Dialogs and popovers look the same by default and have really basic default styling that you’ll almost certainly want to override.

They are essentially position: fixed; and margin: auto; which centers them in the viewport. This is a probably a smart default for dialogs. In my opinion, popovers are usually begging for anchor positioning to open the popover near where the element that opened it is, but they work nicely as slide-out drawers as well, particularly on mobile.

You’ll likely want to bring your own padding, border, background, typography, internal structure, etc.

The Top Layer

Another amazing feature of both dialogs and popovers is that, when open, they are placed on what is called the “top layer”. It is literally impossible for any other element to be on top of them. It doesn’t matter where they are in the DOM (could be quite nested) or what containing blocks or z-index is involved, the top layer is the top no matter what. (Although – it is true that if you open subsequent dialogs/popovers, e.g. a button in a dialog opens another dialog, the second one will beat the first and be on top, as you’d expect.) This top-layer ability is yet another thing you get for free and a fantastic reason to use these native features.

DevTools showing the #top-layer

Backdrops

Both (modal) dialogs and popovers use (and share) a backdrop. This is the layer above all content on the page that covers the page (by default), but is still underneath the actual dialog or popover. This backdrop is a very light transparent black by default, but can be styled like this:

::backdrop {
  background: color-mix(in srgb, purple, transparent 20%);
}

That will apply both to modal dialogs and default popovers. If you wanted to have different backdrops for them, you could scope them like this, as the backdrop is applied to the element that is open:

[popover]::backdrop {
  
}

dialog::backdrop {
  
}

.some-very-specific-element::backdrop {

}

You don’t have to show a backdrop if you don’t want to, but it’s a good indicator for users particularly when modal behavior is in play (and perhaps an anti-pattern when it’s not, as you may be visually hiding elements in focus).

Non-modal dialogs do not have a backdrop.

Soft Dismiss

This feature is unique to popovers. You can “click outside” the popover to close it, by default (yet another tricky behavior to code yourself). I’ve used the term “default popover” in this article and what I mean is when you don’t provide a value to the popover attribute. That implies auto as a value which is what makes soft dismissal work.

<!-- Soft Dismissible -->
<div popover id="myPopover"></div>

<!-- Soft Dismissible -->
<div popover="auto" id="myPopover"></div>

<!-- NOT Soft Dismissible -->
<div popover="manual" id="myPopover"></div>

Multiple Open

Both dialogs and popovers can have multiple open at once. The most recent one to be opened will be the one that is most “on top” and will close the first via soft dismiss or the ESC key. (Also see the CloseWatcher API).

For a modal dialog, note that because the rest of the page is essentially inert when it is open, the near-only way to open another is via interactivity within the first opened dialog.

For popups, because the default behavior has soft dismissal, the popovers will need to be popover="manual" or be opened with JavaScript without interaction for multiple of them to be open.

Purpose and Semantics

Popovers likely have more use cases than dialogs. Any time you need a tooltip or to provide more contextual information that has good reason not to be visible by default, a popover is a good choice.

Non modal dialogs are pretty similar to a popup, but are perhaps better suited to situations where there is no other element on the page that is relevant to the messaging. Perhaps something like a “No internet connection detected” message, which could be very important to tell a user, but doesn’t need to 100% stop other activity on the page.

Modal dialogs are show-stoppers, forcing a user to deal with them before anything else can happen. They should be used sparingly (they are reached for far too much, some people say). Perhaps a message like “Are you sure you want to delete this entire document? This cannot be undone.” would be a modal dialog, as any other interaction on the page is moot should the user be deleting.

Animation

This is all very cutting edge right now, so browser support is spotty, but both of these elements can be animated both on the way in and out.


I played around with this Pen while I was thinking and working on all this, which may be helpful to you if you’re doing the same.

]]>
https://frontendmasters.com/blog/whats-the-difference-between-htmls-dialog-element-and-popovers/feed/ 0 4069
The Dialog Element with Entry *and* Exit Animations https://frontendmasters.com/blog/the-dialog-element-with-entry-and-exit-animations/ https://frontendmasters.com/blog/the-dialog-element-with-entry-and-exit-animations/#respond Wed, 28 Aug 2024 17:11:01 +0000 https://frontendmasters.com/blog/?p=3559 Una Kravets blogged the other day that animating entry effects are now supported in the latest stable version of all major browsers. The new cool way to do it, that is. We’ve long had trickery like applying a @keyframe animation with a to frame that would behave like an “entry effect”, but it was a bit awkward and didn’t work in all situations. Specifically one like using the new and very useful <dialog> element.

This bit of code says a lot:

dialog[open] {
  transition: 
    translate 0.7s ease-out, 
    display 0.7s ease-out allow-discrete;

  /* Post-Entry (Normal) State */
  translate: 0 0;

  /* Pre-Entry State */
  @starting-style {
    translate: 0 100vh;
  }
}

There are two big things at work there:

  1. The display property is listed in the transitions, with the keyword allow-discrete. The code for it is hidden in User-Agent stylesheets, but when a <dialog> moves from close (default) to open, the display goes from none to block. Using this keyword means that the display property is changed after the animation timing, so animations can actually happen.
  2. The @starting-style gives us an opportunity to apply styling to the element just as it’s entering it’s current state, meaning the transition will happen between the styles declared inside and outside that block.

Golf clap. Everything is awesome.

What Una didn’t cover, on purpose surely, was exit animations (because they aren’t in “Baseline” yet, meaning not supported across browsers). But they are supported in Chrome-n-friends land, so I thought it was worth looking at. To me, they are just as interesting, cool, and useful as the entry kind.

Both Entry and Exit

The trick isn’t terribly different than the code above, it’s just to have very specific styles for both the open and closed (i.e. :not([open]) states. Like this:

dialog {
  --duration: 0.34s;

  transition: 
    translate var(--duration) ease-in-out, 
    scale     var(--duration) ease-in-out,
    filter    var(--duration) ease-in-out,
    display   var(--duration) ease-in-out allow-discrete;

  &[open] {

    /* Post-Entry (Normal) State */
    translate: 0 0;
    scale: 1;
    filter: blur(0);

    /* Pre-Entry State */
    @starting-style {
      translate: 0 8vh;
      scale: 1.15;
      filter: blur(8px);
    }
  }

  /* Exiting State */
  &:not([open]) {
    translate: 0 -8vh;
    scale: 1.15;
    filter: blur(8px);
  }
}

Check it out:

And a video in case you’re in a browser that doesn’t support it yet:

Note that not only does it have entry and exit animations, but those states are different — which is very cool! Emphasizing that, here’s one where I move the dialog along an offset-path so the exit is really a continuation of the path:

Usage with Popovers

This isn’t exclusively for dialogs, you can make it work with whatever. But naturally open-closable things make the most sense. Like native popovers! Nils Riedemann has a nice demo here:

]]>
https://frontendmasters.com/blog/the-dialog-element-with-entry-and-exit-animations/feed/ 0 3559
CloseWatcher https://frontendmasters.com/blog/closewatcher/ https://frontendmasters.com/blog/closewatcher/#respond Tue, 27 Aug 2024 18:39:43 +0000 https://frontendmasters.com/blog/?p=3669 I’m first hearing about the CloseWatcher API after running across Abdelrahman Awad’s blog post about it. The MDN docs are quite direct, making the purpose clear:

Some UI components have “close behavior”, meaning that the component appears, and the user can close it when they are finished with it. For example: sidebars, popups, dialogs, or notifications.

Users generally expect to be able to use a particular mechanism to close these elements, and the mechanism tends to be device-specific. For example, on a device with a keyboard it might be the Esc key, but Android might use the back button.

So rather than bind an closing action to the Esc key, use this API, which normalizes that user action across platforms.

I also like how it automatically creates a “stack” of things to close. So if there are multiple closeable things open, a close event only closes the most recently open one. I made a demo to play with this (note the Chrome-n-friends only support!).

A video in case it doesn’t work on your browser:

]]>
https://frontendmasters.com/blog/closewatcher/feed/ 0 3669
Footnotes Progressively Enhanced to Popovers https://frontendmasters.com/blog/footnotes-progressively-enhanced-to-popovers/ https://frontendmasters.com/blog/footnotes-progressively-enhanced-to-popovers/#comments Wed, 19 Jun 2024 11:41:27 +0000 https://frontendmasters.com/blog/?p=2743 Michelle Barker is onto an excellent idea in Progressively Enhanced Popover Toggletips. Her idea is that footnotes are the perfect sort of thing to make popovers. You know popovers: It’s like when I put a superset1 number in a sentence (like I just did) and then link it down to the bottom of the post to explain something in more detail which would have been distracting detail to put in the paragraph itself. But “jumping down” like <a href="#footnote-1"> is also distracting. Why not just open the extra information in a little popup? Indeed!

I like the idea of “doing both”. As Michelle says:

We could also hide the list when anchor positioning is supported, but I think it’s also more user-friendly to include references as a list in addition to within the document.

But then I read:

A small downside is that this approach requires a bit of content duplication.

The way Michelle did it was to put an <ol> of footnotes at the bottom as usual, and then also put the [popover] footnotes up in the content as well, which is the duplication.

The duplication isn’t that big of a deal. It just could be a little annoying for some readers (uh, yes, I already read this thank you). It mostly sucks because it makes them harder to maintain. You’d have to make identical changes manually in two places. If that’s no problem, Michelle’s technique is sound and you could use it as-is!

But… it got me thinking that there must be a way to re-use the same content for both!

Article Series

Attempt 1) Duplicates via JavaScript

My first take was that we gotta stop the manual content duplication. That’ll be a huge pain to maintain. So let’s do the duplication of the footnote list in JavaScript, hide it both visually and from screen readers as the original list is still there, and use the duplicates for the popovers.

Since that original list is still there, we can test the browser support for popovers and just entirely bail out if the support isn’t there.

// browser supports popovers
if (HTMLElement.prototype.hasOwnProperty("popover")) {

}

Then we’ll:

  1. Clone the existing list of footnotes
  2. Adjust the IDs so we don’t have duplicate IDs
  3. Add the popover attributes
if (HTMLElement.prototype.hasOwnProperty("popover")) {
  // find the list of footnotes and clone it
  const references = document.querySelector(".references");
  const duplicateReferences = references.cloneNode(true);
  
  // hide the duplicate from screen readers before re-injecting it into DOM
  duplicateReferences.setAttribute("aria-hidden", true);
  references.parentNode.insertBefore(duplicateReferences, references.nextSibling);
	
  // loop over each footnote to make a popover
  const popovers = duplicateReferences.querySelectorAll("li");
  popovers.forEach(popover => {
    popover.id = `ref_${popover.id}`;
    popover.setAttribute("popover", true);
  });
}

Looking in the web inspector we can see now that both lists are there:

The <button> elements which trigger the popups are still up above in the content, referring to these new popovers that now exist. In fact both the link and the button are both there:

<a class="popover-replace" href="#1"><sup>1</sup></a>
<button popovertarget="ref_1"><sup>1</sup></button>

People will only ever see/use one or the other. There is an <a> link there by default, then if popovers are supported, it’s replaced by the button. That was already in Michelle’s code:

[popovertarget] {
  display: none;
}

@supports (anchor-name: --ref_1) {
  [popovertarget] {
    display: inline;
    ...
  }
}

So that’s this!

Attempt 2) The Original List can be Popovers Too

That previous attempt required JavaScript, which doesn’t bother me really as it’s progressive enhancement, but it’s not always clear where JavaScript like that that spreads across different areas of a page would go, and may be a bit more fragile.

Can’t the footnotes at the bottom of the page… just be the popovers themselves? They can! Mostly!

The first trouble is that adding the popover attribute immediately makes them hidden. So the trick is to un-hide them and style them as you want them as a list. Then when they are open, which we know with the :popover-open pseudo-class, we can style them like popovers.

/* closed */
[popover] {
  display: list-item;
  position: relative;
  border: 0;
  padding: 0;
  width: auto;
  overflow: visible;

  /* open */
  &:popover-open {
    display: block;
    margin: 0;
    padding: 1rem;
    background: lavender;
    border-radius: 0.5rem;
    max-width: 15rem;
    ...
   }
}

The first side-effect here is that when any of these popovers are open, the footnote in the list below disappears. The content literally transports to the new popover location. So that causes a bit of reflow and you might not like it for that reason alone. But it works!

There is a second side-effect here. The list of footnotes-become-popovers are forced to be display: list-item so they are visible and have numbers and such. If you leave them as that when you move them into popover position, the little number marker will come with it. You can hide it:

[popover] {
  &:popover-open {
    &::marker {
      content: "";
    }
  }
}

If you make the popover, say, display: block instead, this immediately breaks the automatic list numbering. You’ll need to bring your own list numbering that is based on counters instead.

.references {
  counter-reset: item;
  > li {
    counter-increment: item;
    &::marker {
      content: counter(item) ") ";
    }
  }
}

Others?

There are upsides and downsides to both of the above techniques. Mostly they focus on solving content duplication.

There is another thing they fail at though. Michelle notes:

I’m making use of the :has pseudo-class to position the little arrows attached to the bubbles when the popover is open, by styling the ::before pseudo-element of the button.

My two techniques fail at having the little arrow be present because the DOM position of the button with target and popover aren’t next to each other anymore. You might imagine the little pointer off the popover bubble being part of the bubble itself, but no, Michelle cleverly made it part of the button instead, so it’s always pointing at the correct place. I love that.

If you wanted to preserve that, you could write JavaScript that duplicates the footnote popovers like I did in the first example, but then place them within the content (likely with aria-hidden to avoid duplicate content awkwardness) like in Michelle’s original demo.

I’ll leave that as an excercise for you readers if you wish to take it on. I’d love to see it, or any other interesting variations.

Article Series


  1. My favorite part about footnotes is the perfect excuse to use the <sup> element. ↩︎
]]>
https://frontendmasters.com/blog/footnotes-progressively-enhanced-to-popovers/feed/ 1 2743
State of HTML 2023 Results https://frontendmasters.com/blog/state-of-html-2023-results/ https://frontendmasters.com/blog/state-of-html-2023-results/#respond Mon, 20 May 2024 17:33:16 +0000 https://frontendmasters.com/blog/?p=2267 The State of HTML 2023 Results are out!

I thought this survey was more interesting to take than reading these results. It’s not that the results aren’t interesting. I’m almost impressed by how low the “used it” percentages are for certain features, like less than half of people have used a <details>?? And 28% are itching to use tabindex again? 😬 It’s just that in taking the survey we were all reminded of how many interesting things are going on in HTML land and likely learned about a good many things, like there is a Badging API??

I like Lea’s conclusion:

Some argue that improving HTML is futile, but the survey resoundingly demonstrates the contrary. Developers crave more interactive HTML elements: not only were interactive elements like <datalist> or the Popover API among those accumulating the most positive sentiment across all categories, but in addition all top missing elements were interactive widgets.

That new Popover API is extremely awesome, in no small part because it can be used in HTML alone.

]]>
https://frontendmasters.com/blog/state-of-html-2023-results/feed/ 0 2267
Using the Popover API for HTML Tooltips https://frontendmasters.com/blog/using-the-popover-api-for-html-tooltips/ https://frontendmasters.com/blog/using-the-popover-api-for-html-tooltips/#comments Mon, 06 May 2024 16:50:59 +0000 https://frontendmasters.com/blog/?p=2027 Can it be done? This plucky front-end developer intends to find out.

Can we do it?

We looked at the Popover API and how it’s made it’s way across all browsers already just last week. One of the things I should have done is looked at the accessibility considerations more closely. Thanks to Melanie Sumner there is a great explainer with demos. I tried to adhere to the points made in there the best I could while making a classic tooltips experience, and we’ll do a bit of a review at the end where deviations happened.

Article Series

Starting With HTML

This is actually my favorite part, as remember, this API can be used entirely in HTML. That rules. Remember when we got <details> where you could build an interactive open/close disclosure widget thing with just HTML? That also rules, but this is even cooler.

One small weirdness though, it only works in HTML alone when a <button> is the “invoker” of opening the popup. So in the case of a tooltip, that button might come right in the middle of a sentence like.

<p>
   This blog post was written by 
   <button popovertarget="popover-chris-coyier">
     Chris Coyier
   </button>
   the genius.
</p>

Then elsewhere in the DOM (the location of which doesn’t effect the core functionality):

<div id="popover-chris-coyier" popover role="tooltip">
  <img src="/images/chris.jpg" alt="A portrait of Chris Coyier in a tuxedo." width="100" height="100">
  <h4>Chris Coyier</h4>
  <p>Handsome fella.</p>
</div>

Note I’m calling this an HTML Tooltip, because what pops up isn’t text alone, which you might argue can be done with the title attribute. These popovers are far more flexible, allowing you to style them and can contain anything HTML can.

The weird part here is the button smack in the middle of the paragraph. That’ll be a bit awkward styling-wise, but that’s surmountable. Mostly I don’t know if that’s “cool” screen-reader wise. So if someone know’s, feel free to chime in. If it’s not cool, we might have to think about using a different element that is cool and relying on JavaScript to invoke the popup.

The Basic CSS

I say basic, because we cannot to tooltip-like styling in CSS for these yet. That is, we cannot position them next to the button that invoked them. We’ll get that, someday, when we can use the Anchor Positioning API.

But we can do everything else that we want in terms of styling.

For one thing, let’s make the the middle-of-text look of the button look like something you can click. That’s something Melanie pointed out as a requirement. Rather than just being blue and underlined like a normal link, I’ll add an icon so it indicates slightly different behavior. We also need to undo basic button styling so the button looks more like just some text. I usually have a class for that I call “text-like”. So:

button[popovertarget].text-like {
  border: 0;
  background: none;
  font: inherit;
  display: inline-block;

  color: #1e88e5;
  text-decoration-style: dashed;
  text-decoration-line: underline;
  text-decoration-color: #42a5f5;
  text-underline-offset: 2px;
  overflow: visible;
  

  padding: 0 1.1rem 0 0; /* space for icon */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' width='100' title='question-circle'%3E%3Cpath fill='%231e88e5' d='M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zM262.655 90c-54.497 0-89.255 22.957-116.549 63.758-3.536 5.286-2.353 12.415 2.715 16.258l34.699 26.31c5.205 3.947 12.621 3.008 16.665-2.122 17.864-22.658 30.113-35.797 57.303-35.797 20.429 0 45.698 13.148 45.698 32.958 0 14.976-12.363 22.667-32.534 33.976C247.128 238.528 216 254.941 216 296v4c0 6.627 5.373 12 12 12h56c6.627 0 12-5.373 12-12v-1.333c0-28.462 83.186-29.647 83.186-106.667 0-58.002-60.165-102-116.531-102zM256 338c-25.365 0-46 20.635-46 46 0 25.364 20.635 46 46 46s46-20.636 46-46c0-25.365-20.635-46-46-46z' /%3E%3C/svg%3E");
  background-size: 0.8em;
  background-repeat: no-repeat;
  background-position: center right 1px;

  &:focus,
  &:hover {
    text-decoration-color: lightblue;
    text-decoration-style: solid;
  }
}

That leads to a within-paragraph look like this:

Then if we apply some super basic styling to the [popover] element itself, we can get a popover opening exactly in the middle of the page like this:

A couple of notes here:

  • I did not use any ::backdrop styling to style the rest of the page behind the popover. I feel like that may be a bit of an anti-pattern if using for tooltips. I don’t see any need to mess with the rest of the page when a tooltip is open.
  • I wanted to use flexbox on the popup, but that caused an issue, because display: flex; overrides the default display: none; of the popup, making it visible all the time. Instead, you can use [popover]:popover-open { } to apply it only when the popover is open. Or use an internal wrapper or whatever.

The Functional JavaScript

We need JavaScript here for two jobs:

  1. Normal tooltip behavior suggests you should be able to hover over an element and the tooltip will appear. That cannot be done in HTML alone. Maybe it could be done in CSS somehow with an animation delay or something, but it’s likely a bit more straightforward in JavaScript.
  2. Again, CSS doesn’t have anchor positioning yet, so we’ll do positioning with JavaScript.

These are both progressive enhancements, which is nice. So if either thing fails, the tooltip should still work.

The Hover Delay

For the delay, we’ll start a timer when the mouse cursor enters a button that opens a popup, if the timer finishes, we’ll open it. If the mouse cursor leaves before the timeout, we’ll clear that timeout:

const popoverButtons = document.querySelectorAll(
  "button[popovertarget]"
);

popoverButtons.forEach((button) => {
  let timeout = 0;
  button.addEventListener("mouseenter", () => {
    const target = button.getAttribute("popovertarget");
    const popover = document.querySelector("#" + target);
    // delay opening
    timeout = setTimeout(() => {
      popover.showPopover();
    }, 1500);
  });

  button.addEventListener("mouseleave", () => {
    clearTimeout(timeout);
  });
});

The Positioning

I went for a library called Floating UI to do this. I came across it the other day while using a library called Shepherd JS for creating tours (example). Shepherd uses it for positioning the steps of the tour, which are usually not far away from what an HTML tooltip might look like.

The .showPopover() API is what opens the popup, so the trick is positioning it as it is being opened.


popoverButtons.forEach((button) => {
  let timeout = 0;
  button.addEventListener("mouseenter", () => {
    const target = button.getAttribute("popovertarget");
    const popover = document.querySelector("#" + target);
    // delay opening
    timeout = setTimeout(() => {
      popover.showPopover();
      computePosition(button, popover, {
        placement: "top",
        middleware: [flip(), shift({ padding: 5 }), offset(6)]
      }).then(({ x, y }) => {
        Object.assign(popover.style, {
          left: `${x}px`,
          top: `${y}px`
        });
      });
    }, 1500);
  });

  button.addEventListener("mouseleave", () => {
    clearTimeout(timeout);
    button.removeAttribute("style");
  });
});

All that computePosition stuff with the middleware is just how Floating UI works. The flip() function is especially nice, which is essentially “edge detection”. Now the popover will attempt to be positioned on top like this:

But if there is no room on top, because that’s where the edge of the browser window is, it will “flip” to the bottom like this:

Nice.

Demo

Review

  • ✅ The links that open popups looks like that’s probably what they will do.
  • ✅ The links the trigger the popups work. They can be clicked to open (and close) the popup. JavaScript provides a hover-to-open effect as well, mimicking title behavior.
  • 🤷‍♀️ Unsure if a <button> in the middle of a <p> where the text is part of the sentence is acceptable from an accessibility perspective.
  • 🤷‍♀️ Unsure where the perfect DOM Positioning of the popup would be. My gut tells me to treat it like a footnote, putting them at the end of the main content they apply to. This seems like it would matter quite a bit for something like syndication/RSS where they might just appear as additional paragraphs without context. Or possibly right after the text element that has the popup so they are closer contextually.
  • 🤷‍♀️ I would probably always use popover="hint" to allow for multiple popovers to be open at once in a tooltip scenario, but it’s unclear if that will happen or if that will be the naming.

Article Series

]]>
https://frontendmasters.com/blog/using-the-popover-api-for-html-tooltips/feed/ 8 2027
Popover API is Here https://frontendmasters.com/blog/popover-api-is-here/ https://frontendmasters.com/blog/popover-api-is-here/#comments Tue, 30 Apr 2024 18:12:46 +0000 https://frontendmasters.com/blog/?p=1948 The Popover API has support across browsers now, and apparently the new way to say that is “landed in Baseline”, albeit as “newly available” rather than “widely available”, which may be an important distinction for you.

A popover is very much like a <dialog> in that you don’t have to worry about where it exists in the DOM. When it’s open, it will be “on top”. That alone is a sick feature. I dislike having to put modals as children of the <body> just so they don’t have other elements inescapable stacking contexts to worry about.

So then how is it different than a <dialog>? Popups are not “modal”. The web-world definition of modal is that it forces a user to deal with it (the rest of the page cannot receive focus, it is inert). Popups just… pop… up. The user is free to do anything else while a popup is open. Dialogs, not so much.

Robin showed off how extremely very easy it is to use:

<button popovertarget="mypopover">Toggle the popover</button>

<div id="mypopover" popover>Popover content</div>

The fact that that’s possible in HTML alone is awfully nice. But of course you can style it with CSS if you like. Plus you get freebies like the fact that they ESC key closes it. Taking it a bit further, here’s an only slightly more fleshed out example using other cool freebies like popovertargetaction="hide":

(p.s. I dig the trick of getting a white ❌ with the filter property as shown above. Golf clap.)

Robin goes on to say that the logic for using it is something like this:

  1. Use title if the content in my popover is just text.
  2. Use the popover attribute if the content is more than plain text and like a menu of options or something.
  3. Use <dialog> if you need to force the user to make a decision or block all other interactions on the page.

I only disagree on the first one. I think title is pretty useless and I only tend to include one if some accessibility tool warns me to add one, like on an <iframe>. The title attribute does nothing on mobile/touch devices or screen readers. You can’t control anything about it, like how it looks, where it goes, or how long it takes to show up if it does at all. If we get more control over it (which, who knows, we might) then I’d be happy to take another look, but it seems likely it’ll still be a “only use if it’s a just text” situation.

I’m certainly a fan of this API existing, but I do think it’s very hampered right now without the Anchor Position API being nearly as well supported. Massive use cases like tooltips or flyout menus aren’t really possible without it. Una has written about this as well. And good news, it can also be HTML powered.

<button id="menu-toggle" popovertarget="menu-items">
  Open Menu
</button>

<ul id="menu-items" popover anchor="menu-toggle">
  <li class="item">...</li>
  <li class="item">...</li>
</ul>

Note the anchor attribute there connecting the two elements. So cool. And again, you can take finer grained control with CSS if you’d like. “Open right next to this other element” is clutch. Heads up though, the anchor attribute is not ready yet.

Taking it futher, here’s another idea from Una that uses an anchor element as a central position and then moves individual items in a circle around it.

I’ll tell ya, Una is ready with all this! Particularly with the Anchor Positioning API, she’s got a bunch of demos and a little helper website to make sure you can declare the positioning CSS you intend:

I now will await patiently the Anchor Positioning API to drop everwhere. Alas, Popover was part of interop 2024 where Anchor was not.

]]>
https://frontendmasters.com/blog/popover-api-is-here/feed/ 2 1948