Introduction
The web platform's anchor-link model lets any URL point at a specific element via the fragment identifier. This has powered in-page navigation for decades — click a link, the browser scrolls, :target fires. But designers have long wanted to style the surrounding context too, not just the target itself.
Origins of the proposal
The CSS Overflow 5 specification introduced :target-before and :target-after alongside scroll markers. They match scroll markers that precede or follow the active one inside a scroll-marker-group — giving carousels and reading-progress indicators a pure-CSS solution. Chrome 141 ships both pseudo-classes.
Scroll marker groups
A scroll-marker-group container holds ::scroll-marker pseudo-elements. The browser tracks which marker is "current" (:target-current), and all markers before it match :target-before while those after match :target-after. Progress indicators, step wizards, and breadcrumb trails are the canonical use cases.
The CSS cascade
Pseudo-classes participate in specificity like any other selector. section:target-before has specificity (0,1,1) — same as section:hover. Authors can layer these on top of existing styles without fighting inheritance. The pseudo-classes also compose with combinators, allowing section:target-after + p or :target-before > h2.
Accessibility considerations
Colour alone is never sufficient. The "coming up next" label injected via ::after on :target-after h2 is announced by screen readers because ::after content is accessible text. Pair border accents with a visible change in text or icon content for users who cannot distinguish colours. WCAG 1.4.1 applies.
Fallback strategy
A JavaScript shim using hashchange and sibling traversal covers browsers that don't yet support :target-before / :target-after. Apply CSS classes .js-before, .js-target, .js-after — the same styles apply and the UX is identical. Progressive enhancement at its most literal.
Current limitations
As of Chrome 141, :target-before and :target-after are specified primarily for scroll markers inside a scroll-marker-group. Applying them to arbitrary sections (as this demo does via the JS shim) relies on a broader interpretation that may not be supported natively outside scroll-marker-groups. Watch the spec for updates.