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.
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 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 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 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
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
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:
<!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:
<!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:
<!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: --infoon 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:
<!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.
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
gappixels between them, is the first choice:sideis'above'. - If the tooltip would stick out of the top of the viewport there, it flips below the anchor, with the same gap:
sideis'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:
sideis'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.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
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.
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-namenames the anchor;position-anchor,position-areaandanchor()place an absolutely positioned box against it.position-areapicks cells of a 3 by 3 grid; the cells become the containing block and auto margins become 0.position-try-fallbacks: flip-blockflips the box below, gap included; boxes also shift sideways to stay on screen.- A
popovertargetpopover 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-visibilityalready hides a box whose anchor scrolls out of view.- Tooltips still need script for hover and focus, Escape to dismiss,
role="tooltip"andaria-describedby.
References
- CSS Anchor Positioning Module Level 1 (W3C CSS Working Group)
- CSS Anchor Positioning Module Level 1: the position-area grid (W3C CSS Working Group)
- CSS Anchor Positioning Module Level 1: finding an anchor and anchor-scope (W3C CSS Working Group)
- CSS Anchor Positioning Module Level 1: position-try-fallbacks (W3C CSS Working Group)
- CSS Anchor Positioning Module Level 1: position-visibility (W3C CSS Working Group)
- CSS Anchor Positioning Module Level 1: accessibility implications (W3C CSS Working Group)
- HTML Standard: the popover attribute and its implicit anchor (WHATWG)
- Understanding WCAG 2.2: Content on Hover or Focus (W3C)
- WAI-ARIA Authoring Practices Guide: Tooltip Pattern (W3C)
- WAI-ARIA Authoring Practices Guide: Disclosure Pattern (W3C)
- CSS anchor positioning (MDN Web Docs) (Mozilla)
- Fallback options and conditional hiding for overflow (MDN Web Docs) (Mozilla)
- Web features explorer: anchor positioning (W3C WebDX Community Group)
- Interop 2026 focus areas (web-platform-tests Interop team)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress