> All in One 586

Ads

Monday, March 2, 2026

Popover API or Dialog API: Which to Choose?

Choosing between Popover API and Dialog API is difficult because they seem to do the same job, but they don’t!

After a bit lots of research, I discovered that the Popover API and Dialog API are wildly different in terms of accessibility. So, if you’re trying to decide whether to use Popover API or Dialog’s API, I recommend you:

  • Use Popover API for most popovers.
  • Use Dialog’s API only for modal dialogs.

Popovers vs. Dialogs

The relationship between Popovers and Dialogs are confusing to most developers, but it’s actually quite simple.

Dialogs are simply subsets of popovers. And modal dialogs are subsets of dialogs. Read this article if you want to understand the rationale behind this relationship.

![[popover-accessible-roles.jpg.webp]]

This is why you could use the Popover API even on a <dialog> element.

<!-- Using popover on a dialog element --> 
<dialog popover>...</div> 

Stylistically, the difference between popovers and modals are even clearer:

  • Modals should show a backdrop.
  • Popovers should not.

Therefore, you should never style a popover’s ::backdrop element. Doing so will simply indicate that the popover is a dialog — which creates a whole can of problems.

You should only style a modal’s ::backdrop element.

Popover API and its accessibility

Building a popover with the Popover API is relatively easy. You specify three things:

  • a popovertarget attribute on the popover trigger,
  • an id on the popover, and
  • a popover attribute on the popover.

The popovertarget must match the id.

<button popovertarget="the-popover"> ... </button>
<dialog popover id="the-popover"> The Popover Content </dialog>

Notice that I’m using the <dialog> element to create a dialog role. This is optional, but recommended. I do this because dialog is a great default role since most popovers are simply just dialogs.

This two lines of code comes with a ton of accessibility features already built-in for you:

  • Automatic focus management
    • Focus goes to the popover when opening.
    • Focus goes back to the trigger when closing.
  • Automatic aria connection
    • No need to write aria-expanded, aria-popup and aria-controls. Browsers handle those natively. Woo!
  • Automatic light dismiss
    • Popover closes when user clicks outside.
    • Popover closes when they press the Esc key.

Now, without additional styling, the popover looks kinda meh. Styling is a whole ‘nother issue, so we’ll tackle that in a future article. Geoff has a few notes you can review in the meantime.

Dialog API and its accessibility

Unlike the Popover API, the Dialog API doesn’t have many built-in features by default:

  • No automatic focus management
  • No automatic ARIA connection
  • No automatic light dismiss

So, we have to build them ourselves with JavaScript. This is why the Popover API is superior to the Dialog API in almost every aspect — except for one: when modals are involved.

The Dialog API has a showModal method. When showModal is used, the Dialog API creates a modal. It:

  1. automatically inerts other elements,
  2. prevents users from tabbing into other elements, and
  3. prevents screen readers from reaching other elements.

It does this so effectively, we no longer need to trap focus within the modal.

But we gotta take care of the focus and ARIA stuff when we use the Dialog API, so let’s tackle the bare minimum code you need for a functioning dialog.

We’ll begin by building the HTML scaffold:

<button 
  class="modal-invoker" 
  data-target="the-modal" 
  aria-haspopup="dialog"
>...</button>

<dialog id="the-modal">The Popover Content</dialog>

Notice I did not add any aria-expanded in the HTML. I do this for a variety of reasons:

  1. This reduces the complexity of the HTML.
  2. We can write aria-expanded, aria-controls, and the focus stuff directly in JavaScript – since these won’t work without JavaScript.
  3. Doing so makes this HTML very reusable.

Setting up

I’m going to write about a vanilla JavaScript implementation here. If you’re using a framework, like React or Svelte, you will have to make a couple of changes — but I hope that it’s gonna be straightforward for you.

First thing to do is to loop through all dialog-invokers and set aria-expanded to false. This creates the initial state.

We will also set aria-controls to the <dialog> element. We’ll do this even though aria-controls is poop, ’cause there’s no better way to connect these elements (and there’s no harm connecting them) as far as I know.

const modalInvokers = Array.from(document.querySelectorAll('.modal-invoker'))

modalInvokers.forEach(invoker => {
  const dialogId = invoker.dataset.target
  const dialog = document.querySelector(`#${dialogId}`)
  invoker.setAttribute('aria-expanded', false)
  invoker.setAttribute('aria-controls', dialogId)
})

Opening the modal

When the invoker/trigger is clicked, we gotta:

  1. change the aria-expanded from false to true to show the modal to assistive tech users, and
  2. use the showModal function to open the modal.

We don’t have to write any code to hide the modal in this click handler because users will never get to click on the invoker when the dialog is opened.

modalInvokers.forEach(invoker => {
  // ... 

  // Opens the modal
  invoker.addEventListener('click', event => {
    invoker.setAttribute('aria-expanded', true)
    dialog.showModal()
  })
})

Great. The modal is open. Now we gotta write code to close the modal.

Closing the modal

By default, showModal doesn’t have automatic light dismiss, so users can’t close the modal by clicking on the overlay, or by hitting the Esc key. This means we have to add another button that closes the modal. This must be placed within the modal content.

<dialog id="the-modal"> 
  <button class="modal-closer">X</button>
  <!-- Other modal content -->
</dialog>

When users click the close button, we have to:

  1. set aria-expanded on the opening invoker to false,
  2. close the modal with the close method, and
  3. bring focus back to the opening invoker element.
modalInvokers.forEach(invoker => {
  // ... 

  // Opens the modal
  invoker.addEventListener('click', event => {
    invoker.setAttribute('aria-expanded', true)
    dialog.showModal()
  })
})

const modalClosers = Array.from(document.querySelectorAll('.modal-closer'))

modalClosers.forEach(closer => {
  const dialog = closer.closest('dialog')
  const dialogId = dialog.id
  const invoker = document.querySelector(`[data-target="${dialogId}"]`)
  
  closer.addEventListener('click', event => {
    dialog.close()
    invoker.setAttribute('aria-expanded', false)
    invoker.focus()
  })
})

Phew, with this, we’re done with the basic implementation.

Of course, there’s advanced work like light dismiss and styling… which we can tackle in a future article.

Can you use the Popover API to create modals?

Yeah, you can.

But you will have to handle these on your own:

  1. Inerting other elements
  2. Trapping focus

I think what we did earlier (setting aria-expanded, aria-controls, and focus) are easier compared to inerting elements and trapping focus.

The Dialog API might become much easier to use in the future

A proposal about invoker commands has been created so that the Dialog API can include popovertarget like the Popover API.

This is on the way, so we might be able to make modals even simpler with the Dialog API in the future. In the meantime, we gotta do the necessary work to patch accessibility stuff.

Deep dive into building workable popovers and modals

We’ve only began to scratch the surface of building working popovers and modals with the code above — they’re barebone versions that are accessible, but they definitely don’t look nice and can’t be used for professional purposes yet.

To make the process of building popovers and modals easier, we will dive deeper into the implementation details for a professional-grade popover and a professional-grade modal in future articles.

In the meantime, I hope these give you some ideas on when to choose the Popover API and the Dialog API!

Remember, there’s no need to use both. One will do.


Popover API or Dialog API: Which to Choose? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



from CSS-Tricks https://ift.tt/EOxRAIf
via IFTTT

Sunday, March 1, 2026

Partly Cloudy today!



With a high of F and a low of 30F. Currently, it's 40F and Clear outside.

Current wind speeds: 6 from the Southeast

Pollen: 0

Sunrise: March 1, 2026 at 07:24PM

Sunset: March 2, 2026 at 06:44AM

UV index: 0

Humidity: 49%

via https://ift.tt/SHZf7dC

March 2, 2026 at 10:02AM

Saturday, February 28, 2026

Mostly Cloudy today!



With a high of F and a low of 28F. Currently, it's 45F and Fair outside.

Current wind speeds: 12 from the Southeast

Pollen: 0

Sunrise: February 28, 2026 at 07:25PM

Sunset: March 1, 2026 at 06:42AM

UV index: 0

Humidity: 33%

via https://ift.tt/rI8WclH

March 1, 2026 at 10:02AM

Friday, February 27, 2026

Clear today!



With a high of F and a low of 32F. Currently, it's 44F and Partly Cloudy outside.

Current wind speeds: 3 from the Southeast

Pollen: 0

Sunrise: February 27, 2026 at 07:27PM

Sunset: February 28, 2026 at 06:41AM

UV index: 0

Humidity: 21%

via https://ift.tt/YyI0nge

February 28, 2026 at 10:02AM

What’s !important #6: :heading, border-shape, Truncating Text From the Middle, and More

Despite what’s been a sleepy couple of weeks for new Web Platform Features, we have an issue of What’s !important that’s prrrretty jam-packed. The web community had a lot to say, it seems, so fasten your seatbelts!

@keyframes animations can be strings

Peter Kröner shared an interesting fact about @keyframes animations — that they can be strings:

@keyframes "@animation" {
  /* ... */
}

#animate-this {
  animation: "@animation";
}

Yo dawg, time for a #CSS fun fact: keyframe names can be strings. Why? Well, in case you want your keyframes to be named “@keyframes,” obviously! #webdev

[image or embed]

— Peter Kröner (@sirpepe.bsky.social) Feb 18, 2026 at 10:33

I don’t know why you’d want to do that, but it’s certainly an interesting thing to learn about @keyframes after 11 years of cross-browser support!

: vs. = in style queries

Another hidden trick, this one from Temani Afif, has revealed that we can replace the colon in a style query with an equals symbol. Temani does a great job at explaining the difference, but here’s a quick code snippet to sum it up:

.Jay-Z {
  --Problems: calc(98 + 1);

  /* Evaluates as calc(98 + 1), color is blueivy */
  color: if(style(--Problems: 99): red; else: blueivy);

  /* Evaluates as 99, color is red */
  color: if(style(--Problems = 99): red; else: blueivy);
}

In short, = evaluates --Problems differently to :, even though Jay-Z undoubtably has 99 of them (he said so himself).

Declarative <dialog>s (and an updated .visually-hidden)

David Bushell demonstrated how to create <dialog>s declaratively using invoker commands, a useful feature that allows us to skip some J’Script in favor of HTML, and works in all web browsers as of recently.

Also, thanks to an inquisitive question from Ana Tudor, the article spawned a spin-off about the minimum number of styles needed for a visually-hidden utility class. Is it still seven?

Maybe not…

How to truncate text from the middle

Wes Bos shared a clever trick for truncating text from the middle using only CSS:

Someone on reddit posted a demo where CSS truncates text from the middle. They didn't post the code, so here is my shot at it with Flexbox

[image or embed]

— Wes Bos (@wesbos.com) Feb 9, 2026 at 17:31

Donnie D’Amato attempted a more-native solution using ::highlight(), but ::highlight() has some limitations, unfortunately. As Henry Wilkinson mentioned, Hazel Bachrach’s 2019 call for a native solution is still an open ticket, so fingers crossed!

How to manage color variables with relative color syntax

Theo Soti demonstrated how to manage color variables with relative color syntax. While not a new feature or concept, it’s frankly the best and most comprehensive walkthrough I’ve ever read that addresses these complexities.

How to customize lists (the modern way)

In a similar article for Piccalilli, Richard Rutter comprehensively showed us how to customize lists, although this one has some nuggets of what I can only assume is modern CSS. What’s symbols()? What’s @counter-style and extends? Richard walks you through everything.

A table with headings titled CSS and USE CASE detailing HTML list customizations. It lists the property list-style for basic bullet styles; the pseudo-element li::marker for coloring numbering; the function symbols() for Firefox-specific styles; the at-rule @counter-style for custom numbering systems; the descriptor extends for modifying existing systems; and the pseudo-element li::before for advanced marker positioning.
Source: Piccalilli.

Can’t get enough on counters? Juan Diego put together a comprehensive guide right here on CSS-Tricks.

How to create typescales using :heading

Safari Technology Preview 237 recently began trialing :heading/:heading(), as Stuart Robson explains. The follow-up is even better though, as it shows us how pow() can be used to write cleaner typescale logic, although I ultimately settled on the old-school <h1><h6> elements with a simpler implementation of :heading and no sibling-index():

:root {
  --font-size-base: 16px;
  --font-size-scale: 1.5;
}

:heading {
  /* Other heading styles */
}

/* Assuming only base/h3/h2/h1 */

body {
  font-size: var(--font-size-base);
}

h3 {
  font-size: calc(var(--font-size-base) * var(--font-size-scale));
}

h2 {
  font-size: calc(var(--font-size-base) * pow(var(--font-size-scale), 2));
}

h1 {
  font-size: calc(var(--font-size-base) * pow(var(--font-size-scale), 3));
}

Una Kravets introduced border-shape

Speaking of new features, border-shape came as a surprise to me considering that we already have — or will have — corner-shape. However, border-shape is different, as Una explains. It addresses the issues with borders (because it is the border), allows for more shapes and even the shape() function, and overall it works differently behind the scenes.

Source: Una Kravets.

modern.css wants you to stop writing CSS like it’s 2015

It’s time to start using all of that modern CSS, and that’s exactly what modern.css wants to help you do. All of those awesome features that weren’t supported when you first read about them, that you forgot about? Or the ones that you missed or skipped completely? Well, modern.css has 75 code snippets and counting, and all you have to do is copy ‘em.

Screenshot of a website titled modern.css showing browser compatibility filters and six code snippets, labeled with their category (e.g., SELECTORS or LAYOUT), difficulty level, topic, an example of outdated code to avoid, a browser support percentage, and a link to view the modern solution.

Kevin Powell also has some CSS snippets for you

And the commenters? They have some too!

Honestly, Kevin is the only web dev talker that I actually follow on YouTube, and he’s so close to a million followers right now, so make sure to hit ‘ol K-Po’s “Subscribe” button.

In case you missed it

Actually, you didn’t miss that much! Firefox 148 released the shape() function, which was being held captive by a flag, but is now a baseline feature. Safari Technology Preview 237 became the first to trial :heading. Those are all we’ve seen from our beloved browsers in the last couple of weeks (not counting the usual flurry of smaller updates, of course).

That being said, Chrome, Safari, and Firefox announced their targets for Interop 2026, revealing which Web Platform Features they intend to make consistent across all web browsers this year, which more than makes up for the lack of shiny features this week.

Also coming up (but testable in Chrome Canary now, just like border-shape) is the scrolled keyword for scroll-state container queries. Bramus talks about scrolled scroll-state queries here.

Remember, if you don’t want to miss anything, you can catch these Quick Hits as the news breaks in the sidebar of css-tricks.com.

See you in a fortnight!


What’s !important #6: :heading, border-shape, Truncating Text From the Middle, and More originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



from CSS-Tricks https://ift.tt/Evk78ce
via IFTTT

Yet Another Way to Center an (Absolute) Element

TL;DR: We can center absolute-positioned elements in three lines of CSS. And it works on all browsers!

.element {
  position: absolute;
  place-self: center; 
  inset: 0;
}

Why? Well, that needs a longer answer.

In recent years, CSS has brought a lot of new features that don’t necessarily allow us to do new stuff, but certainly make them easier and simpler. For example, we don’t have to hardcode indexes anymore:

<ul style="--t: 8">
  <li style="--i: 1"></li>
  <li style="--i: 2"></li>
  <!--  ...  -->
  <li style="--i: 8"></li>
</ul>

Instead, all this is condensed into the sibling-index() and sibling-count() functions. There are lots of recent examples like this.

Still, there is one little task that feels like we’ve doing the same for decades: centering an absolutely positioned element, which we usually achieve like this:

.element {
  position: absolute;
  top: 50%;
  left: 50%;
  
  translate: -50% -50%;
}

We move the element’s top-left corner to the center, then translate it back by 50% so it’s centered.

There is nothing wrong with this way — we’ve been doing it for decades. But still it feels like the old way. Is it the only way? Well, there is another not-so-known cross-browser way to not only center, but also easily place any absolutely-positioned element. And what’s best, it reuses the familiar align-self and justify-self properties.

Turns out that these properties (along with their place-self shorthand) now work on absolutely-positioned elements. However, if we try to use them as is, we’ll notice our element doesn’t even flinch.

/* Doesn't work!! */
.element {
  position: absolute;
  place-self: center; 
}

So, how do align-self and justify-self work for absolute elements? It may be obvious to say they should align the element, and that’s true, but specifically, they align it within its Inset-Modified Containing Block (IMCB). Okay… But what’s the IMCB?

Imagine we set our absolute element width and height to 100%. Even if the element’s position is absolute, it certainly doesn’t grow infinitely, but rather it’s enclosed by what’s known as the containing block.

The containing block is the closest ancestor with a new stacking context. By default, it is the html element.

We can modify that containing block using inset properties (specifically top, right, bottom, and left). I used to think that inset properties fixed the element’s corners (I even said it a couple of seconds ago), but under the hood, we are actually fixing the IMCB borders.

Diagram showing the CSS for an absolutely-positioning element with inset properties and how those values map to an element.

By default, the IMCB is the same size as the element’s dimensions. So before, align-self and justify-self were trying to center the element within itself, resulting in nothing. Then, our last step is to set the IMCB so that it is the same as the containing block.

.element {
  position: absolute;
  place-self: center; 
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

Or, using their inset shorthand:

.element {
  position: absolute;
  place-self: center; 
  inset: 0;
}

Only three lines! A win for CSS nerds. Admittedly, I might be cheating since, in the old way, we could also use the inset property and reduce it to three lines, but… let’s ignore that fact for now.

We aren’t limited to just centering elements, since all the other align-self and justify-self positions work just fine. This offers a more idiomatic way to position absolute elements.

Pro tip: If we want to leave a space between the absolutely-positioned element and its containing block, we could either add a margin to the element or set the container’s inset to the desired spacing.

What’s best, I checked Caniuse, and while initially Safari didn’t seem to support it, upon testing, it seems to work on all browsers!


Yet Another Way to Center an (Absolute) Element originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



from CSS-Tricks https://ift.tt/zrK5LWa
via IFTTT

Thursday, February 26, 2026

Clear today!



With a high of F and a low of 30F. Currently, it's 39F and Clear outside.

Current wind speeds: 3 from the West

Pollen: 0

Sunrise: February 26, 2026 at 07:28PM

Sunset: February 27, 2026 at 06:40AM

UV index: 0

Humidity: 27%

via https://ift.tt/4khcW86

February 27, 2026 at 10:02AM

Popover API or Dialog API: Which to Choose?

Choosing between Popover API and Dialog API is difficult because they seem to do the same job, but they don’t! After a bit lots of researc...