All cases
RL-009 / Mobile Viewports & OverlaysEvidence verified

Prevent Sticky Headers From Hiding Anchor Targets

A sticky 56-pixel header covers a focused jump target after scroll positioning, so keyboard users cannot see the control or its focus indicator.

Experiment mode

Fixture viewport

Failingscroll-margin: 0
Viewport height
--
Target top
--
Header overlap
--
Fixedscroll-margin-top: 64px
Viewport height
--
Target top
--
Header overlap
--

Three-browser evidence verified. Live values come from your current browser. The formal matrix records the controlled browser runs and fixture revision used for the published conclusion.

Observed failure

The scroll target sits underneath the sticky header

The browser aligns the focused action with the scrollport start while a 56-pixel header remains stuck at that same edge. The header completely covers the 40-pixel control and its focus indicator.

Minimal code diff

Reserve space in scroll positioning

Add a block-start scroll margin larger than the sticky header so anchor and focus-driven scrolling place the target inside the visible region.

Before

.release-action {
  scroll-margin-block-start: 0;
}

After

.release-action {
  scroll-margin-block-start: 64px;
}

Root cause

Sticky positioning keeps the header at the scrollport’s block-start edge without removing the underlying content from the document. A normal anchor or scrollIntoView() operation can align its target to that same edge. The target is technically inside the viewport but visually covered by authored content.

The regression is more serious when the target is an interactive control. A keyboard user can move focus to it while neither the control nor its focus indicator is visible. The recorded fixture focuses the action after positioning it so the overlap is measured rather than inferred.

Test every path that moves the scroll position

Activate the in-page link, focus the target directly, and approach the same control through forward and reverse keyboard navigation. Measure the sticky header’s block-end edge and the target’s block-start edge after each movement. A visible heading is not sufficient if the interactive target or its focus ring is still covered.

Repeat the sequence after the header wraps at a narrow breakpoint and at increased browser zoom. If more than one sticky layer can occupy the top edge, calculate the combined obstruction or manage one shared offset token. This makes the clearance follow the actual navigation system rather than a number copied into unrelated components.

When this fix is not enough

One fixed pixel offset may become wrong when the header wraps, changes size at a breakpoint, or includes a temporary notice. A shared custom property, scroll-padding-block-start on the scroll container, or unfixing the header at constrained heights can be more maintainable. Test forward and reverse keyboard navigation, browser zoom, and every sticky layer that can occupy the same edge.

Regression condition

At every recorded viewport, the failing target must have positive overlap with the sticky header after scroll positioning and focus. The fixed target must start below the header with zero overlap. Both states must keep zero page-level horizontal overflow.

Primary references

Sources checked for this case

  1. Understanding Success Criterion 2.4.11: Focus Not Obscured (Minimum) - W3C Web Accessibility Initiative, checked 2026-08-04
  2. F110: Sticky content completely hiding focused elements - W3C Web Accessibility Initiative, checked 2026-08-04
  3. CSS Scroll Snap Module Level 1 - scroll-margin - W3C, checked 2026-08-04

Formal evidence

Browser and viewport matrix

Three-browser run

Cells show failing / fixed sticky-header clearance deficit in CSS pixels.

Browser32037576810241440
Chromium+56 / +0+56 / +0+41 / +0+41 / +0+0 / +0
Firefox+56 / +0+56 / +0+41 / +0+41 / +0+0 / +0
WebKit+56 / +0+56 / +0+45 / +0+45 / +0+0 / +0
18/18 recorded assertions passed
  • chromium-failing-overlap-32056px
  • chromium-fixed-overlap-3200px
  • chromium-horizontal-reflow-3200px / 0px
  • chromium-failing-overlap-37556px
  • chromium-fixed-overlap-3750px
  • chromium-horizontal-reflow-3750px / 0px
  • firefox-failing-overlap-32056px
  • firefox-fixed-overlap-3200px
  • firefox-horizontal-reflow-3200px / 0px
  • firefox-failing-overlap-37556px
  • firefox-fixed-overlap-3750px
  • firefox-horizontal-reflow-3750px / 0px
  • webkit-failing-overlap-32056px
  • webkit-fixed-overlap-3200px
  • webkit-horizontal-reflow-3200px / 0px
  • webkit-failing-overlap-37556px
  • webkit-fixed-overlap-3750px
  • webkit-horizontal-reflow-3750px / 0px