Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

CSS  Module 17 – Forms, tables, popovers and anchor positioning

Anchor positioning for tooltips and menus

Tether tooltips and menus to their buttons with anchor-name, position-area and anchor(), flip them to stay on screen, and pair them with popovers.

  • Advanced
  • 30 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free: 30 minutes

What you will learn

  • Build a tooltip and a menu anchored to their buttons with anchor-name, position-area and anchor()
  • Use position-try-fallbacks and position-visibility to keep anchored boxes on screen or hide them
  • Combine anchors with popovers for accessible tooltips and menus that meet WCAG 2.2
  • Predict where an anchored tooltip lands near each edge of the viewport
  • Fix tooltips in repeated rows with anchor-scope

Before you start

What you’ll learn · Before you start

What you will learn

  • Build a tooltip and a menu anchored to their buttons with anchor-name, position-area and anchor()
  • Use position-try-fallbacks and position-visibility to keep anchored boxes on screen or hide them
  • Combine anchors with popovers for accessible tooltips and menus that meet WCAG 2.2
  • Predict where an anchored tooltip lands near each edge of the viewport
  • Fix tooltips in repeated rows with anchor-scope

Before you start

On this page

CSS anchor positioning attaches an absolutely positioned box, such as a tooltip or a menu, to another element, its anchor, without a script measuring the page. The anchor gets a name with anchor-name: --help; the box picks that name with position-anchor: --help and is placed with position-area, which chooses a cell of a 3 by 3 grid around the anchor (top, bottom span-right and so on), or with the anchor() function, which gives an exact edge of the anchor to top, left and the other inset properties. position-try-fallbacks lists other places to try when the box would overflow the screen, and position-visibility hides it when its anchor scrolls out of view. A popover opened by a popovertarget button is anchored to that button by itself. Firefox 147 and Safari 26 support nearly all of it, Chrome and Edge added the parts between versions 125 and 151, and Chromium 153, Firefox 155 and WebKit 26.6 placed every box in this lesson at the same pixel.

Naming an anchor and placing a box

A help button and its tooltip, above the button and centred on it:

A tooltip tethered to its button CSS · tooltip.css
/* A help tooltip tethered to its button: above it and centred, or below it when there is no room above. */
.help-button {
  anchor-name: --help;            /* names this box as an anchor */
}

.help-tip {
  position: absolute;             /* fixed works too; anchoring needs an absolutely positioned box */
  position-anchor: --help;        /* the default anchor for position-area and anchor() */
  position-area: top;             /* the cell above the anchor in the 3 by 3 grid around it */
  margin-block-end: 8px;          /* the gap between tooltip and button */
  position-try-fallbacks: flip-block;   /* if it overflows above, try the mirror image below */
  max-inline-size: 20em;
  padding: 0.5rem 0.75rem;
  border-radius: 0.375rem;
  background: #1a1a1a;
  color: #fff;                    /* 17.40:1 */
}

The tooltip needs position: absolute or fixed, because anchoring changes where an absolutely positioned box goes. position-anchor names its default anchor, which position-area and anchor() use. The anchor must be laid out before the box that refers to it, and the specification lists the conditions that guarantee this. The simplest way to meet them is position: fixed, or a popover, which is fixed in the top layer: the specification’s own tooltip example uses it and notes that the tooltip can then live anywhere in the document. With position: absolute, keep the anchor and the box in the same containing block, with the anchor either not absolutely positioned or earlier in the document.

position-area divides the space around the anchor into nine cells. The four lines in each direction are the edges of the box’s containing block and the edges of the anchor, so the outer cells grow with the space around the anchor:

The position-area grid: nine cells around an anchor, named top left, top, top right, left, center, right and the bottom row.top lefttop(top center)top rightleft(center left)the anchorcenterright(center right)bottom leftbottom(bottom center)bottom right

The nine cells that position-area names

Text description of the diagram

The diagram is a grid of three rows and three columns. The anchor is the middle cell, center.

The top row holds top left, top (top center) and top right. The middle row holds left (center left), the anchor itself and right (center right). The bottom row holds bottom left, bottom (bottom center) and bottom right.

The lines between the cells are the edges of the anchor, extended to the edges of the containing block, so the outer cells grow with the space around the anchor. position-area: top places a box in the top middle cell, and values such as span-right make it span two cells.

The chosen cells become the box’s containing block, and with position-area set, auto insets and margins compute to 0, so the box fits the area. The program works out the areas for an anchor 40 by 32 at (380, 280) in an 800 by 600 viewport:

The areas that position-area picks JavaScript · position_area.mjs
// The 3 by 3 grid that position-area picks from: four lines in each axis, from the containing block's edges and the
// anchor's edges (CSS Anchor Positioning 1). Each value names a region, and that region becomes the containing block
// of the anchored box. Change the anchor or the viewport and run it again.

const viewport = { width: 800, height: 600 };           // the containing block of a fixed or top-layer box
const anchor = { x: 380, y: 280, width: 40, height: 32 }; // the button

const xs = [0, anchor.x, anchor.x + anchor.width, viewport.width];   // left, anchor left, anchor right, right
const ys = [0, anchor.y, anchor.y + anchor.height, viewport.height]; // top, anchor top, anchor bottom, bottom

// A region as [first column, last column] and [first row, last row], 0 to 2.
const columns = { left: [0, 0], center: [1, 1], right: [2, 2], 'span-left': [0, 1], 'span-right': [1, 2], 'span-all': [0, 2] };
const rows = { top: [0, 0], center: [1, 1], bottom: [2, 2], 'span-top': [0, 1], 'span-bottom': [1, 2], 'span-all': [0, 2] };

function area(row, column) {
  const [c0, c1] = columns[column];
  const [r0, r1] = rows[row];
  return { x: xs[c0], y: ys[r0], width: xs[c1 + 1] - xs[c0], height: ys[r1 + 1] - ys[r0] };
}

const values = [
  ['top', 'top', 'center'],                       // one keyword: the other axis is centred
  ['bottom', 'bottom', 'center'],
  ['right', 'center', 'right'],
  ['top left', 'top', 'left'],
  ['bottom span-right', 'bottom', 'span-right'],
  ['span-all top', 'top', 'span-all'],
];
for (const [value, row, column] of values) {
  const a = area(row, column);
  console.log(`position-area: ${value.padEnd(18)} → area at (${a.x}, ${a.y}), ${a.width} × ${a.height}`);
}

Output

position-area: top                → area at (380, 0), 40 × 280
position-area: bottom             → area at (380, 312), 40 × 288
position-area: right              → area at (420, 280), 380 × 32
position-area: top left           → area at (0, 0), 380 × 280
position-area: bottom span-right  → area at (380, 312), 420 × 288
position-area: span-all top       → area at (0, 0), 800 × 280

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node position_area.mjs

A single keyword such as top takes the middle column, centred on the anchor; span-right takes the anchor’s column and the one to its right, so bottom span-right starts at the anchor’s left edge and opens to the right, the shape of a dropdown. For an exact edge, anchor() reads one side of the anchor: top: anchor(bottom) puts the box’s top at the anchor’s bottom, and inline-size: anchor-size(width) makes it as wide as the anchor.

Staying on screen: position-try-fallbacks

Near an edge, the box overflows its area. position-try-fallbacks: flip-block tells the browser to try the mirror image in the block direction, below instead of above, when the first position overflows. The test page moves the button to five places in an 800 by 600 viewport:

A tooltip that flips at the edge of the viewport HTML · tooltip-flip.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>A tooltip that flips when it reaches the edge of the viewport</title>
  <link rel="stylesheet" href="tooltip.css">
  <style>
    body { margin: 0; font: 16px/24px Arial, sans-serif; }
    .help-button { position: absolute; inline-size: 40px; block-size: 32px; }
    .help-tip { inline-size: 160px; block-size: 48px; box-sizing: border-box; }
    .edges { position: absolute; position-anchor: --help; top: anchor(bottom); left: anchor(left); inline-size: anchor-size(width); block-size: 4px; background: #1f4e8c; }
  </style>
</head>
<body>
  <button class="help-button" id="button" type="button" aria-describedby="tip">?</button>
  <div class="help-tip" id="tip" role="tooltip">Your card number is on the front.</div>
  <div class="edges" id="edges"></div>
  <script>
    const button = document.getElementById('button');
    const tip = document.getElementById('tip');
    const edges = document.getElementById('edges');
    const places = [['in the middle', 280, 380], ['near the top', 10, 380], ['near the bottom', 560, 380], ['at the left edge', 280, 0], ['at the right edge', 280, 760]];
    for (const [where, top, left] of places) {
      button.style.top = `${top}px`;
      button.style.left = `${left}px`;
      const a = button.getBoundingClientRect();
      const t = tip.getBoundingClientRect();
      const e = edges.getBoundingClientRect();
      const side = t.bottom <= a.top ? `above, ${a.top - t.bottom}px gap` : `below, ${t.top - a.bottom}px gap`;
      const inside = t.left >= 0 && t.top >= 0 && t.right <= innerWidth && t.bottom <= innerHeight;
      console.log(`button ${where} (${a.left}, ${a.top}): tooltip at (${t.left}, ${t.top}), ${side}, ${inside ? 'fully on screen' : 'off screen'}; anchor() bar at (${e.left}, ${e.top}), ${e.width}px wide`);
    }
  </script>
</body>
</html>
Button at Tooltip at, in all three engines Where
(380, 280), the middle (320, 224) above, 8px gap
(380, 10), near the top (320, 50) flipped below, 8px gap
(380, 560), near the bottom (320, 504) above, 8px gap
(0, 280), the left edge (0, 224) above, shifted right to stay on screen
(760, 280), the right edge (640, 224) above, shifted left to stay on screen

The flip mirrors the margin too, so the 8px gap moved to the other side of the tooltip. Sideways, the centred tooltip was shifted just enough to stay inside the viewport. And when it fitted neither above nor below, in a viewport only 100px tall, all three engines kept the first position and moved the tooltip into the viewport, over its own button. The bar placed with anchor() and anchor-size() sat exactly under the button and was exactly as wide, 40px. Other fallbacks are flip-inline (left and right swap), flip-start, a position-area value such as bottom, and named options written with @position-try.

Popovers as menus and tooltips

A popover opened by a button with popovertarget gets that button as its implicit anchor, so the popover needs neither anchor-name nor position-anchor. The popover also brings the behaviour a menu needs: it sits in the top layer, so no overflow: hidden clips it, and Escape and a click outside close it. An account menu:

An account menu: a popover anchored to its button HTML · account-menu.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>An account menu: a popover anchored to its button</title>
  <style>
    body { margin: 0; font: 16px/24px Arial, sans-serif; }
    .account { position: absolute; inline-size: 120px; block-size: 40px; }
    #b1 { top: 100px; left: 100px; }
    #b2 { top: 100px; left: 660px; }   /* near the right edge */
    #b3 { top: 520px; left: 100px; }   /* near the bottom edge */
    .menu {
      /* popovertarget makes the button the popover's implicit anchor: no anchor-name needed */
      position-area: bottom span-right;           /* below the button, starting at its left edge */
      position-try-fallbacks: flip-inline, flip-block;
      min-inline-size: anchor-size(width);        /* at least as wide as the button */
      inline-size: 200px;
      block-size: 120px;
      box-sizing: border-box;
      margin: 0;
      padding: 0.5rem;
    }
    .menu ul { margin: 0; padding: 0; list-style: none; }
    .menu a { display: block; padding: 0.25rem 0.5rem; }
    /* Without anchor positioning a popover opens in the middle of the screen; make it a sheet along the bottom. */
    @supports not (position-area: bottom) {
      .menu { inset: auto 0 0; inline-size: auto; block-size: auto; }
    }
  </style>
</head>
<body>
  <button class="account" id="b1" popovertarget="m1">Account</button>
  <button class="account" id="b2" popovertarget="m2">Account</button>
  <button class="account" id="b3" popovertarget="m3">Account</button>
  <nav class="menu" id="m1" popover="manual" aria-label="Account"><ul><li><a href="#orders">Orders</a></li><li><a href="#profile">Profile</a></li><li><a href="#sign-out">Sign out</a></li></ul></nav>
  <nav class="menu" id="m2" popover="manual" aria-label="Account"><ul><li><a href="#orders">Orders</a></li><li><a href="#profile">Profile</a></li><li><a href="#sign-out">Sign out</a></li></ul></nav>
  <nav class="menu" id="m3" popover="manual" aria-label="Account"><ul><li><a href="#orders">Orders</a></li><li><a href="#profile">Profile</a></li><li><a href="#sign-out">Sign out</a></li></ul></nav>
  <script>
    // The test opens all three at once, so they are manual popovers here; a real menu is popover="auto".
    const box = (el) => { const r = el.getBoundingClientRect(); return `(${r.left}, ${r.top}) ${r.width}x${r.height}`; };
    window.afterActions = () => {
      for (const n of [1, 2, 3]) {
        const menu = document.getElementById(`m${n}`);
        console.log(`#b${n} at ${box(document.getElementById(`b${n}`))}: menu ${menu.matches(':popover-open') ? 'open' : 'closed'} at ${box(menu)}`);
      }
    };
  </script>
</body>
</html>
Button Menu, in all three engines
at (100, 100) (100, 140): below, from the button’s left edge
at (660, 100), near the right edge (580, 140): flipped to end at the button’s right edge
at (100, 520), near the bottom (100, 400): flipped above

A popover’s default style centres it with inset: 0 and margin: auto; position-area turns those into 0, so the menu sat exactly against its button. A list of links like this one is a disclosure, not an ARIA menu: role="menu" promises arrow-key navigation that you would then have to write. The @supports not (position-area: bottom) block turns the popover into a sheet along the bottom of the screen in an engine without anchor positioning, which is better than the centred box it would get by default.

A tooltip is harder, because it must appear on hover and focus as well as on demand. WCAG 2.2’s success criterion 1.4.13 asks for three things of content that appears on hover or focus: it can be dismissed without moving the pointer or focus (Escape), the pointer can move onto it without it disappearing, and it stays until the hover or focus ends or the person dismisses it. A CSS-only :hover tooltip fails the first. Show it from a small script on pointerenter and focus, hide it on pointerleave (with a short delay so the pointer can reach it), blur and Escape, give the tooltip role="tooltip" and point to it from the button with aria-describedby. popover="hint" fits tooltips well, because it doesn’t close an open auto menu; Chrome 151 and Firefox 153 support it.

Tooltips in repeated rows: anchor-scope

A list where every row has the same info button and tooltip uses one anchor name many times. When several elements share a name, the specification has the box use the last one in the document:

One anchor name in every row of a list HTML · list-tips.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>One anchor name in every row of a list</title>
  <style>
    body { margin: 0; font: 16px/24px Arial, sans-serif; }
    li { block-size: 48px; }
    .info { anchor-name: --info; inline-size: 32px; block-size: 32px; }
    .tip { position: absolute; position-anchor: --info; position-area: right; inline-size: 100px; block-size: 24px; margin: 0; }
    #scoped li { anchor-scope: --info; }   /* each row sees only its own --info */
  </style>
</head>
<body>
  <ul id="plain">
    <li><button class="info" type="button">i</button><span class="tip">Tip 1</span></li>
    <li><button class="info" type="button">i</button><span class="tip">Tip 2</span></li>
    <li><button class="info" type="button">i</button><span class="tip">Tip 3</span></li>
  </ul>
  <ul id="scoped">
    <li><button class="info" type="button">i</button><span class="tip">Tip 1</span></li>
    <li><button class="info" type="button">i</button><span class="tip">Tip 2</span></li>
    <li><button class="info" type="button">i</button><span class="tip">Tip 3</span></li>
  </ul>
  <script>
    for (const list of document.querySelectorAll('ul')) {
      const rows = [...list.querySelectorAll('li')].map((li) => {
        const button = li.querySelector('.info').getBoundingClientRect();
        const tip = li.querySelector('.tip').getBoundingClientRect();
        return `button at ${button.top} → tip at ${tip.top}`;
      });
      console.log(`#${list.id}: ${rows.join('; ')}`);
    }
  </script>
</body>
</html>
  • Without anchor-scope: the buttons were at 16, 64 and 112px, and all three tooltips at 116px, beside the last button.
  • With anchor-scope: --info on each row: the buttons were at 176, 224 and 272px, and the tooltips at 180, 228 and 276px, each beside its own button.

All three engines agreed. anchor-scope limits the name to the element and its descendants, so each row’s tooltip can only see its own button. anchor-scope: all scopes every name at once.

When the anchor scrolls away

A tooltip often sits outside a scrolling list while its anchor is a row inside it. When the row scrolls out of the list’s visible area, the tooltip would keep pointing at nothing:

A tooltip whose anchor scrolls out of view HTML · scroll-hide.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>A tooltip whose anchor scrolls out of view</title>
  <style>
    body { margin: 0; font: 16px/24px Arial, sans-serif; }
    .list { margin: 100px 0 0 100px; inline-size: 300px; block-size: 200px; overflow: auto; border: 1px solid; }
    .list p { margin: 0; block-size: 40px; }
    .row { anchor-name: --row; background: #e8eef6; }
    .tip { position: absolute; position-anchor: --row; position-area: right; inline-size: 120px; block-size: 40px; margin: 0; }
    #always { position-visibility: always; }    /* stays on screen, pointing at nothing */
    #default { position-area: right span-bottom; }   /* position-visibility is left at its initial value */
  </style>
</head>
<body>
  <div class="list" id="list">
    <p>Row 1</p><p class="row">Row 2, the anchor</p><p>Row 3</p><p>Row 4</p><p>Row 5</p>
    <p>Row 6</p><p>Row 7</p><p>Row 8</p><p>Row 9</p><p>Row 10</p>
  </div>
  <div class="tip" id="always">always</div>
  <div class="tip" id="default">initial value</div>
  <script>
    const list = document.getElementById('list');
    console.log(`position-visibility of #default: ${getComputedStyle(document.getElementById('default')).positionVisibility}`);
    list.scrollTop = 100;      // Row 2 is now above the list's visible area, still inside the viewport
    requestAnimationFrame(() => requestAnimationFrame(() => {
      const row = document.querySelector('.row').getBoundingClientRect();
      console.log(`after scrolling: the anchor's top is at ${row.top}px, above the list's visible top at ${list.getBoundingClientRect().top + 1}px`);
    }));
  </script>
</body>
</html>

All three engines computed position-visibility: anchors-visible for the tooltip that sets nothing, and after the list scrolled by 100px, putting the anchor’s top at 41px against the list’s visible top at 101px, screenshots of all three showed only the tooltip with position-visibility: always, floating beside an empty list. So the default already hides an anchored box with its anchor; set always only when the box should stay. The specification now spells the values anchor-visible and anchor-valid, which only Safari 27 parses so far, while the plural anchors-visible works in all three engines.

Support today

web-features rates anchor positioning as a whole as limited availability. Its data lists anchor-name, anchor() and @position-try from Chrome and Edge 125, Firefox 147 and Safari 26, position-area from Chrome 129, anchor-scope from Chrome 131, a popover’s implicit anchor from Chrome 133, and position-anchor in its current form from Chrome and Firefox 151 and Safari 27. The newly renamed anchor-valid and anchor-visible values, so far only in Safari 27, are what keep the feature as a whole out of Baseline. Anchor positioning is also an Interop 2026 focus area, so engines are still fixing details. For older engines, wrap the anchored placement in @supports (position-area: top) or give a fallback with @supports not, as the menu does, and test the result where it matters.

HTML, CSS & JS Playground (Online Editor) Paste tooltip-flip.html, then move the button to each edge and watch the tooltip flip.

Practise: place the tooltip

Predict where the tooltip goes, with the rules that all three engines followed:

Exercise · Medium · JavaScript

Place a tooltip the way anchor positioning does

The lesson's tooltip uses position-area: top, an 8px gap and position-try-fallbacks: flip-block. Write placeTooltip(anchor, size, viewport, gap = 8) in place.mjs and export it, so that it predicts where the browser puts the tooltip. anchor is { x, y, width, height }, size is { width, height } and viewport is { width, height }, all in CSS pixels. Return { x, y, side }, the tooltip's top-left corner and where it ended up. These are the rules all three engines followed in the lesson's runs:

  • Horizontally the tooltip is centred on the anchor, then shifted just enough to stay inside the viewport: its left edge never goes below 0 and its right edge never past the viewport's width.
  • Above the anchor, with gap pixels between them, is the first choice: side is 'above'.
  • If the tooltip would stick out of the top of the viewport there, it flips below the anchor, with the same gap: side is 'below'.
  • If it doesn't fit below either, the browser keeps the first position and shifts the tooltip into the viewport, on top of its anchor: side is 'over'.

The sample tests import the function from place.mjs and run in your browser.

Starter code · place.mjs

/** Where a tooltip with position-area: top and position-try-fallbacks: flip-block ends up. */
export function placeTooltip(anchor, size, viewport, gap = 8) {
  // Replace this line with your code.
  return { x: anchor.x, y: anchor.y, side: 'above' };
}
The sample tests · place.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { placeTooltip } from './place.mjs';

const size = { width: 160, height: 48 };
const screen = { width: 800, height: 600 };
const button = (x, y) => ({ x, y, width: 40, height: 32 });

test('above the anchor and centred, when there is room', () => {
  assert.deepEqual(placeTooltip(button(380, 280), size, screen), { x: 320, y: 224, side: 'above' });
  assert.deepEqual(placeTooltip(button(380, 560), size, screen), { x: 320, y: 504, side: 'above' });
});

test('flipped below when it would stick out of the top', () => {
  assert.deepEqual(placeTooltip(button(380, 10), size, screen), { x: 320, y: 50, side: 'below' });
  assert.deepEqual(placeTooltip(button(380, 56), size, screen), { x: 320, y: 0, side: 'above' });
  assert.deepEqual(placeTooltip(button(380, 55), size, screen), { x: 320, y: 95, side: 'below' });
});

test('shifted sideways to stay inside the viewport', () => {
  assert.deepEqual(placeTooltip(button(0, 280), size, screen), { x: 0, y: 224, side: 'above' });
  assert.deepEqual(placeTooltip(button(760, 280), size, screen), { x: 640, y: 224, side: 'above' });
  assert.deepEqual(placeTooltip(button(10, 4), size, { width: 800, height: 100 }), { x: 0, y: 44, side: 'below' });
});

test('over the anchor when it fits neither above nor below', () => {
  assert.deepEqual(placeTooltip(button(380, 30), size, { width: 800, height: 100 }), { x: 320, y: 0, side: 'over' });
  assert.deepEqual(placeTooltip(button(380, 30), size, { width: 800, height: 118 }), { x: 320, y: 70, side: 'below' });
});

test('the gap can change', () => {
  assert.deepEqual(placeTooltip(button(380, 280), size, screen, 0), { x: 320, y: 232, side: 'above' });
  assert.deepEqual(placeTooltip(button(380, 10), size, screen, 12), { x: 320, y: 54, side: 'below' });
});
A hint

Start with x = anchor.x + anchor.width / 2 - size.width / 2, then clamp it with Math.min and Math.max between 0 and viewport.width - size.width. For the vertical position, work out the top edge above the anchor (anchor.y - gap - size.height) and below it (anchor.y + anchor.height + gap), and test them in that order.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 A tooltip should sit above its button. Which declarations does it need, with anchor-name: --help on the button?

    Choose every answer that is right.

    Show the answer to question 1

    Answer:

    • position-anchor: --help
    • position: absolute (or fixed)
    • position-area: top

    Anchoring works on absolutely positioned boxes, position-anchor names the default anchor, and position-area places the box in a cell of the grid around it. A popover opened by a popovertarget button doesn't even need position-anchor: the button becomes its implicit anchor.

  2. Question 2 of 6 An anchor is 40 by 32 at (380, 280) in an 800 by 600 viewport. How tall is the area that position-area: top gives the tooltip, in pixels?

    Type a number.

    Show the answer to question 2

    Answer: 280 px

    The grid's lines are the viewport's edges and the anchor's edges, so the top row runs from 0 to the anchor's top edge at 280. The lesson's program prints the area as (380, 0), 40 × 280: as wide as the anchor and as tall as the space above it.

  3. Question 3 of 6 A tooltip has position-area: top, margin-block-end: 8px and position-try-fallbacks: flip-block. Its button is 10px from the top of the viewport. Where does the tooltip go?

    Choose one answer.

    Show the answer to question 3

    Answer: Below the button, still 8px away from it

    There is no room above, so the browser tries the mirror image below. flip-block mirrors the margins too, so the 8px gap stays between the button and the tooltip: in all three engines the tooltip sat at 50px, 8px under the button's bottom edge at 42px.

  4. Question 4 of 6 Every row of a list has an info button with anchor-name: --info and a tooltip with position-anchor: --info. Where do the tooltips go?

    Choose one answer.

    Show the answer to question 4

    Answer: All to the last button with that name, unless each row has anchor-scope: --info

    When several elements share an anchor name, a box uses the last one in the document. In the lesson's run all three tooltips of the plain list stacked at the third button. anchor-scope: --info on each row limits the name to that row, so each tooltip found its own button.

  5. Question 5 of 6 A tooltip outside a scrolling list is anchored to a row inside it. The row scrolls out of the list's visible area. What happens by default?

    Choose one answer.

    Show the answer to question 5

    Answer: The tooltip is hidden: position-visibility starts at anchors-visible

    All three engines computed position-visibility: anchors-visible for a box that sets nothing, so the tooltip disappeared once its anchor was scrolled out of view, while a tooltip with position-visibility: always stayed on screen pointing at nothing.

  6. Question 6 of 6 A help tooltip appears when its button is hovered or focused. What does WCAG 2.2's success criterion 1.4.13 ask of it?

    Choose every answer that is right.

    Show the answer to question 6

    Answer:

    • People can dismiss it without moving the pointer or focus, for example with Escape
    • It stays visible until the hover or focus ends, it is dismissed, or it is no longer valid
    • The pointer can move onto the tooltip without it disappearing

    Dismissible, hoverable and persistent are the three conditions. Anchor positioning only places the box; the behaviour comes from HTML and a little script, and the button points to the tooltip with aria-describedby.

Interview questions

Fresher · What is the difference between position-area and anchor()?

Both place an absolutely positioned box against its anchor. position-area picks one or more cells of a 3 by 3 grid around the anchor, whose lines are the anchor’s edges and the containing block’s edges: position-area: top puts the box in the cell above the anchor, centred on it, and bottom span-right below it, starting at its left edge. The chosen cells become the box’s containing block, and auto insets and margins compute to 0. anchor() is a function used in an inset property that returns the position of one edge of the anchor, such as top: anchor(bottom), so you control each edge exactly and can use several anchors at once. position-area is the shorter choice for tooltips and menus; anchor() suits connectors and boxes that span between two anchors.

Key takeaways

  • anchor-name names the anchor; position-anchor, position-area and anchor() place an absolutely positioned box against it.
  • position-area picks cells of a 3 by 3 grid; the cells become the containing block and auto margins become 0.
  • position-try-fallbacks: flip-block flips the box below, gap included; boxes also shift sideways to stay on screen.
  • A popovertarget popover is anchored to its button with no anchor name, and stays above every clipping ancestor.
  • Repeated names resolve to the last element; scope them with anchor-scope.
  • position-visibility already hides a box whose anchor scrolls out of view.
  • Tooltips still need script for hover and focus, Escape to dismiss, role="tooltip" and aria-describedby.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.