demo · v138

Menu chevron flip

A dropdown menu where the chevron, the heading, and the colour palette of the popover automatically reflect which try-fallback the anchor engine actually picked. Scroll the viewport so each menu hits a different edge — the popover tells you exactly which fallback landed without any JavaScript layout-querying.

Behind a flag: @container anchored(…) ships in Chrome 138 with positioning-fallback support enabled. If your browser doesn't support it, the popover renders with the default styling — the chevron and headline won't update.
probing @container anchored() support…

The popovers are placed with position-anchor + position-area: block-end span-inline-end (below + to the right of the anchor). When that's blocked by the viewport edge, the browser walks position-try-fallbacks: flip-block (try above), then flip-inline (try to the left), then both. The container query reads which one the engine settled on.

Scroll the panel below. Each menu's popover updates its colour and chevron as it gets pushed past viewport edges.

↓ Plenty of space below this menu. Popover renders with the default block-end span-inline-end placement.

↑ Now the menu is near the bottom of the viewport — the popover ought to flip up.

→ This menu sits near the right edge. The popover should flip toward the start.

↖ This last menu pushes against bottom and right at once — the corner fallback fires.

▼ default — placed below
▲ flip-block — placed above
◀ flip-inline — placed at start
↖ corner — both flipped

The CSS

.menu-pop {
  position: fixed;
  position-anchor: --a1;
  position-area: block-end span-inline-end;
  position-try-fallbacks:
    flip-block,
    flip-inline,
    flip-block flip-inline;
}

/* This is the new bit. */
@container anchored(flip-block) {
  .menu-pop .where::before { content: "▲ placed above"; }
  .menu-btn .chev { transform: rotate(180deg); }
}
@container anchored(flip-inline) {
  .menu-pop .where::before { content: "◀ placed at start"; }
  .menu-btn .chev { transform: rotate(-90deg); }
}
@container anchored(flip-block flip-inline) {
  .menu-pop .where::before { content: "↖ placed at corner"; }
  .menu-btn .chev { transform: rotate(135deg); }
}

Note that the container query matches an anchored element — every popover acts as its own implicit container. No container-type needed; anchored() targets the anchored element itself.

What's happening

  1. The popover declares an ordered list of fallbacks via position-try-fallbacks.
  2. The layout engine walks the list, picks the first that fits, and the popover takes that placement.
  3. @container anchored(flip-block) matches when the engine ended up using flip-block. Same for the others.
  4. The popover's headline, colour, and chevron rotation all switch via CSS only — no getBoundingClientRect(), no ResizeObserver, no JS.
  5. This was the missing primitive: previously, you knew the popover had flipped (because you watched the browser do it), but you had no way to ask why in CSS.

see also