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.
scroll-margin: 0- Viewport height
- --
- Target top
- --
- Header overlap
- --
scroll-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
- Understanding Success Criterion 2.4.11: Focus Not Obscured (Minimum) - W3C Web Accessibility Initiative, checked 2026-08-04
- F110: Sticky content completely hiding focused elements - W3C Web Accessibility Initiative, checked 2026-08-04
- CSS Scroll Snap Module Level 1 - scroll-margin - W3C, checked 2026-08-04
Formal evidence
Browser and viewport matrix
Cells show failing / fixed sticky-header clearance deficit in CSS pixels.
| Browser | 320 | 375 | 768 | 1024 | 1440 |
|---|---|---|---|---|---|
| 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-32056pxchromium-fixed-overlap-3200pxchromium-horizontal-reflow-3200px / 0pxchromium-failing-overlap-37556pxchromium-fixed-overlap-3750pxchromium-horizontal-reflow-3750px / 0pxfirefox-failing-overlap-32056pxfirefox-fixed-overlap-3200pxfirefox-horizontal-reflow-3200px / 0pxfirefox-failing-overlap-37556pxfirefox-fixed-overlap-3750pxfirefox-horizontal-reflow-3750px / 0pxwebkit-failing-overlap-32056pxwebkit-fixed-overlap-3200pxwebkit-horizontal-reflow-3200px / 0pxwebkit-failing-overlap-37556pxwebkit-fixed-overlap-3750pxwebkit-horizontal-reflow-3750px / 0px
Run 2026-08-04T11-36-57-234Z. chromium 149.0.7827.201, firefox 153.0, webkit 26.5. Open raw manifest