CSS Tutorial 0/203 lessons ~6 min read Lesson 148

    Focus Management

    focus management css focus management complements javascript focus traps in modals, drawers, and spa route changes — scroll-margin for fixed

    Course progress0%
    Focus
    27 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    CSS focus management complements JavaScript focus traps in modals, drawers, and SPA route changes — scroll-margin for fixed headers, z-index stacking, :focus-visible styling, and :has() selectors to adapt layout when dialogs open. WCAG 2.4.11 Focus Not Obscured is primarily a CSS layout problem.

    Business problem

    Users lose context when focus moves under sticky headers, behind cookie banners, or off-screen in horizontal scroll containers — especially keyboard and low-vision zoom users.

    • 2.4.11 AA: Focused element not entirely hidden by author-created content.
    • 2.4.12 AAA: No part of focus indicator obscured.

    Why this feature exists

    Sticky and fixed positioning proliferated in modern UI — headers, chat widgets, toasts stack in z-index layers. Without scroll-margin and stacking discipline, focus rings render under opaque overlays.

    • scroll-margin / scroll-padding: CSS Scroll Snap module — offsets scrollIntoView for focus navigation.
    • :has(): Style page when dialog open — e.g. prevent body scroll, dim background.

    Browser rendering perspective

    Focus scroll — browser calls scrollIntoView on focused element; scroll-margin-top adjusts final position relative to scrollport, accounting for fixed headers without JS.

    • Composite layers: High z-index fixed elements paint above focus ring if stacking context wrong.

    Internal browser workflow

    Modal open flow: JS moves focus to dialog → CSS ensures dialog above chrome (z-index) → focused element scroll-margin clears sticky header → :focus-visible ring visible on dark overlay backdrop context.

    • Route change: Move focus to h1 with tabindex=-1 — style :focus outline none on heading if programmatic only.

    Feature deep dive

    Focus management CSS toolkit:

    css
    :root {
    --sticky-header: 72px;
    --focus-ring: 2px solid #2563eb;
    }
    :focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
    :focus { scroll-margin-top: var(--sticky-header); }
    .dialog { z-index: 1000; }
    .site-header { z-index: 100; }
    body:has(.dialog[open]) { overflow: hidden; }

    Syntax

    Programmatic focus target (route heading):

    css
    h1:focus { outline: none; } /* tabindex=-1 programmatic focus — provide visible page change via other cues */
    h1:focus-visible { outline: var(--focus-ring); } /* if user tabs to it */

    Examples

    Chat bubble covering submit: Raise z-index of focused form field via :focus-within on fieldset or increase scroll-margin-bottom above widget height.

    Real-world use

    Radix, Headless UI, and Adobe React Spectrum document focus ring tokens and scroll-margin integration. Chrome devrel published focus-not-obscured debugging guide.

    Real production example

    Design token --scroll-margin-top per layout variant — marketing long header vs app compact header.

    Enterprise use case

    Modal component CSS contract: z-index scale, scroll-margin on dialog content, body scroll lock via :has.

    Accessibility considerations

    Focus not obscured audits — tab through form behind sticky footer CTA; measure overlap in DevTools screenshot.

    Performance considerations

    body:has() can be expensive on huge DOM — scope to direct child dialog open selector where possible.

    SEO considerations

    N/A direct — focus management is operability not crawl.

    Scalability considerations

    Global z-index token scale — 100 header, 200 dropdown, 1000 modal — prevents ad-hoc 99999 wars obscuring focus unpredictably.

    Common production issues

    Incidents: iOS Safari address bar resize broke scroll-margin calc — focus hidden under header. Intercom widget z-index above modal focus ring.

    Debugging guide

    Tab and screenshot each step — overlay semi-transparent debug mode shows stacking. Chrome Layers panel for z-index context.

    Best practices

    • Define z-index token scale in design system.
    • Apply scroll-margin-top on all focusable elements matching sticky header height.
    • Use :has(.dialog[open]) for scroll lock instead of inline body styles when possible.
    • Test focus not obscured with 400% zoom.
    • Pair CSS scroll-margin with JS focus trap in modals.

    Anti-patterns

    • z-index: 999999 on marketing widgets without focus consideration.
    • Removing focus outline on h1 programmatic focus without alternative cue.
    • Fixed bottom bars without padding-bottom on main content — focus lands under bar.

    Trade-offs

    • Benefit: scroll-margin fixes most sticky header obscuring without JS.
    • Cost: Per-layout header height variables multiply in white-label.

    Architecture review questions

    • Is focused control ever hidden under sticky/fixed UI?
    • Does modal z-index exceed all page chrome?
    • Is scroll-margin set for header height on focusable elements?

    Interview questions

    How does CSS fix focus obscured by sticky header?(Advanced)

    scroll-margin-top on :focus/:focus-visible matching header height; ensure modal z-index above header; optionally :has dialog open to hide/shrink sticky promo. Test 2.4.11 with keyboard tab + zoom.

    Follow-up: JS vs CSS focus management split?

    Hands-on exercise

    Exercise: Add sticky header + form; tab to first field; fix scroll-margin; open modal and verify focus ring not obscured.

    Staff engineer notes

    • Film keyboard tab through checkout — obscured focus visible in 30 seconds.
    • Z-index tokens prevent focus wars across teams.

    Common pitfalls

    • calc header height wrong on mobile dynamic viewport — use env(safe-area-inset-top) too.

    Try it yourself

    Edit the CSS panel — the preview updates live. Use DevTools Performance and Accessibility panels to validate.

    Try it yourself

    Preview

    Summary

    CSS focus management uses scroll-margin, z-index scales, :focus-visible rings, and :has() scroll lock so focused elements remain visible and unobscured — meeting WCAG 2.4.11 across sticky headers, modals, and widgets.

    Key takeaways

    • scroll-margin and z-index tokens fix most focus-not-obscured failures.
    • Pair CSS with JS focus trap for modals.
    • Test at 400% zoom with sticky chrome present.
    Ready to mark this lesson complete?Track your journey across the entire course.