Back to CSS Sidebar Layouts Target-active Highlight Light JS
Share
.sb-act {
  display: grid; grid-template-columns: 240px 1fr;
  min-height: 480px;
  font-family: 'Georgia', 'Times New Roman', serif;
  background: #f4ecdb;
  color: #2a1a10;
  border-radius: 0; overflow: hidden;
  border: 1px solid #2a1a10;
}
.sb-act-side {
  background: #ede0c4;
  border-right: 1px solid #2a1a10;
  padding: 22px 18px;
  display: flex; flex-direction: column; gap: 14px;
}
.sb-act-side header { padding-bottom: 14px; border-bottom: 2px solid #2a1a10; }
.sb-act-issue { font-family: 'Inter', sans-serif; font-size: 10.5px; font-weight: 700; letter-spacing: 0.22em; color: #8a4a2c; text-transform: uppercase; }
.sb-act-side h3 { margin: 4px 0 0; font-size: 30px; font-weight: 500; font-style: italic; color: #2a1a10; letter-spacing: -0.02em; line-height: 1; }
.sb-act-side ol { list-style: none; margin: 0; padding: 0; counter-reset: act; display: flex; flex-direction: column; gap: 0; }
.sb-act-side li { counter-increment: act; }
.sb-act-side a {
  position: relative;
  display: grid; grid-template-columns: 26px 1fr auto;
  align-items: baseline; gap: 12px;
  padding: 10px 4px;
  font-size: 16px; color: #2a1a10; text-decoration: none;
  border-bottom: 1px dotted #b89668;
  transition: padding-left 0.16s, color 0.16s;
}
.sb-act-side a::before {
  content: counter(act, decimal-leading-zero);
  font-family: 'Inter', sans-serif;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: #8a4a2c;
  align-self: center;
}
.sb-act-side a span { font-style: italic; }
.sb-act-side a em { font-family: 'Inter', sans-serif; font-style: normal; font-size: 10.5px; font-weight: 600; letter-spacing: 0.04em; color: #8a4a2c; }
.sb-act-side a:hover { padding-left: 4px; color: #8a1f2c; }
.sb-act-side footer { margin-top: auto; padding-top: 14px; border-top: 1px solid #2a1a10; font-family: 'Inter', sans-serif; font-size: 10.5px; letter-spacing: 0.18em; color: #8a4a2c; text-align: center; text-transform: uppercase; }
.sb-act-main { padding: 28px 32px; overflow-y: auto; max-height: 480px; background: #fbf5e6; }
.sb-act-sec { padding: 18px 0; border-bottom: 1px solid #d8c598; }
.sb-act-sec:last-child { border-bottom: 0; }
.sb-act-page { font-family: 'Inter', sans-serif; font-size: 10.5px; font-weight: 700; letter-spacing: 0.2em; color: #8a1f2c; text-transform: uppercase; }
.sb-act-sec h2 { margin: 4px 0 8px; font-family: 'Playfair Display', 'Georgia', serif; font-size: clamp(28px, 4vw, 44px); font-weight: 900; color: #2a1a10; line-height: 0.96; letter-spacing: -0.02em; }
.sb-act-sec p  { margin: 0; font-size: 14.5px; color: #3c2818; line-height: 1.7; max-width: 540px; }
.sb-act-sec code { font-family: 'JetBrains Mono', monospace; font-size: 12.5px; color: #8a1f2c; background: rgba(138,31,44,0.08); padding: 1px 5px; border-radius: 0; font-weight: 600; }
/* Default first-link active when no :target yet */
.sb-act-side a[href="#sb-act-overview"] { padding-left: 4px; color: #8a1f2c; font-weight: 700; }
.sb-act-side a[href="#sb-act-overview"]::after { content: '·'; font-family: serif; font-size: 22px; line-height: 0; color: #8a1f2c; margin-left: 4px; }
/* Override default when any section is targeted */
.sb-act:has(:target) .sb-act-side a[href="#sb-act-overview"] { padding-left: 0; color: #2a1a10; font-weight: 400; }
.sb-act:has(:target) .sb-act-side a[href="#sb-act-overview"]::after { content: ''; }
/* Highlight the link matching the targeted section */
.sb-act:has(#sb-act-overview:target)   .sb-act-side a[href="#sb-act-overview"],
.sb-act:has(#sb-act-features:target)   .sb-act-side a[href="#sb-act-features"],
.sb-act:has(#sb-act-pricing:target)    .sb-act-side a[href="#sb-act-pricing"],
.sb-act:has(#sb-act-changelog:target)  .sb-act-side a[href="#sb-act-changelog"]   { padding-left: 4px; color: #8a1f2c; font-weight: 700; }
.sb-act:has(#sb-act-overview:target)   .sb-act-side a[href="#sb-act-overview"]::after,
.sb-act:has(#sb-act-features:target)   .sb-act-side a[href="#sb-act-features"]::after,
.sb-act:has(#sb-act-pricing:target)    .sb-act-side a[href="#sb-act-pricing"]::after,
.sb-act:has(#sb-act-changelog:target)  .sb-act-side a[href="#sb-act-changelog"]::after { content: '·'; font-family: serif; font-size: 22px; line-height: 0; color: #8a1f2c; margin-left: 4px; }
<div class="sb-act" id="sb-act-root">
  <aside class="sb-act-side" aria-label="Primary">
    <header>
      <span class="sb-act-issue">Issue No. 47</span>
      <h3>Contents</h3>
    </header>
    <nav aria-label="Primary">
      <ol>
        <li><a href="#sb-act-overview"   data-tgt="overview"><span>Overview</span><em>p. 04</em></a></li>
        <li><a href="#sb-act-features"   data-tgt="features"><span>Features</span><em>p. 12</em></a></li>
        <li><a href="#sb-act-pricing"    data-tgt="pricing"><span>Pricing</span><em>p. 28</em></a></li>
        <li><a href="#sb-act-changelog"  data-tgt="changelog"><span>Changelog</span><em>p. 36</em></a></li>
      </ol>
    </nav>
    <footer>
      Spring · MMXXVI
    </footer>
  </aside>
  <main class="sb-act-main">
    <section id="sb-act-overview" class="sb-act-sec">
      <span class="sb-act-page">— Page 04</span>
      <h2>Overview.</h2>
      <p>Click a link to the left — the <code>:target</code> pseudo-class highlights it automatically. Sibling selectors do the work; <code>history.pushState</code> keeps the host page from jumping.</p>
    </section>
    <section id="sb-act-features" class="sb-act-sec">
      <span class="sb-act-page">— Page 12</span>
      <h2>Features.</h2>
      <p>The CSS pattern is <code>:has(#section:target) a[href="#section"]</code>. No JavaScript needed for the highlighting itself.</p>
    </section>
    <section id="sb-act-pricing" class="sb-act-sec">
      <span class="sb-act-page">— Page 28</span>
      <h2>Pricing.</h2>
      <p>Hash-based routing works in any browser. No router library, no framework dependency, no virtual DOM.</p>
    </section>
    <section id="sb-act-changelog" class="sb-act-sec">
      <span class="sb-act-page">— Page 36</span>
      <h2>Changelog.</h2>
      <p>Each section is a real <code>&lt;section&gt;</code> with an id — semantic, bookmarkable, and shareable.</p>
    </section>
  </main>
</div>
/* :target stays the engine — JS only updates the hash without scrolling. */
document.querySelectorAll('.sb-act-side a').forEach(function (a) {
  a.addEventListener('click', function (e) {
    e.preventDefault();
    var hash = a.getAttribute('href');
    if (hash && hash.charAt(0) === '#') history.pushState(null, '', hash);
  });
});
Live preview Edit any tab — preview updates live Ready