CSS scroll target group

CSS 스크롤 스파이

The browser marks the navigation link for the section in view.

···
html
<div class="v spy"><header>SCROLL SPY <span id="section">section 1</span></header><div class="stage spy-stage"><nav><a href="#part1" id="link1">01 Intro</a><a href="#part2" id="link2">02 Details</a><a href="#part3" id="link3">03 Finish</a></nav><div class="spy-page" id="page"><section id="part1">INTRO <i></i><i></i></section><section id="part2">DETAILS <i></i><i></i></section><section id="part3">FINISH <i></i><i></i></section></div></div><div class="muted">scroll position → current link</div></div>
css
.v{width:min(94vw,760px);height:min(88vh,320px);padding:clamp(9px,2.4vmin,18px);border:1px solid var(--line);border-radius:12px;background:var(--surface);font:500 clamp(12px,2.4vmin,16px)/1.3 var(--font-sans,system-ui,sans-serif);display:flex;flex-direction:column;gap:clamp(5px,1.4vmin,10px);overflow:hidden}.v header{display:flex;align-items:center;justify-content:space-between;gap:8px;color:var(--accent);font-weight:700}.v header span{color:var(--muted);font-weight:500}.v .stage{flex:1;min-height:0;position:relative}.v .muted{color:var(--muted)}.v .active{background:color-mix(in srgb,var(--accent) 18%,var(--surface));border-color:var(--accent)!important;color:var(--fg)}.v button{font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--line);border-radius:7px;padding:5px 8px;cursor:pointer}.spy-stage{display:flex;gap:8px}.spy nav{width:36%;display:flex;flex-direction:column;gap:5px;scroll-target-group:auto}.spy nav a{font-size:clamp(12px,2.3vmin,14px);padding:6px;border:1px solid var(--line);border-radius:6px;color:var(--muted);text-decoration:none;white-space:nowrap}.spy nav a:target-current,.spy nav a.on{color:var(--accent);border-color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,var(--surface))}.spy-page{width:64%;overflow:auto;scroll-snap-type:y mandatory;border:1px solid var(--line);border-radius:7px;scroll-behavior:smooth}.spy-page section{height:100%;min-height:100%;scroll-snap-align:start;padding:8px;background:var(--bg);font-weight:700;color:var(--accent-2)}.spy-page section i{display:block;height:9px;background:var(--line);border-radius:4px;margin-top:9px}.spy-page section i:last-child{width:64%}
js
let i=0;const page=document.getElementById("page"),parts=[...page.querySelectorAll("section")];function show(n){i=n;parts[i].scrollIntoView({behavior:"smooth",block:"start"});for(let j=0;j<3;j++)document.getElementById("link"+(j+1)).classList.toggle("on",j===i);document.getElementById("section").textContent="section "+(i+1)}show(0);let timer=setInterval(()=>show((i+1)%3),950);document.querySelectorAll(".spy nav a").forEach((a,j)=>a.addEventListener("click",e=>{e.preventDefault();clearInterval(timer);show(j)}))

Setting `scroll-target-group: auto` on a navigation group lets `:target-current` match the link for the visible section. This can replace custom scroll-observer code for a table of contents.

The demo scrolls a document and moves the active item. Supporting browsers compute the state natively; other browsers can use an observer-based fallback.

When to use

Use it to keep a long document’s table of contents in sync with scrolling.

Open as page ↗