Promoting Fixed and Sticky Elements

Part of Layer Promotion & will-change Strategy in Performance Budgeting & GPU Architecture.

The problem

An application has a fixed header, a sticky table header, a floating action button and a cookie banner pinned to the bottom. Scrolling is noticeably less smooth than on a page without them, and adding a frosted-glass effect to the header made it worse. Animating the header’s shrink-on-scroll effect seemed to make no difference at all — it was already slow.

Elements that stay in place while the page scrolls have a particular cost profile, and it is mostly not about their animations.

Root cause analysis: staying still while everything moves

Scrolling moves a layer. The page’s contents are composited, and scrolling translates them. An element that must stay in place while everything else moves cannot be part of that moving content, so browsers promote fixed elements — and sticky elements while they are stuck — to their own layers. The promotion is not something the author asked for and is usually the right decision: it lets scrolling happen on the compositor.

Each promoted element costs its area. A fixed header 64px tall on a 412px-wide phone at device pixel ratio 2.6 costs around 0.7MB — trivial. A full-viewport fixed overlay costs around 10MB, and a full-viewport fixed background image costs the same permanently, whether or not anything is animating, which is the arithmetic in texture memory budget for mobile.

backdrop-filter on a sticky surface is the expensive case. The filter must sample what is behind the element. While scrolling, the content behind changes every frame, so the blur is recomputed every frame over the element’s area. A frosted header across a wide viewport is one of the most expensive things a page can do during scroll.

Animating them is comparatively cheap. Because they are already promoted, a header that shrinks by scaling or translating costs almost nothing extra. A header that shrinks by animating height or padding costs layout for itself and everything below it on every frame — the usual property-choice problem, not a fixed-position problem.

They can promote their neighbours. Content that scrolls over a promoted sticky element may be promoted to stay above it, the cascade described in stacking contexts and accidental layer explosions.

Main-thread and GPU cost per scroll frameMid-range phone, 412 px viewport, scrolling a long article.Main-thread and GPU cost per scroll framebudget 16.7 msNo fixed elements1.2 msFixed header, solid background1.6 msFixed header with backdrop-filter9.4 msFixed header + sticky table head + FAB2.4 ms
Mid-range phone, 412 px viewport, scrolling a long article.

Step-by-step resolution

Keeping pinned UI cheapSmall areas, solid surfaces, transform-based effects.Keeping pinned UI cheap1List fixed and sticky elements with their pixel dimensions.A promotion inventory2Replace full-viewport fixed backgrounds with scrolling ones where possible.Large memory saving3Swap backdrop-filter on large surfaces for a translucent solid colour.No per-frame blur during scroll4Implement shrink effects with scale or translate.No layout per frame5Check whether overlapping content is being promoted.Bounded layer count6Disable scroll-linked shrink under reduced motion.
Small areas, solid surfaces, transform-based effects.

Production code pattern

.site-header {
  position: fixed;
  inset-block-start: 0;
  inline-size: 100%;
  block-size: 64px;
  z-index: 20;
  background: color-mix(in oklch, var(--color-surface) 88%, transparent);
  /* backdrop-filter: blur(12px);  <- removed: re-blurs every scroll frame */
  transform-origin: top center;
  animation: header-shrink linear both;
  animation-timeline: scroll(root block);
  animation-range: 0 120px;
}
@keyframes header-shrink {
  to { scale: 1 0.75; }                 /* scale, not height: no layout per frame */
}
.site-header__inner {
  animation: header-counter linear both;
  animation-timeline: scroll(root block);
  animation-range: 0 120px;
}
@keyframes header-counter {
  to { scale: 1 1.333; }                /* counter-scale so the logo is not squashed */
}

/* Sticky table head: promoted only while stuck, and small. */
.table thead th {
  position: sticky;
  inset-block-start: 64px;
  background: var(--color-surface);
}

@media (prefers-reduced-motion: reduce) {
  .site-header, .site-header__inner { animation: none; scale: 1; }
}

Rendering Impact: composite. The header is already promoted because it is fixed, so scaling it adds nothing; the counter-scale keeps its contents undistorted. Removing backdrop-filter removes a per-frame blur over a wide, always-visible area.

Counter-scaling has limits: it corrects uniform distortion but not effects like border radius or shadows, which scale with the element. For headers that must genuinely change height rather than appear to, animate height and accept the layout cost, or cross-fade between two fixed-height headers.

Pinned UI patterns and their costsArea and backdrop effects dominate; animation choice matters less.Pinned UI patterns and their costsLayer sizePer-frame cost whilescrollingVerdict64px fixed header,solidSmallNegligibleFineFixed header withblurSmallBlur per frameAvoid on wide screensFull-viewport fixedbackgroundVery largeMemory, not timeUse scrolling backgroundSticky sectionheadersSmall, severalNegligibleFineFloating actionbuttonTinyNegligibleFine
Area and backdrop effects dominate; animation choice matters less.

Verification checklist

Constraints and trade-offs

  • Counter-scaling cannot correct border radius, shadows or borders, which scale with the element.
  • A translucent solid background is less visually rich than a blurred one.
  • Sticky elements that are stuck for most of a page are effectively permanently promoted.
  • Raising a header’s z-index to avoid promoting neighbours changes what can overlap it.
  • Fixed-position elements are affected by mobile browser chrome appearing and disappearing.

Frequently asked questions

Are fixed elements automatically promoted?

Browsers generally promote fixed-position elements, and sticky elements while they are stuck, so that scrolling can stay on the compositor.

Why is my frosted header expensive while scrolling?

backdrop-filter must re-sample and re-blur the content behind it, which changes on every scroll frame. Use a translucent solid colour on large sticky surfaces.

How should a header shrink on scroll?

With scale or translate, counter-scaling the contents if needed. Animating height or padding lays out the page on every frame.

Do sticky section headers cost a lot?

Individually they are small layers and cheap. Many of them at once add up, so keep their number and size in mind on long lists.