Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Thu, 08 Jan 2026 17:13:44 +0000 en-US hourly 1 https://wordpress.org/?v=6.9 225069128 Popover Context Menus with Anchor Positioning https://frontendmasters.com/blog/popover-context-menus-with-anchor-positioning/ https://frontendmasters.com/blog/popover-context-menus-with-anchor-positioning/#comments Thu, 08 Jan 2026 17:13:43 +0000 https://frontendmasters.com/blog/?p=8194 Tooltips are the classic use case for anchor positioning in CSS. You click a thing, and right next to it another thing opens up. That other thing could also be a menu. I mean — what’s a context menu aside from a tooltip with links, right?

I’ll illustrate this with some appropriately common and vague components, like a “Kebab” menu button within a “Card” component:

This video comes from the complete demo for this post.

The positioning of that menu happens via the magic of anchor positioning. Lemme show you all the HTML first, as that is interesting in it’s own right.

The HTML for the Menu Button & Menu

We’ll use a <button> to toggle the menu (duh!) and put the attributes on it to set up an invoker. Invokers are super cool, allowing us to open/close the menu without any JavaScript at all. Just these declarative instructions will do. The interestfor attribute is extra-new, allowing popover="hint" to work meaning we can even open the popover on hover/focus (which is kinda amazing that it can be done in HTML alone).

<button
  class="menu-toggle"

  <!-- interest invoker! -->
  command="toggle-popover"
  <!-- connect to id of the popover menu -->
  commandfor="card-menu"
  <!-- for "hint" type (open menu on hover) -->
  interestfor="card-menu"

  style="anchor-name: --card;"
>
  <span class="screenreader-only">Open Menu</span>
  <span class="aria-hidden">•••</span>
</button>

<!-- 
  These two things ⬆️⬇️ could be anywhere 
  in DOM, thanks to anchor positioning,
  but it's still probably smart to keep 
  them nearby for tabbing accessibility.
-->

<menu 
  popover="hint" 
  id="card-menu" 
  style="position-anchor: --card;"
>
  <li><button>This</button></li>
  <li><button>Little</button></li>
  <li><button>Piggy</button></li>
  <li><button>Went</button></li>
  <li><button>to Market</button></li>
</menu>

Did you know the <menu> tag is just an <ul>? I love that.

I put the style tag on both of those elements, naming an anchor and styling against that named anchor, because “in the real world” I think that will be extremely common. The rest of the CSS can happen in a CSS file, but those things will likely be in the HTML because a page is likely to have lots of context menus and they will all need –unique-names.

The typical/ideal situation.

The CSS for the Menu

The CSS for the button isn’t terribly interesting aside from the named anchor we already gave it, so we’ll skip over that (just make sure it has nice hover/focus styles).

The CSS for the menu is much more interesting because…

  • We get to use anchor positioning to put it right where we want, including fallbacks.
  • We can animate the opening and closing.

The menu is going to be properly closed to start, with display: none;. Normally this means we can’t animate it, but with modern CSS these days, we can!

menu {
  /* Already has this from the HTML */
  /* position-anchor: --card; */

  /* Put it in place */
  position-area: block-start span-inline-start;

  transition-property: translate, opacity, display, overlay;
  transition-duration: 0.45s;
  /* Important for swapping timing of when the display properly flips */
  transition-behavior: allow-discrete;

  /* OUTGOING style */
  opacity: 0;
  display: none;
  translate: 10px 0;

  &:popover-open {
    /* MENU OPEN style */
    opacity: 1;
    display: block;
    translate: 0 0;

    /* INCOMING style */
    @starting-style {
      opacity: 0;
      translate: 0 -20px;
    }
  }

That position-area is decently complex all in itself. You might think there are basically eight places to put it (four sides and four corners) but there is really sixteen as the direction it “spans” can be sort of reversed from the natural flow. Ours does!

The Positioning Fallbacks

This is actually why I originally made this demo and why I’m writing this article. I found the anchor positioning fallbacks to be very mind-bending. Now that I have the right solution in place, it seems more obvious, but it’s been a mental journey for me getting here 😵‍💫.

Why fallbacks? If we didn’t have them, we’d risk opening our menus in into areas where they are unusable. Like if the menu button was toward the top of the screen, our initial position-area has the menu opening upwards, and we’d be hosed:

A kebab menu button opened, displaying menu items including 'Went' and 'to Market', set against a colorful, blurred background.
The top edge here is the top of the browser window, and the menu options are cut off. We can’t read them or click them.

In this situation, what we want to happen is for the menu to open downwards instead. That’s pretty straightforward, we add:

menu {
  ...

  position-try: flip-block;
}

This is basically saying: if you need to and there is space, go ahead and flip the position in the block direction. So from top to bottom for us. So if we had that situation where it’s cutting off on top, it’ll flip to the bottom if it can. That works great:

But top and bottom aren’t the only places a menu like this could get cut off. The left and right edges of the browser window are just as plausible. So in my mind, we’d just do this:

position-try: flip-inline flip-block;

My mind: OK you have everything you need now, if you need to flip in the block direction, you’ve been given permission, and if you need to flip in the inline direction, you’ve also been given permission:

Spoiler: This is now how it works.

What I’ve written above actually says: If you’re getting cut off with your initial positioning, try flipping in both the inline and block directions and if and only if that is better, do it.

That’s not really what I want.

What I want is: If the initial positioning is cut off, try flipping in the block direction, if that doesn’t work, try flipping in the inline direction, if that doesn’t work, then try flipping in both directions. What we need to that is this:

position-try: flip-block, flip-inline, flip-block flip-inline;

The Most Useful position-try Incantation

I’m going to say this again, because I’ve always thought this is just how it should work naturally without even having to ask for it (but it doesn’t). If you want positioning fallbacks that attempt to flip all the different directions to best fit, do this:

.good-positioning-fallbacks {
  position-try: flip-block, flip-inline, flip-block flip-inline;
}

Behold, a menu that works no matter where it shows up in a browser window:

We Could Write it Longhand

There is also a special @position-try syntax which we could use to do the exact same thing, like…

position-try-fallbacks: 
  --flip-block, 
  --flip-inline, 
  --flip-both;

@position-try --flip-block {
  position-area: block-end span-inline-start;
}

@position-try --flip-inline {
  position-area: block-start span-inline-end;
}

@position-try --flip-both {
  position-area: block-end span-inline-end;
}

The advantage to doing it this way is those @rule blocks allow us to do more stuff when they “hit”, for example adjust the margin differently or change alignment. That’s certainly nice if you need it!

Demo

Here’s the demo page. Feel free to see a full page preview.

]]>
https://frontendmasters.com/blog/popover-context-menus-with-anchor-positioning/feed/ 1 8194
CSS offset and animation-composition for Rotating Menus https://frontendmasters.com/blog/css-offset-and-animation-composition-for-rotating-menus/ https://frontendmasters.com/blog/css-offset-and-animation-composition-for-rotating-menus/#respond Wed, 17 Sep 2025 13:49:38 +0000 https://frontendmasters.com/blog/?p=7147 Circular menu design exists as a space-saver or choice, and there’s an easy and efficient way to create and animate it in CSS using offset and animation-composition. Here are some examples (click the button in the center of the choices):

I’ll take you through the second example to cover the basics.

The Layout

Just some semantic HTML here. Since we’re offering a menu of options, a <menu> seems appropriate (yes, <li> is correct as a child!) and each button is focusable.

<main>
  <div class="menu-wrapper">
    <menu>
      <li><button>Poland</button></li>
      <li><button>Brazil</button></li>
      <li><button>Qatar</button></li>
      <!-- etc. -->
    </menu>
    <button class="menu-button" onclick="revolve()">See More</button>
  </div>
</main>

Other important bits:

The menu and the menu button (<button id="menu-button">) are the same size and shape and stacked on top of each other.

Half of the menu is hidden via overflow: clip; and the menu wrapper being pulled upwards.

main { 
  overflow: clip;
}
.menu-wrapper { 
  display: grid;
  place-items: center;
  transform: translateY(-129px);
  menu, .menu-button {
    width: 259px;
    height: 129px;
    grid-area: 1 / 1;
    border-radius: 50%;
  }
}

Set the menu items (<li>s) around the <menu>’s center using offset.

menu {
    padding: 30px;
    --gap: 10%; /* The in-between gap for the 10 items */
}
li {
  offset: padding-box 0deg;
  offset-distance: calc((sibling-index() - 1) * var(--gap)); 
  /* or 
    &:nth-of-type(2) { offset-distance: calc(1 * var(--gap)); }
    &:nth-of-type(3) { offset-distance: calc(2 * var(--gap)); }
    etc...
  */
}

The offset (a longhand property) positions all the <li> elements along the <menu>’s padding-box that has been set as the offset path.

The offset CSS shorthand property sets all the properties required for animating an element along a defined path. The offset properties together help to define an offset transform, a transform that aligns a point in an element (offset-anchor) to an offset position (offset-position) on a path (offset-path) at various points along the path (offset-distance) and optionally rotates the element (offset-rotate) to follow the direction of the path. — MDN Web Docs

The offset-distance is set to spread the menu items along the path based on the given gap between them (--gap: 10%).

ItemsInitial value of offset-distance
10%
210%
320%

The Animation

@keyframes rev1 { 
  to {
    offset-distance: 50%;
  } 
}

@keyframes rev2 { 
  from {
    offset-distance: 50%;
  } 
  to {
    offset-distance: 0%;
  } 
}

Set two @keyframes animations to move the menu items halfway to the left, clockwise, (rev1), and then from that position back to the right (rev2)

li {
  /* ... */
  animation: 1s forwards;
  animation-composition: add; 
}

Set animation-time (1s) and animation-direction (forwards), and animation-composition (add) for the <li> elements

Even though animations can be triggered in CSS — for example, within a :checked state — since we’re using a <button>, the names of the animations will be set in the <button>’s click handler to trigger the animations.

By using animation-composition, the animations are made to add, not replace by default, the offset-distance values inside the @keyframes rulesets to the initial offset-distance values of each of the <li>.

ItemsInitial Valueto
10%(0% + 50%) 50%
210%(10% + 50%) 60%
320%(20% + 50%) 70%
rev1 animation w/ animation-composition: add
Itemsfromback to Initial Value
1(0% + 50%) 50%(0% + 0%) 0%
2(10% + 50%) 60%(10% + 0%) 10%
3(20% + 50%) 70%(20% + 0%) 20%
rev2 animation w/ animation-composition: add

Here’s how it would’ve been without animation-composition: add:

ItemsInitial Valueto
10%50%
210%50%
320%50%

The animation-composition CSS property specifies the composite operation to use when multiple animations affect the same property simultaneously.

MDN Web Docs

The Trigger

const LI = document.querySelectorAll('li');
let flag = true;
function revolve() {
  LI.forEach(li => li.style.animationName = flag ? "rev1" : "rev2");
  flag = !flag;
}

In the menu button’s click handler, revolve(), set the <li> elements’ animationName to rev1 and rev2, alternatively.

Assigning the animation name triggers the corresponding keyframes animation each time the <button> is clicked.

Using the method covered in this post, it’s possible to control how much along a revolution the elements are to move (demo one), and which direction. You can also experiment with different offset path shapes. You can declare (@keyframes) and trigger (:checked, :hover, etc.) the animations in CSS, or using JavaScript’s Web Animations API that includes the animation composition property.

]]>
https://frontendmasters.com/blog/css-offset-and-animation-composition-for-rotating-menus/feed/ 0 7147