anchor-size(): Overlays That Match Their Trigger's Size

A select-style dropdown looks broken when its menu is narrower than the button that opened it, and awkward when it is much wider. Autocomplete suggestions should line up exactly with the text field above them. A focus highlight that slides between tabs should be exactly as wide as the active tab. All of these are sizing relationships between two elements that are not parent and child — the overlay usually lives in the top layer, far from its trigger in the DOM — and for years they required JavaScript to measure one element and copy the size onto another. Anchor positioning's anchor-size() function expresses the relationship directly in CSS. This page shows how to use it for widths and heights, how to combine it with minimums and maximums, and what it cannot do. It belongs to Anchor Positioning & Overlays in the Mastering Container Queries & Responsive Layouts guide.

Why size needs anchoring too

Anchor positioning solved the where of overlays: a popover can be placed below its trigger, aligned to its edge, and flipped when space runs out, with no measuring script. The how big was still missing. An overlay's percentage widths resolve against its containing block — for a top-layer popover, the viewport — not against the trigger, so width: 100% makes a dropdown as wide as the screen.

anchor-size() returns a dimension of the anchor element and can be used wherever an anchor-positioned element accepts a size. It is resolved during layout, so it follows the trigger through label changes, zoom, container resizes and font loading, exactly the cases a measure-once script gets wrong.

Three ways to size a dropdown A button labelled choose country above three alternative menus. With width 100 percent the menu spans the whole viewport. With auto width it shrinks to its longest item and is narrower than the button. With anchor-size width it exactly matches the button's width. Same trigger, three menu widths Choose country ▾ width: 100% → as wide as the viewport width: auto narrower than the button width: anchor-size(width) Only the last one lines up with the trigger's edges at every label length.

The complete implementation

The picker below opens a listbox-style popover exactly as wide as its trigger at minimum, able to grow for long options, and never taller than the space available.

Live demoMenu sized from its trigger with anchor-size()
Open the picker: the menu is at least as wide as the button and grows only for the long option. (Needs anchor positioning; elsewhere it opens centred.)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>anchor-size() menu</title>
<style>
  body { font: 15px/1.5 system-ui, sans-serif; margin: 2rem; }

  .picker {
    anchor-name: --picker;
    inline-size: 14rem;
    padding: 0.55rem 0.8rem;
    border: 1px solid #94a3b8;
    border-radius: 8px;
    background: #ffffff;
    font: inherit;
    text-align: start;
  }
  .picker:focus-visible { outline: 2px solid #4338ca; outline-offset: 2px; }

  .options {
    position-anchor: --picker;
    position-area: block-end span-inline-end;
    position-try-fallbacks: flip-block;
    margin: 0.25rem 0 0;

    /* Never narrower than the trigger, may grow for long labels,
       and capped so it cannot run off a small screen. */
    min-inline-size: anchor-size(width);
    max-inline-size: min(24rem, 90vw);

    /* Scroll inside when there are many options. */
    max-block-size: min(18rem, 60svh);
    overflow-y: auto;
    overscroll-behavior: contain;

    padding: 0.25rem;
    border: 1px solid #cbd5e1;
    border-radius: 8px;
    background: #ffffff;
    box-shadow: 0 10px 30px rgb(15 23 42 / 0.15);
  }

  .options button {
    display: block;
    inline-size: 100%;
    padding: 0.45rem 0.6rem;
    border: 0;
    border-radius: 6px;
    background: transparent;
    font: inherit;
    text-align: start;
  }
  .options button:hover, .options button:focus-visible { background: #eef2ff; outline: none; }
</style>
</head>
<body>
  <button class="picker" type="button" popovertarget="country-list" aria-haspopup="listbox">Choose country ▾</button>
  <div class="options" id="country-list" popover>
    <button type="button">Norway</button>
    <button type="button">New Zealand</button>
    <button type="button">Saint Vincent and the Grenadines</button>
  </div>
</body>
</html>

min-inline-size: anchor-size(width) is the heart of it. With the trigger at 14rem and short options, the menu is exactly 14rem and its edges line up with the button's. When "Saint Vincent and the Grenadines" needs more room, width: auto lets the menu grow until it hits the max-inline-size cap, after which the long label wraps. The popover remains in the top layer, so no ancestor's overflow can clip it.

The key technique: combine anchor-size() with min, max and clamp

anchor-size() returns a length, so it composes with every other length expression. That is where most of its practical value lies.

anchor-size() recipes width anchor-size width gives an exact match. min-width anchor-size width gives at least the anchor. width calc anchor-size width plus 2rem makes the overlay overhang both sides. width clamp 12rem, anchor-size width, 24rem keeps it within bounds even for tiny or huge anchors. anchor-size() inside ordinary length maths width: anchor-size(width) exact match min-width: anchor-size(width) at least the trigger width: calc(anchor-size(width) + 2rem) overhang both sides width: clamp(12rem, anchor-size(width), 24rem) bounded

The clamped form is the most defensive: icon-only triggers produce menus at least 12rem wide, and full-width triggers in wide layouts do not produce menus that span half the screen. The overhang form suits autocomplete panels that should extend slightly past a text field with a matching negative margin to stay centred. anchor-size() also accepts height, block and inline (and self-) keywords, so a side panel can match its anchor's height with min-block-size: anchor-size(height).

Patterns beyond dropdowns

Matching a trigger's size is the headline use, but the function shows up in several other interface patterns.

Autocomplete panels. A search field's suggestions panel should align with the field's edges exactly. inline-size: anchor-size(width) plus position-area: block-end places it flush beneath the input, and it tracks the input if the field grows on focus.

Sliding highlights. A single highlight element anchored to the active tab can take the tab's width and height, so it sits exactly behind the tab's label. Switching the active tab changes which element is the anchor — for example with an anchor-name that moves via :has(:checked) — and the highlight resizes to the new tab.

Side panels matching a row. An editing panel that opens beside a table row can match the row's height with min-block-size: anchor-size(height), so short rows get compact panels and tall rows get panels that align with both edges.

Callouts proportional to their target. An onboarding callout pointing at a large chart can be sized as a fraction of it, such as max-inline-size: calc(anchor-size(width) * 0.6), so it never covers more than part of the thing it describes.

In each case the alternative is a script that measures one element with getBoundingClientRect() and writes a pixel value onto another, then has to repeat the work on every resize, font load and content change. The CSS version is evaluated as part of layout, so it cannot fall out of date.

Limits worth knowing

anchor-size() is deliberately restricted. It can only be used on an anchor-positioned element, and only in sizing properties, margins and inset properties. It cannot size text (font-size: anchor-size(height) is invalid) or feed arbitrary properties such as translate. Those restrictions keep layout computable in one pass: allowing any property to depend on another element's size could create cycles.

Within those limits, one pattern deserves a caution. A menu wider than a narrow trigger near the right edge of the viewport can overflow the screen; position-try-fallbacks with a horizontal flip, or max-inline-size relative to the viewport, prevents it. The positioning side of that is in Anchor Positioning Fallbacks. For an indicator that animates between anchors — a tab highlight sliding from one tab to the next — changing the element's position-anchor and transitioning its inset and size properties gives a CSS-only sliding effect; keep it short and respect reduced motion, as described in Smooth Hover Effects Without JavaScript.

Accessibility notes

The example uses the popover attribute for behaviour — light dismiss, top layer, Escape to close — and buttons inside it for options. For a real single-select control, prefer the native <select> element, which is fully accessible and, in engines that support customisable selects, stylable with the same anchor techniques. If a custom listbox is unavoidable, it needs role="listbox", role="option" on items, aria-selected, and arrow-key navigation, which require script. CSS sizing is the easy part of a custom select; semantics and keyboard support are the hard part.

Browser support

Anchor positioning is supported in Chrome and Edge 125+, Firefox 147+ and Safari 26+, with position-area from Chrome and Edge 129 and position-try-fallbacks from Chrome and Edge 128. anchor-size() is part of the same specification and ships alongside anchor positioning in current Chromium releases; confirm its status in other engines before relying on it, and provide a fixed min-inline-size fallback declared before the anchor-size() declaration. The popover attribute is supported in Chrome and Edge 114+, Firefox 125+ and Safari 17+.

FAQ

What does anchor-size() do? It returns a dimension of the positioned element's anchor, such as its width or height, for use in sizing properties like width, min-width, max-height and inset-related margins. A dropdown can be exactly as wide as its trigger button with width: anchor-size(width).

Where can anchor-size() be used? In sizing properties — width, height, min and max sizes, and their logical equivalents — plus margins and inset properties on an anchor-positioned element. It cannot be used in arbitrary properties like font-size or on elements that are not anchor-positioned.

Does anchor-size() update when the anchor resizes? Yes. It is evaluated during layout, so if the trigger's width changes because its label changes or the container resizes, the positioned element's size follows on the next layout.

How do I make a menu at least as wide as its button but able to grow? Use min-width: anchor-size(width) and leave width as auto. The menu is never narrower than the button but can expand to fit longer items. Add a max-width, for example max-width: min(24rem, 90vw), so it cannot grow without limit.

Related articles

More pages in the same section.