Formatting contexts and CSS positioningPositioning and containing blocks

100%

Positioning and containing blocks.

Every offset you write, top 8px or right 0, is measured from some box. CSS calls that box the containing block, and finding it is a short walk up the tree with a few surprising stops. Get the walk right and dropdowns stop getting cut off, toasts stay in their corner and sticky headers stick.

Intermediate37 minUpdated 2 Oct 2026

Builds on Formatting contexts.

The idea.

Positioning is two questions: does this box keep its place in the flow, and what is it measured from? The second answer is called its containing block, and most layout bugs with dropdowns, toasts and sticky headers come from getting it wrong.

Open a task card on the Slatework board and press More. The menu is written as position: absolute with top: 100% and right: 0, which reads as 'just under the button, flush with its right edge'. That sentence hides a question. 100% of what, and the right edge of what? CSS answers with the containing block: a rectangle chosen by walking up from the menu until an ancestor qualifies. For an absolutely positioned box the walk stops at the first ancestor whose position is anything but static, and the offsets are measured from that ancestor's padding box.

The same walk decides two more things that look unrelated. Whether a scrolling or clipping ancestor may cut the menu off depends on whether the menu's containing block sits inside that ancestor. And whether position: fixed really means 'fixed to the window' depends on whether some ancestor carries a transform, a filter or containment, because any of those quietly turns that ancestor into the containing block for fixed descendants too.

So in Slatework the menu is cut off at the bottom of the card, the 'Card moved' toast drifts up the page when you scroll, and the column header refuses to stick. Three symptoms, one habit that fixes them all: before touching z-index or adding !important, name the containing block.

The More menu, cut off by its own card

The menu's containing block is inside the card body, and the card body clips. Everything below the card's bottom edge is lost, including Archive.

Menu opens. Box layout “app.slatework.example/projects/atlas/board”: 7 shapes. View: 432 × 344 from -16, -28. Viewport: 400 × 300 at 0, 0. 1. “body” (frame) 400 × 300 at 0, 0 2. “div.card” (rect) badge “position: relative” 232 × 150 at 24, 24 in body 3. “Fix login redirect” (rect) 140 × 22 at 36, 34 sand in div.card 4. “div.card-body” (rect) badge “overflow: hidden” 232 × 110 at 24, 64 in div.card clips its children (overflow: hidden) 5. “description” (rect) 150 × 54 at 36, 104 sand in div.card-body 6. “More” (rect) 44 × 24 at 200, 72 blue in div.card-body 7. “ul.menu” (rect) badge “absolute; top: 100%” 120 × 132 at 124, 100 orange in div.card-body Clipped (the hidden parts drawn dashed): ul.menu partly by div.card-body. Note on shape:menu: Archive is in the hidden part

  1. The More menu4MoreMenu
  2. Clipping card body3TaskCard

The five position values at a glance

positionKeeps its space in the flow?Offsets and percentages measured fromWhen the page scrolls
staticYes; top, left and friends are ignoredContent box of the nearest block container (for percentage widths, heights and margins)Moves with its parent
relativeYes; drawn shifted, but the gap stays where it wasIts own normal position (the shift); percentages from the same content box as staticMoves with its parent
absoluteNo; the rest of the flow closes upPadding box of the nearest ancestor that is not static (or the initial containing block)Moves with that ancestor
fixedNoThe viewport, unless an ancestor has transform, filter, contain and a few othersStays put, unless such an ancestor has captured it
stickyYes, alwaysLaid out like relative; sticks within the nearest scrolling ancestor and never leaves its parentScrolls until it reaches its inset, holds, then is pushed off by its parent's end
Was this section helpful?

How it works.

One walk up the tree, with a different stopping rule per position value. Then two consequences of where the walk stopped: who may clip the box, and what it moves with when the page scrolls.

Finding the containing block

For a box in the flow (static, relative or sticky) the containing block is the content box of the nearest ancestor that is a block container or starts a formatting context: a block, a list item, a flex or grid container, a table cell. That is the box its percentage widths and margins come from, and it is almost always the parent you expect.

An absolute box skips every static ancestor and stops at the first one whose position is relative, absolute, fixed or sticky. It uses that ancestor's padding box, so an offset of 0 lands just inside the border, on top of the padding. If no ancestor qualifies, the walk ends at the initial containing block, a rectangle the size of the viewport anchored at the top of the document.

A fixed box normally skips the whole tree and uses the viewport. The exception is the one that surprises people: an ancestor with a transform, an individual translate, rotate or scale, perspective, filter, backdrop-filter, contain set to layout, paint, strict or content, will-change naming one of those, or content-visibility auto becomes the containing block for absolute and fixed descendants alike. The walk for fixed stops there, and so does the walk for absolute, even if that ancestor is static. (Browsers have not always agreed on perspective and filter, so test those two rather than relying on them.)

Where the More menu's walk stops

Where the More menu's walk stops. The numbered component cards that follow describe each part.
Where the More menu's walk stopsComponents: 1. AppShell (The page frame around every route. Slides between routes with a transform during the page transition.), 2. BoardColumn (One column of the board. Scrolls its own cards (overflow-y auto) and keeps a sticky header.), 3. TaskCard (A task on the board. Its body clips long descriptions (overflow hidden) and holds the More button.), 4. MoreMenu (The card's dropdown (Edit, Move to, Copy link, Archive). Must open next to its button and never be cut off.).

Viewport
initial containing block
what fixed means

1AppShell
div.app-shell
transform during the route slide

div.board
overflow-x: auto

2BoardColumn
section.column
overflow-y: auto

3TaskCard
div.card
position: relative

div.card-body
overflow: hidden
position: relative (for the fade)

div.more
position: relative

4MoreMenu
ul.menu
position: absolute

5 steps. Step through to see what one change does to these components.

Same card, two containing blocks

div.card (border-box 320)
  1. width 1, Border
  2. 16, width 16, Padding
  3. content 286, width 286, Content
  4. 16, width 16, Padding
  5. width 1, Border
  • static child starts here, cursor at 17
  • content box: 286, from 17 to 303
  • Border
  • Padding
  • Content
Start

As it starts. 3 steps follow.

A Slatework card 320 px wide with a 1 px border and 16 px of padding. A static child measures percentages against the 286 px content box; an absolute child measures them against the 318 px padding box and puts left 0 at the padding edge.

Ancestors that capture absolute and fixed descendants

Property on the ancestorWhy it is there in real appsWhat a fixed child inside it does
position other than staticThe usual, deliberate anchor for absolute childrenNothing: fixed ignores positioned ancestors
transform, translate, rotate, scale (not none)Slide-in routes, drawers, card hover lifts, centring tricksMeasured from this box and scrolls with it
perspective (not none)3D flips and tiltsSame as transform
filter, backdrop-filter (not none)Dimmed or blurred backgrounds, frosted top barsSame as transform
contain: layout, paint, strict or contentPerformance isolation of a widget or a list rowSame as transform
will-change naming one of the aboveAnimation hints left on after the animationSame as transform, before anything moves
content-visibility: autoSkipping work for off-screen sectionsSame as transform

Who may clip a positioned box

overflow hidden, clip, auto or scroll on a box cuts off its descendants at its padding edge, with one exception written into the spec: a descendant whose containing block is the viewport, or an ancestor of the clipping box, is not cut. Read it as a question about the walk. If the positioned box's walk stopped inside the clipper (at the clipper itself or something below it), the clipper wins. If it stopped above the clipper, the clipper has no say.

That is why Slatework's menu is clipped: div.more is positioned and sits inside the card body. It also explains a confusing half-fix. Removing position from div.more and the body moves the containing block up to the card, so the body stops clipping, yet the menu is still cut at the bottom of the column, because the column scrolls (overflow-y auto) and the card is inside it. Every scroll container between the trigger and the root is another clipper, and on a board there are usually two or three.

Note also that hidden, auto and scroll cannot clip one axis alone: when one axis uses them and the other is left visible, the visible one computes to auto. (Only overflow-x or overflow-y set to clip can leave the other axis visible.) A column with overflow-y auto therefore clips sideways too, which is why a menu that pokes out of a column's right edge is cut there as well.

The menu as shipped, and the half-fix

.column      { overflow-y: auto; }          /* clipper 1 */
.card        { position: relative; }
.card-body   { position: relative;          /* holds the fade overlay */
               overflow: hidden; }          /* clipper 2 */
.more        { position: relative; }        /* containing block */
.more-menu   { position: absolute;
               top: calc(100% + 4px);
               right: 0; }
.card-body   { overflow: hidden; }          /* no longer positioned */
.more        { }                            /* no longer positioned */
.more-menu   { position: absolute;          /* containing block: .card */
               top: 56px;                   /* now measured from the card */
               right: 12px; }

When fixed stops being fixed

The toast that scrolls away

At scroll 0 the shell sits exactly where the viewport is, so the toast looks fixed. The bug is invisible until someone scrolls.

Top of the page. Box layout “app.slatework.example/projects/atlas/board”: 7 shapes. View: 432 × 604 from -16, -28. Viewport: 400 × 260 at 0, 0. 1. “body” (frame) 400 × 560 at 0, 0 2. “div.app-shell” (rect) badge “transform: translateX(0)” 400 × 560 at 0, 0 in body 3. “header.topbar” (rect) 400 × 40 at 0, 0 sand in div.app-shell 4. “div.board” (rect) 368 × 480 at 16, 56 in div.app-shell 5. “To do” (rect) 168 × 456 at 28, 68 blue in div.board 6. “Doing” (rect) 168 × 456 at 204, 68 blue in div.board 7. “Card moved. Undo” (rect) badge “fixed, inside the shell” says “position: fixed” 156 × 40 at 228, 204 green in div.app-shell Clipped (the hidden parts drawn dashed): body partly by the viewport; div.app-shell partly by the viewport; div.board partly by the viewport; To do partly by the viewport; Doing partly by the viewport.

  1. Shell with a leftover transform1AppShell
  2. Undo toast5Toast

The transform does not have to move anything. translateX(0), scale(1) and a will-change: transform hint left on for 'smoothness' all count, because the rule looks at the computed value, not at the visual result. The usual ways in: a route transition or drawer that animates transform and leaves its final value set, a filter: blur(4px) on the page while a modal is open, and contain: paint added to a widget for speed. The same ancestor also becomes a stacking context, which is the next topic's subject.

Two fixes, depending on whether you own the ancestor. If you do, let the effect end at none: animate with a class that is removed on animationend, and drop the will-change hint after the animation. If you do not, move the fixed element out of the subtree: render the toast region as a direct child of body (a portal in component terms), or put it in the top layer as a popover, which ignores every ancestor's transform.

What sticky needs to stick

A sticky box is laid out exactly like a relative one and keeps its space in the flow. Then, as its scroll container scrolls, the browser shifts it so it never crosses the inset you gave it (top: 0 means 'not above the top edge of the scrollport'). Three conditions must hold, and each failure looks the same: the box scrolls away as if sticky were ignored.

  1. An inset on the axis it should stick in. top or bottom for vertical scrolling, left or right (or the inline-start and inline-end forms) for horizontal. With every inset auto there is no threshold to hold.
  2. The right scroll container. A sticky box sticks to the nearest ancestor with a scrolling mechanism, meaning overflow hidden, scroll or auto, even when that ancestor never actually scrolls because it is as tall as its content. A wrapper with overflow auto 'just in case' silently becomes the scrollport, and since it never scrolls, nothing sticks while the page scrolls past it. overflow clip does not create a scroll container, so it does not capture sticky boxes.
  3. Room in its parent. The box may move only while it stays inside its containing block, its parent's content box. A heading in a section that is barely taller than the heading has nowhere to go: it sticks for a few pixels and is pushed off.

Column headers that will not stick

Both headers carry position sticky and top 0. At scroll 0 there is nothing to tell yet.

Top of the page. Box layout “app.slatework.example/projects/atlas/board”: 11 shapes. View: 432 × 644 from -16, -28. Viewport: 400 × 240 at 0, 0. 1. “body” (frame) 400 × 600 at 0, 0 2. “div.board-wrap” (rect) badge “overflow: auto” 368 × 544 at 16, 40 in body 3. “section.todo” (rect) 170 × 528 at 24, 48 in div.board-wrap 4. “Fix login redirect” (rect) 154 × 60 at 32, 84 blue in section.todo 5. “Draft release notes” (rect) 154 × 60 at 32, 152 blue in section.todo 6. “Rename billing plan” (rect) 154 × 60 at 32, 220 blue in section.todo 7. “Audit invite emails” (rect) 154 × 60 at 32, 288 blue in section.todo 8. “h3 To do” (rect) badge “sticky; top: 0” 170 × 28 at 24, 48 purple in section.todo 9. “section.done” (rect) 170 × 140 at 206, 48 in div.board-wrap 10. “Ship the dark theme” (rect) 154 × 60 at 214, 84 green in section.done 11. “h3 Done” (rect) badge “sticky; top: 0” 170 × 28 at 206, 48 purple in section.done Clipped (the hidden parts drawn dashed): body partly by the viewport; div.board-wrap partly by the viewport; section.todo partly by the viewport; Rename billing plan partly by the viewport; Audit invite emails entirely by the viewport.

  1. Column header2BoardColumn

When a sticky box scrolls away anyway

What you seeCauseFix
Never sticks, anywhereNo inset in the scrolling axis (top, bottom, left and right all auto)Set top: 0 (or the height of whatever sits above it, such as a sticky top bar)
Never sticks while the page scrollsAn ancestor between it and the page has overflow hidden or auto and does not scroll itselfRemove that overflow, use overflow: clip where only clipping was wanted, or make that ancestor the real scroller with a fixed height
Sticks briefly then leavesThe parent is barely taller than the sticky box, so its end pushes the box outMove the sticky box up a level, to a parent that spans the content it labels
Sticks under the top bartop: 0 is measured from the scrollport, and the sticky top bar covers itOffset by the bar: top equal to the bar height, ideally from one custom property shared by both
Was this section helpful?

In practice.

The More menu is Slatework's dropdown case study: get it out from under every clipper, place it next to its button, and flip it when the window has no room below. The same moves apply to every floating surface on the board.

The Slatework board and its floating surfaces

Every card's body clips its description, and every column scrolls on its own. Both are reasons a menu drawn in place gets cut.

Board. Board: 7 items. Column “To do”: 1. Fix login redirect by Ines R. badges: Bug. 2. Draft release notes by Tomas K. 3. Audit invite emails badges: Security. Column “Doing”: 1. Rename billing plan by Ines R. 2. Board keyboard shortcuts by Priya N. badges: Feature. Column “Review”: 1. Audit colour contrast by Dev S. Column “Done”: 1. Ship the dark theme by Theo B.

  1. Task card with a clipping body3TaskCard
  2. Scrolling column2BoardColumn

Case study: one menu, three placements

The menu's containing block is inside the card body, which is inside the column. Both cut it, and the window would cut it next.

In place. Box layout “app.slatework.example/projects/atlas/board”: 7 shapes. View: 432 × 400 from -16, -28. Viewport: 400 × 300 at 0, 0. 1. “body” (frame) 400 × 300 at 0, 0 2. “section.column” (rect) badge “overflow-y: auto” 208 × 268 at 16, 16 in body clips its children (overflow: hidden) 3. “Fix login redirect” (rect) 192 × 100 at 24, 48 blue in section.column 4. “Audit invite emails” (rect) badge “position: relative” 192 × 110 at 24, 168 in section.column 5. “div.card-body” (rect) badge “overflow: hidden” 192 × 82 at 24, 196 in Audit invite emails clips its children (overflow: hidden) 6. “More” (rect) 44 × 22 at 164, 204 blue in div.card-body 7. “ul.menu” (rect) badge “absolute” 112 × 110 at 96, 230 orange in div.card-body Clipped (the hidden parts drawn dashed): ul.menu partly by section.column and div.card-body and the viewport.

  1. More menu4MoreMenu

Does the menu fit below?

Assumptions
Viewport height
800 pxa laptop window
More button top
612 pxfrom getBoundingClientRect()
More button bottom
640 px
Menu height
220 pxfour items and padding
Gap between button and menu
4 px
Working
  1. Room below the buttonvh − btn-bottom = 800 − 640160 pxfrom Viewport height and More button bottom
  2. Room the menu needsmenu-h + gap = 220 + 4224 pxfrom Menu height and Gap between button and menu
  3. Room above the buttonbtn-top − 0612 pxfrom More button top
  4. Placement160 < 224 and 612 ≥ 224flip abovefrom Room below the button, Room the menu needs and Room above the button
  5. Menu top when flippedbtn-top − gap − menu-h = 612 − 4 − 220388 pxfrom More button top, Gap between button and menu and Menu height
What it means
  • Flip on the block axis first; shift along the inline axis second, so a menu near the right edge slides left by the overflow plus a small padding instead of jumping sides.
  • If neither side fits (a short phone window), cap the menu's height to the larger side and let it scroll inside, rather than letting the window cut it.
  • The numbers change with every scroll and resize, so the check has to run again whenever either happens while the menu is open.

The More menu's placement, while it is open

States of4MoreMenu

The More menu's placement, while it is open. 5 states, 10 transitions. The table below lists them.
The More menu's placement, while it is openThe states of MoreMenu. 5 states, 10 transitions. The table below lists them.

click More

measured [room below]

measured [no room below] / flip-block

measured [button clipped away]

scroll or resize

scroll or resize

scroll or resize

pick, Escape or click outside

pick, Escape or click outside

click outside or route change

Closed

Measuring

Open below

Open above (flipped)

Hidden (button out of view)

3 steps.

Placement is not decided once. Every scroll or resize sends the menu back to measuring, and a button scrolled out of view hides the menu rather than leaving it floating over unrelated cards.

Transitions of The More menu's placement, while it is open
From → ToEventGuardAction
Closed → Measuringclick More
Measuring → Open belowmeasuredroom below
Measuring → Open above (flipped)measuredno room belowflip-block
Measuring → Hidden (button out of view)measuredbutton clipped away
Open below → Measuringscroll or resize
Open above (flipped) → Measuringscroll or resize
Hidden (button out of view) → Measuringscroll or resize
Open below → Closedpick, Escape or click outside
Open above (flipped) → Closedpick, Escape or click outside
Hidden (button out of view) → Closedclick outside or route change
Closedstart
Measuring
Read the button's rectangle and the menu's size
Open below
The default placement

Two ways to ship the fix

<button class="more" popovertarget="menu-sw142">More</button>
<ul class="more-menu" id="menu-sw142" popover>
  <li><button>Edit</button></li>
  <li><button>Move to…</button></li>
  <li><button>Copy link</button></li>
  <li><button>Archive</button></li>
</ul>

<style>
  /* popovertarget makes the button the menu's implicit anchor;
     without it: anchor-name: --more on the button and
     position-anchor: --more on the menu */
  .more-menu {
    inset: auto;                       /* replace the popover's centred defaults */
    margin: 4px 0;                     /* (inset: 0, margin: auto) with a 4px gap */
    position-area: bottom span-left;   /* below, right edges lined up */
    position-try-fallbacks: flip-block;
    position-visibility: anchors-visible; /* the default, written out: hide when More scrolls out of view */
    max-height: 60vh;
    overflow-y: auto;
  }
</style>
import { computePosition, autoUpdate, offset, flip, shift } from '@floating-ui/dom';

// menu is rendered as a child of <body>, outside every clipper
function openMenu(button, menu) {
  menu.hidden = false;
  const stop = autoUpdate(button, menu, async () => {
    const { x, y, placement } = await computePosition(button, menu, {
      strategy: 'fixed',
      placement: 'bottom-end',
      middleware: [offset(4), flip(), shift({ padding: 8 })],
    });
    Object.assign(menu.style, { left: `${x}px`, top: `${y}px` });
    menu.dataset.placement = placement; // 'top-end' after a flip
  });
  return () => { stop(); menu.hidden = true; };
}

// Take this path only where anchor positioning is missing
const needsFallback = !CSS.supports('position-area', 'bottom');

The three Slatework bugs, as you would explain them

Q1
Clipped menuThe More menu is cut off at the bottom of its card. Removing overflow hidden fixes it but breaks the descriptions. What is going on, and what is the real fix?
The menu is absolute and its containing block, the positioned div.more, sits inside the card body, so the body may clip it; the scrolling column above may too. Clipping follows the containing block, so the fix is to give the menu a containing block above every clipper: render it as a popover (top layer, viewport as containing block) or portal it to body with position fixed, then place it next to the button with anchor positioning or measured coordinates.
Q2
Drifting toastThe Card moved toast is position fixed, yet it scrolls up with the page after navigating between routes. Why?
The route transition animates transform on the app shell and leaves translateX(0) set when it ends. Any transform other than none makes the shell the containing block for fixed descendants, so the toast is measured from the shell and scrolls with it. End the animation at transform none (remove the class on animationend, drop will-change), or render the toast region outside the shell.
Q3
Non-sticky headerThe column headers have position sticky and top 0 but scroll away with the page. Nothing else seems wrong.
Someone put overflow auto on the board wrapper. Sticky attaches to the nearest ancestor with a scrolling mechanism, and that wrapper never scrolls because it is as tall as its content. Remove the overflow, or use overflow clip if it only existed to hide a stray pixel, so the page becomes the scrollport again. Then check that each header's parent is tall enough to give it room.
Q4
InterviewIn a design interview: 'Your autocomplete dropdown sits inside a scrollable panel. How do you make sure it is never cut off?'
Name the mechanism first: an absolute dropdown is clipped by any overflow ancestor its containing block sits inside. Then the plan: render the list in the top layer as a popover (or portal it to body), anchor it to the input with CSS anchor positioning and a flip-block fallback, and feature-detect to a JS positioning library that flips and shifts and re-runs on scroll and resize. Close or hide it when the input scrolls out of view. Keyboard and ARIA behaviour stay with the input, which is a separate part of the answer.

The same moves in the systems on this track

Search suggestions (a Pinterest-like search)
The suggestion list hangs from the search input in a sticky header. It must escape any clipping ancestor of that header, follow the header while it sticks, and cap its height when the window is short, as on a phone held sideways.
Slash menu at the caret (a Notion-like editor)
The anchor is a caret rectangle, not an element, so the menu is placed from measured coordinates and re-placed as the page scrolls under it.
Date picker (an Airbnb-like booking card)
A popover anchored to the Check-in field inside a sticky booking card: flip above when the card is low, and stay attached while the card sticks.
Fixed composer and toasts
Anything meant to stay in the window (chat composer, toasts, a back-to-top button) must live outside transformed or filtered ancestors, or in the top layer.
Was this section helpful?

Trade-offs.

Two decisions for every floating surface, where it lives in the tree and who computes its position, plus a short list of the ways each choice goes wrong.

01
Where the More menu lives
Chosen:A popover in the top layer, next to its button in the markup
  • Pro:Escapes every clipper and every transformed ancestor, with the viewport as its containing block
  • Pro:Stays next to its trigger in the DOM, so reading order and event bubbling still make sense
  • Pro:Light dismiss (click outside, Escape) comes with the popover attribute
  • Pro:Gets an implicit anchor from popovertarget
Downside we accept:
  • Con:Inherits font and colour from the card it is written in, which the menu may not want
  • Con:Top-layer order is by opening order, not z-index (the next topic)
  • Con:Placement still needs anchor positioning or JS
Ruled out:A portal to the end of body, position fixed

DOM order no longer matches visual order; focus and bubbling need care; Captured again if body or html gets a transform or filter (a modal blur); Needs measured coordinates and updates on every scroll and resize; Inherits only from body, so themed tokens must be global

Ruled out:In place, position absolute

Clipped by every overflow ancestor its containing block sits inside; Breaks again the day someone adds overflow or contain to an ancestor; Cannot flip past the edge of its scroll container

02
Who computes the menu's position
Chosen:CSS anchor positioning, with a library fallback behind feature detection
  • Pro:Declarative: position-area plus position-try-fallbacks covers flip in a few lines
  • Pro:The browser keeps it attached during layout, no scroll listeners
  • Pro:Baseline since September 2026, in Chromium since version 125
Downside we accept:
  • Con:Browsers older than that still need the fallback path for now
  • Con:Some tactics (shift by a padding, size to the larger side) still need more CSS or a custom @position-try
  • Con:Debugging which fallback won means inspecting computed styles
Ruled out:A positioning library (Floating UI or similar) everywhere

Ships JavaScript and runs on every scroll and resize; Runs after layout, so a slow main thread can leave the menu a frame behind its button

Ruled out:Hand-written getBoundingClientRect maths

Re-implements flip, shift, clipping ancestors and scroll listening, usually with edge cases missed; Easy to measure in the wrong coordinate space (page versus viewport versus containing block)

Fixed or sticky for things that should stay on screen

NeedBetter choiceWhy
Top bar on every routesticky; top: 0Keeps its space in the flow, so nothing needs a padding-top equal to its height; stays put as long as no ancestor of it has overflow
Column or table headerssticky; top: 0Sticks within its own column and is pushed off when the column ends
Toasts and a back-to-top buttonfixed, or a popoverBelongs to the window, not to any section; must sit outside transformed or filtered ancestors
Dropdowns and tooltipspopover, anchor-positionedNeeds to escape clippers and follow an element
Badge on a card cornerabsolute in a relative cardMoves and clips with the card on purpose

How positioned surfaces break in production

FailureImpactDetectionMitigationMeanwhile
A new overflow or contain rule on an ancestor clips an in-place menu4MoreMenuThe last items (often the destructive one) cannot be reached; users report 'Archive is missing'A visual regression test that opens every menu on the lowest card of a full columnRender floating surfaces in the top layer or a portal so ancestor rules cannot reach themKeyboard users can still arrow to hidden items, but cannot see them
An ancestor gains transform, filter or will-change and captures a fixed element5ToastThe toast or a fixed composer scrolls away; Undo is never seenAn end-to-end test that scrolls the page and asserts the toast's rectangle is unchangedKeep the toast region outside the app shell; end animations at none and remove will-change afterwardsThe message still exists in the page, only off screen
Sticky header silently disabled by an overflow wrapper2BoardColumnColumn names scroll away on long columnsLint or review for overflow on ancestors of sticky elementsPrefer overflow clip where only clipping is wanted; document which element is the scrollerPurely cosmetic, but cards lose their column label while scrolling
A portaled menu is placed once and never updated4MoreMenuScrolling the column leaves the menu hanging over other cards, detached from its buttonOpen a menu, scroll its column, compare the menu's and button's rectanglesRe-run placement on scroll and resize (autoUpdate) or use anchor positioning; hide when the button leaves viewUsers close and reopen the menu to get it back in place
This topic
The containing block for each position value, and what captures absolute and fixed boxes
Which ancestors may clip a positioned box
Sticky's scroll container, inset and parent rules
Escaping clippers and placing a dropdown, including flip
Elsewhere
Which box paints on top, z-index, the top layer's orderStacking contexts and the top layer
Box sizes, padding and margins in normal flowThe box model and normal flow
What creates a block formatting context and why overflow doesFormatting contexts
Menu keyboard support, focus return and ARIAKeyboard and focus
The portal or popover as a component APIComposition patterns
Why will-change and transforms cost GPU memoryLayers and compositing
Was this section helpful?
Builds on this
Stacking contexts and the top layer
Read next