Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 5.5A1.5 1.5 0 0 1 6 4h13.5v14H6a1.5 1.5 0 0 0-1.5 1.5z"/><path d="M4.5 19.5A1.5 1.5 0 0 0 6 21h13.5v-3"/></symbol>
</svg>
<div><span class="lbl">Docs navigation with an “On this page” list</span>
<div class="app">
  <nav class="dn" aria-label="Quill UI docs">
    <a class="dn-brand" href="#"><svg aria-hidden="true"><use href="#i-book"/></svg>Quill UI<span class="dn-version">v3.2</span></a>
    <p class="dn-heading">Getting started</p>
    <ul class="dn-list"><li><a class="dn-link" href="#">Introduction</a></li><li><a class="dn-link" href="#" aria-current="page">Installation</a></li><li><a class="dn-link" href="#">Quick start</a></li></ul>
    <p class="dn-heading">Components</p>
    <ul class="dn-list"><li><a class="dn-link" href="#">Button</a></li><li><a class="dn-link" href="#">Dialog</a></li><li><a class="dn-link" href="#">Menu</a></li><li><a class="dn-link" href="#">Tabs</a></li></ul>
    <p class="dn-heading">Guides</p>
    <ul class="dn-list"><li><a class="dn-link" href="#">Theming</a></li><li><a class="dn-link" href="#">Accessibility</a></li></ul>
  </nav>
  <article class="doc" id="quill-doc" tabindex="0" aria-label="Installation">
      <section id="q-req"><h2>Installation</h2><p class="lead">Quill UI ships as one npm package that holds the components, the stylesheet and the TypeScript types. It works with any bundler, or without one.</p><h3>Requirements</h3><p>Node.js 20 or newer and a bundler that understands ES modules, such as Vite, Rollup or webpack 5. Without a build step you only need a browser released in the last two years.</p></section>
      <section id="q-npm"><h3>Install with npm</h3><p>Add the package to your project. pnpm and Yarn take the same package name.</p><pre><code>npm install @quill-ui/core</code></pre></section>
      <section id="q-cdn"><h3>Use it without a build</h3><p>For prototypes, import the ES module build straight from a URL in a module script.</p><pre><code>import { Button } from "https://esm.example/[email protected]";</code></pre></section>
      <section id="q-styles"><h3>Import the styles</h3><p>Components are unstyled until the stylesheet is imported once, usually in the entry file of your app.</p><pre><code>import "@quill-ui/core/styles.css";</code></pre></section>
      <section id="q-next"><h3>Next steps</h3><p>Read Quick start to build a first screen, or go to Theming to swap the colour tokens for your own brand.</p></section>
  </article>
  <nav class="toc" aria-label="On this page" data-spy="quill-doc">
    <p class="toc-title">On this page</p>
    <ul class="toc-list"><li><a class="toc-link" href="#q-req">Requirements</a></li><li><a class="toc-link" href="#q-npm">Install with npm</a></li><li><a class="toc-link" href="#q-cdn">Use it without a build</a></li><li><a class="toc-link" href="#q-styles">Import the styles</a></li><li><a class="toc-link" href="#q-next">Next steps</a></li></ul>
  </nav>
</div></div>
<div><span class="lbl">Tutorial steps that fill in as you read</span>
<div class="app">
  <nav class="st" aria-label="Tutorial steps" data-spy="launch-doc">
    <p class="st-kicker">Launchpad tutorial</p>
    <p class="st-title"><b>Deploy your first app</b></p>
    <p class="st-meta">5 steps · about 10 minutes</p>
    <ol class="st-list">
      <li><a class="st-link" href="#l-create"><span class="st-num" aria-hidden="true">1</span><span class="st-text"><b><span class="sr-only">Step 1: </span>Create a project</b><small>1 min</small></span></a></li>
      <li><a class="st-link" href="#l-connect"><span class="st-num" aria-hidden="true">2</span><span class="st-text"><b><span class="sr-only">Step 2: </span>Connect a repository</b><small>2 min</small></span></a></li>
      <li><a class="st-link" href="#l-env"><span class="st-num" aria-hidden="true">3</span><span class="st-text"><b><span class="sr-only">Step 3: </span>Set environment variables</b><small>3 min</small></span></a></li>
      <li><a class="st-link" href="#l-deploy"><span class="st-num" aria-hidden="true">4</span><span class="st-text"><b><span class="sr-only">Step 4: </span>Deploy</b><small>2 min</small></span></a></li>
      <li><a class="st-link" href="#l-domain"><span class="st-num" aria-hidden="true">5</span><span class="st-text"><b><span class="sr-only">Step 5: </span>Add a custom domain</b><small>2 min</small></span></a></li>
    </ol>
  </nav>
  <article class="doc" id="launch-doc" tabindex="0" aria-label="Deploy your first app">
      <section id="l-create"><h2>Deploy your first app</h2><p class="lead">Five steps from an empty dashboard to a live site on your own domain.</p><h3>Step 1: Create a project</h3><p>Open the dashboard, choose New project and give it a name. The name becomes part of the preview address, so keep it short.</p></section>
      <section id="l-connect"><h3>Step 2: Connect a repository</h3><p>Pick GitHub, GitLab or Bitbucket and select the repository. Launchpad reads the framework from package.json and suggests a build command.</p><pre><code>npm run build</code></pre></section>
      <section id="l-env"><h3>Step 3: Set environment variables</h3><p>Add the keys your app reads at build time. Values are encrypted and never printed in build logs.</p><pre><code>DATABASE_URL=postgres://[email protected]/shop</code></pre></section>
      <section id="l-deploy"><h3>Step 4: Deploy</h3><p>Press Deploy. A first build usually takes one to two minutes; later builds reuse the cache and finish faster.</p></section>
      <section id="l-domain"><h3>Step 5: Add a custom domain</h3><p>Under Domains, add your domain and create the CNAME record shown. The certificate is issued as soon as DNS resolves.</p></section>
  </article>
</div></div>
CSS
/* App shell: docs nav | scrolling article | on-this-page list */
.app{display:flex;height:560px;background:#fff;border:1px solid #d6dce5;border-radius:16px;overflow:hidden;
  box-shadow:0 1px 2px rgba(15,23,42,.05),0 18px 40px -22px rgba(15,23,42,.28)}
.doc{flex:1;min-width:0;overflow-y:auto;padding:28px 36px 440px;scroll-behavior:smooth}
.doc section{scroll-margin-top:24px}
.doc:focus-visible{outline:2px solid #0369a1;outline-offset:-2px}

.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* Docs navigation */
.dn{--dn-accent:#0369a1;--dn-active-ink:#075985;--dn-ink:#475569;--dn-strong:#0f172a;--dn-muted:#5b6474;--dn-line:#e2e8f0;
  flex:0 0 232px;padding:18px 16px;background:#fbfcfd;border-right:1px solid var(--dn-line);overflow-y:auto;font-size:.875rem}
.dn-brand{display:flex;align-items:center;gap:9px;margin-bottom:6px;padding:2px 4px;border-radius:8px;color:var(--dn-strong);font-weight:750;text-decoration:none}
.dn-brand svg{width:22px;height:22px;color:var(--dn-accent)}
.dn-version{margin-left:auto;padding:0 8px;border:1px solid #cfd8e3;border-radius:999px;color:var(--dn-ink);font-size:.7rem;font-weight:600;line-height:1.7}
.dn-heading{margin:18px 0 6px;padding:0 4px;font-size:.8125rem;font-weight:700;color:var(--dn-strong)}
.dn-list{list-style:none;margin:0 0 0 4px;padding:0;border-left:1px solid var(--dn-line)}
.dn-link{display:block;margin-left:-1px;padding:5px 12px;border-left:2px solid transparent;color:var(--dn-ink);text-decoration:none;
  transition:color .15s,border-color .15s}
.dn-link:hover{color:var(--dn-strong);border-left-color:#94a3b8}
.dn-link[aria-current="page"]{border-left-color:var(--dn-accent);color:var(--dn-active-ink);font-weight:600}

/* On this page */
.toc{flex:0 0 204px;padding:28px 18px;border-left:1px solid var(--dn-line,#e2e8f0);font-size:.8125rem}
.toc-title{margin:0 0 10px;font-size:.6875rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#5b6474}
.toc-list{list-style:none;margin:0;padding:0;border-left:1px solid #e2e8f0}
.toc-link{display:block;margin-left:-1px;padding:4px 12px;border-left:2px solid transparent;color:#5b6474;text-decoration:none;transition:color .15s,border-color .15s}
.toc-link:hover{color:#0f172a}
.toc-link[aria-current]{border-left-color:#0369a1;color:#075985;font-weight:600}
.dn a:focus-visible,.toc a:focus-visible,.st a:focus-visible{outline:2px solid #0369a1;outline-offset:2px}

/* Tutorial steps: numbered markers joined by a line; steps before the current one turn green (:has) */
.st{--st-accent:#0369a1;--st-done:#047857;flex:0 0 288px;padding:24px 22px;border-right:1px solid #e2e8f0;background:#fbfcfd;overflow-y:auto;font-size:.875rem}
.st-kicker{margin:0;font-size:.72rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--st-accent)}
.st-title{margin:4px 0 2px;font-size:1.05rem;color:#0f172a}
.st-meta{margin:0 0 18px;font-size:.78rem;color:#5b6474}
.st-list{list-style:none;margin:0;padding:0}
.st-list li{position:relative;padding-bottom:14px}
.st-list li:not(:last-child)::before{content:"";position:absolute;left:14px;top:32px;bottom:2px;width:2px;border-radius:2px;background:#e2e8f0;transition:background-color .2s}
.st-link{display:flex;align-items:flex-start;gap:12px;border-radius:8px;color:#475569;text-decoration:none}
.st-num{flex:none;display:grid;place-items:center;width:30px;height:30px;border:2px solid #94a3b8;border-radius:50%;background:#fff;
  color:#475569;font-size:.78rem;font-weight:700;transition:background-color .2s,border-color .2s,color .2s}
.st-text{padding-top:4px}
.st-text b{display:block;color:#334155;font-weight:600;line-height:1.35}
.st-text small{font-size:.75rem;color:#5b6474}
.st-link:hover .st-text b{color:#0f172a}
.st-link[aria-current] .st-num{border-color:var(--st-accent);background:var(--st-accent);color:#fff;box-shadow:0 0 0 4px #e0f2fe}
.st-link[aria-current] .st-text b{color:#0f172a;font-weight:700}
.st-list li:has(~ li [aria-current]) .st-num{border-color:var(--st-done);background:#ecfdf5;color:var(--st-done)}
.st-list li:has(~ li [aria-current])::before{background:var(--st-done)}

@media (max-width:900px){.toc{display:none}}
@media (max-width:640px){
  .app{flex-direction:column;height:auto}
  .dn,.st{flex-basis:auto;border-right:0;border-bottom:1px solid #e2e8f0}
  .doc{flex:none;height:420px;padding:22px 20px 340px}
}
@media (prefers-reduced-motion:reduce){.doc{scroll-behavior:auto}.dn-link,.toc-link,.st-num,.st-list li::before{transition:none}}
JavaScript
// Scroll-spy: marks the link whose section has reached the top of the scrolling article with aria-current.
document.querySelectorAll('[data-spy]').forEach(function (toc) {
  var scroller = document.getElementById(toc.getAttribute('data-spy'));
  if (!scroller) return;
  var links = Array.prototype.slice.call(toc.querySelectorAll('a[href^="#"]'));
  var targets = links.map(function (a) { return document.getElementById(a.hash.slice(1)); });
  function update() {
    var line = scroller.getBoundingClientRect().top + 72, current = 0;
    targets.forEach(function (t, i) { if (t && t.getBoundingClientRect().top <= line) current = i; });
    if (scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 2) current = links.length - 1;
    links.forEach(function (a, i) {
      if (i === current) a.setAttribute('aria-current', 'true'); else a.removeAttribute('aria-current');
    });
  }
  scroller.addEventListener('scroll', update, { passive: true });
  update();
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 5.5A1.5 1.5 0 0 1 6 4h13.5v14H6a1.5 1.5 0 0 0-1.5 1.5z"/><path d="M4.5 19.5A1.5 1.5 0 0 0 6 21h13.5v-3"/></symbol>
</svg>
<div><span class="lbl">Docs navigation with an “On this page” list</span>
<div class="app d-flex">
  <nav class="dn" aria-label="Quill UI docs">
    <a class="dn-brand" href="#"><svg aria-hidden="true"><use href="#i-book"/></svg>Quill UI<span class="dn-version">v3.2</span></a>
    <p class="dn-heading">Getting started</p>
    <ul class="nav flex-column dn-list"><li class="nav-item"><a class="nav-link" href="#">Introduction</a></li><li class="nav-item"><a class="nav-link" href="#" aria-current="page">Installation</a></li><li class="nav-item"><a class="nav-link" href="#">Quick start</a></li></ul>
    <p class="dn-heading">Components</p>
    <ul class="nav flex-column dn-list"><li class="nav-item"><a class="nav-link" href="#">Button</a></li><li class="nav-item"><a class="nav-link" href="#">Dialog</a></li><li class="nav-item"><a class="nav-link" href="#">Menu</a></li><li class="nav-item"><a class="nav-link" href="#">Tabs</a></li></ul>
    <p class="dn-heading">Guides</p>
    <ul class="nav flex-column dn-list"><li class="nav-item"><a class="nav-link" href="#">Theming</a></li><li class="nav-item"><a class="nav-link" href="#">Accessibility</a></li></ul>
  </nav>
  <article class="doc" id="quill-doc" tabindex="0" aria-label="Installation" data-bs-spy="scroll" data-bs-target="#quill-toc" data-bs-root-margin="0px 0px -87%">
      <section id="q-req"><h2>Installation</h2><p class="lead">Quill UI ships as one npm package that holds the components, the stylesheet and the TypeScript types. It works with any bundler, or without one.</p><h3>Requirements</h3><p>Node.js 20 or newer and a bundler that understands ES modules, such as Vite, Rollup or webpack 5. Without a build step you only need a browser released in the last two years.</p></section>
      <section id="q-npm"><h3>Install with npm</h3><p>Add the package to your project. pnpm and Yarn take the same package name.</p><pre><code>npm install @quill-ui/core</code></pre></section>
      <section id="q-cdn"><h3>Use it without a build</h3><p>For prototypes, import the ES module build straight from a URL in a module script.</p><pre><code>import { Button } from "https://esm.example/[email protected]";</code></pre></section>
      <section id="q-styles"><h3>Import the styles</h3><p>Components are unstyled until the stylesheet is imported once, usually in the entry file of your app.</p><pre><code>import "@quill-ui/core/styles.css";</code></pre></section>
      <section id="q-next"><h3>Next steps</h3><p>Read Quick start to build a first screen, or go to Theming to swap the colour tokens for your own brand.</p></section>
  </article>
  <nav class="toc" id="quill-toc" aria-label="On this page">
    <p class="toc-title">On this page</p>
    <ul class="nav flex-column toc-list"><li class="nav-item"><a class="nav-link" href="#q-req">Requirements</a></li><li class="nav-item"><a class="nav-link" href="#q-npm">Install with npm</a></li><li class="nav-item"><a class="nav-link" href="#q-cdn">Use it without a build</a></li><li class="nav-item"><a class="nav-link" href="#q-styles">Import the styles</a></li><li class="nav-item"><a class="nav-link" href="#q-next">Next steps</a></li></ul>
  </nav>
</div></div>
<div><span class="lbl">Tutorial steps that fill in as you read</span>
<div class="app d-flex">
  <nav class="st" id="launch-steps" aria-label="Tutorial steps">
    <p class="st-kicker">Launchpad tutorial</p>
    <p class="st-title"><b>Deploy your first app</b></p>
    <p class="st-meta">5 steps · about 10 minutes</p>
    <ol class="st-list">
      <li><a class="st-link" href="#l-create"><span class="st-num" aria-hidden="true">1</span><span class="st-text"><b><span class="visually-hidden">Step 1: </span>Create a project</b><small>1 min</small></span></a></li>
      <li><a class="st-link" href="#l-connect"><span class="st-num" aria-hidden="true">2</span><span class="st-text"><b><span class="visually-hidden">Step 2: </span>Connect a repository</b><small>2 min</small></span></a></li>
      <li><a class="st-link" href="#l-env"><span class="st-num" aria-hidden="true">3</span><span class="st-text"><b><span class="visually-hidden">Step 3: </span>Set environment variables</b><small>3 min</small></span></a></li>
      <li><a class="st-link" href="#l-deploy"><span class="st-num" aria-hidden="true">4</span><span class="st-text"><b><span class="visually-hidden">Step 4: </span>Deploy</b><small>2 min</small></span></a></li>
      <li><a class="st-link" href="#l-domain"><span class="st-num" aria-hidden="true">5</span><span class="st-text"><b><span class="visually-hidden">Step 5: </span>Add a custom domain</b><small>2 min</small></span></a></li>
    </ol>
  </nav>
  <article class="doc" id="launch-doc" tabindex="0" aria-label="Deploy your first app" data-bs-spy="scroll" data-bs-target="#launch-steps" data-bs-root-margin="0px 0px -87%">
      <section id="l-create"><h2>Deploy your first app</h2><p class="lead">Five steps from an empty dashboard to a live site on your own domain.</p><h3>Step 1: Create a project</h3><p>Open the dashboard, choose New project and give it a name. The name becomes part of the preview address, so keep it short.</p></section>
      <section id="l-connect"><h3>Step 2: Connect a repository</h3><p>Pick GitHub, GitLab or Bitbucket and select the repository. Launchpad reads the framework from package.json and suggests a build command.</p><pre><code>npm run build</code></pre></section>
      <section id="l-env"><h3>Step 3: Set environment variables</h3><p>Add the keys your app reads at build time. Values are encrypted and never printed in build logs.</p><pre><code>DATABASE_URL=postgres://[email protected]/shop</code></pre></section>
      <section id="l-deploy"><h3>Step 4: Deploy</h3><p>Press Deploy. A first build usually takes one to two minutes; later builds reuse the cache and finish faster.</p></section>
      <section id="l-domain"><h3>Step 5: Add a custom domain</h3><p>Under Domains, add your domain and create the CNAME record shown. The certificate is issued as soon as DNS resolves.</p></section>
  </article>
</div></div>
CSS
/* App shell. Bootstrap ScrollSpy (data-bs-spy on the article) adds .active to the matching .nav-link. */
.app{display:flex;height:560px;background:#fff;border:1px solid #d6dce5;border-radius:16px;overflow:hidden;
  box-shadow:0 1px 2px rgba(15,23,42,.05),0 18px 40px -22px rgba(15,23,42,.28)}
.doc{position:relative;flex:1;min-width:0;overflow-y:auto;padding:28px 36px 440px;scroll-behavior:smooth}
.doc section{scroll-margin-top:24px}
.doc:focus-visible{outline:2px solid #0369a1;outline-offset:-2px}

/* Docs navigation */
.dn{--dn-accent:#0369a1;--dn-active-ink:#075985;--dn-ink:#475569;--dn-strong:#0f172a;--dn-muted:#5b6474;--dn-line:#e2e8f0;
  flex:0 0 232px;padding:18px 16px;background:#fbfcfd;border-right:1px solid var(--dn-line);overflow-y:auto;font-size:.875rem}
.dn-brand{display:flex;align-items:center;gap:9px;margin-bottom:6px;padding:2px 4px;border-radius:8px;color:var(--dn-strong);font-weight:750;text-decoration:none}
.dn-brand svg{width:22px;height:22px;color:var(--dn-accent)}
.dn-version{margin-left:auto;padding:0 8px;border:1px solid #cfd8e3;border-radius:999px;color:var(--dn-ink);font-size:.7rem;font-weight:600;line-height:1.7}
.dn-heading{margin:18px 0 6px;line-height:1.5;padding:0 4px;font-size:.8125rem;font-weight:700;color:var(--dn-strong)}
.dn-list{margin:0 0 0 4px;flex-wrap:nowrap;border-left:1px solid var(--dn-line)}
.dn .nav-link{display:block;font-size:.875rem;margin-left:-1px;padding:5px 12px;border-left:2px solid transparent;color:var(--dn-ink);text-decoration:none;
  transition:color .15s,border-color .15s}
.dn .nav-link:hover{color:var(--dn-strong);border-left-color:#94a3b8}
.dn .nav-link[aria-current="page"]{border-left-color:var(--dn-accent);color:var(--dn-active-ink);font-weight:600}

/* On this page */
.toc{flex:0 0 204px;padding:28px 18px;border-left:1px solid var(--dn-line,#e2e8f0);font-size:.8125rem}
.toc-title{margin:0 0 10px;font-size:.6875rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#5b6474}
.toc-list{flex-wrap:nowrap;border-left:1px solid #e2e8f0}
.toc .nav-link{display:block;font-size:.8125rem;margin-left:-1px;padding:4px 12px;border-left:2px solid transparent;color:#5b6474;text-decoration:none;transition:color .15s,border-color .15s}
.toc .nav-link:hover{color:#0f172a}
.toc .nav-link.active{border-left-color:#0369a1;color:#075985;font-weight:600}
.dn .nav-link:focus-visible,.toc .nav-link:focus-visible{box-shadow:none}
.dn a:focus-visible,.toc a:focus-visible,.st a:focus-visible{outline:2px solid #0369a1;outline-offset:2px}

/* Tutorial steps: numbered markers joined by a line; steps before the current one turn green (:has) */
.st{--st-accent:#0369a1;--st-done:#047857;flex:0 0 288px;padding:24px 22px;border-right:1px solid #e2e8f0;background:#fbfcfd;overflow-y:auto;font-size:.875rem}
.st-kicker{margin:0;font-size:.72rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--st-accent)}
.st-title{margin:4px 0 2px;font-size:1.05rem;color:#0f172a}
.st-meta{margin:0 0 18px;font-size:.78rem;color:#5b6474}
.st-list{list-style:none;margin:0;padding:0}
.st-list li{position:relative;padding-bottom:14px}
.st-list li:not(:last-child)::before{content:"";position:absolute;left:14px;top:32px;bottom:2px;width:2px;border-radius:2px;background:#e2e8f0;transition:background-color .2s}
.st-link{display:flex;align-items:flex-start;gap:12px;border-radius:8px;color:#475569;text-decoration:none}
.st-num{flex:none;display:grid;place-items:center;width:30px;height:30px;border:2px solid #94a3b8;border-radius:50%;background:#fff;
  color:#475569;font-size:.78rem;font-weight:700;transition:background-color .2s,border-color .2s,color .2s}
.st-text{padding-top:4px}
.st-text b{display:block;color:#334155;font-weight:600;line-height:1.35}
.st-text small{font-size:.75rem;color:#5b6474}
.st-link:hover .st-text b{color:#0f172a}
.st-link.active .st-num{border-color:var(--st-accent);background:var(--st-accent);color:#fff;box-shadow:0 0 0 4px #e0f2fe}
.st-link.active .st-text b{color:#0f172a;font-weight:700}
.st-list li:has(~ li .active) .st-num{border-color:var(--st-done);background:#ecfdf5;color:var(--st-done)}
.st-list li:has(~ li .active)::before{background:var(--st-done)}

@media (max-width:900px){.toc{display:none}}
@media (max-width:640px){
  .app{flex-direction:column;height:auto}
  .dn,.st{flex-basis:auto;border-right:0;border-bottom:1px solid #e2e8f0}
  .doc{flex:none;height:420px;padding:22px 20px 340px}
}
@media (prefers-reduced-motion:reduce){.doc{scroll-behavior:auto}.dn .nav-link,.toc .nav-link,.st-num,.st-list li::before{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 5.5A1.5 1.5 0 0 1 6 4h13.5v14H6a1.5 1.5 0 0 0-1.5 1.5z"/><path d="M4.5 19.5A1.5 1.5 0 0 0 6 21h13.5v-3"/></symbol>
</svg>
<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Docs navigation with an “On this page” list</span>
<div class="flex h-[560px] overflow-hidden rounded-2xl border border-[#d6dce5] bg-white shadow-[0_1px_2px_rgba(15,23,42,.05),0_18px_40px_-22px_rgba(15,23,42,.28)] max-[640px]:h-auto max-[640px]:flex-col">
  <nav class="flex-none basis-[232px] overflow-y-auto border-r border-[#e2e8f0] bg-[#fbfcfd] px-4 py-[18px] text-[.875rem] max-[640px]:basis-auto max-[640px]:border-r-0 max-[640px]:border-b" aria-label="Quill UI docs">
    <a class="mb-1.5 flex items-center gap-[9px] rounded-lg px-1 py-0.5 font-[750] text-[#0f172a] no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1]" href="#"><svg class="size-[22px] text-[#0369a1]" aria-hidden="true"><use href="#i-book"/></svg>Quill UI<span class="ml-auto rounded-full border border-[#cfd8e3] px-2 text-[.7rem] leading-[1.7] font-semibold text-[#475569]">v3.2</span></a>
    <p class="mt-[18px] mb-1.5 px-1 text-[.8125rem] font-bold text-[#0f172a]">Getting started</p>
    <ul class="m-0 ml-1 list-none border-l border-[#e2e8f0] p-0"><li><a class="-ml-px block border-l-2 border-transparent px-3 py-[5px] text-[#475569] no-underline transition-colors duration-150 hover:border-[#94a3b8] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current=page]:border-[#0369a1] aria-[current=page]:font-semibold aria-[current=page]:text-[#075985]" href="#">Introduction</a></li><li><a class="-ml-px block border-l-2 border-transparent px-3 py-[5px] text-[#475569] no-underline transition-colors duration-150 hover:border-[#94a3b8] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current=page]:border-[#0369a1] aria-[current=page]:font-semibold aria-[current=page]:text-[#075985]" href="#" aria-current="page">Installation</a></li><li><a class="-ml-px block border-l-2 border-transparent px-3 py-[5px] text-[#475569] no-underline transition-colors duration-150 hover:border-[#94a3b8] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current=page]:border-[#0369a1] aria-[current=page]:font-semibold aria-[current=page]:text-[#075985]" href="#">Quick start</a></li></ul>
    <p class="mt-[18px] mb-1.5 px-1 text-[.8125rem] font-bold text-[#0f172a]">Components</p>
    <ul class="m-0 ml-1 list-none border-l border-[#e2e8f0] p-0"><li><a class="-ml-px block border-l-2 border-transparent px-3 py-[5px] text-[#475569] no-underline transition-colors duration-150 hover:border-[#94a3b8] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current=page]:border-[#0369a1] aria-[current=page]:font-semibold aria-[current=page]:text-[#075985]" href="#">Button</a></li><li><a class="-ml-px block border-l-2 border-transparent px-3 py-[5px] text-[#475569] no-underline transition-colors duration-150 hover:border-[#94a3b8] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current=page]:border-[#0369a1] aria-[current=page]:font-semibold aria-[current=page]:text-[#075985]" href="#">Dialog</a></li><li><a class="-ml-px block border-l-2 border-transparent px-3 py-[5px] text-[#475569] no-underline transition-colors duration-150 hover:border-[#94a3b8] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current=page]:border-[#0369a1] aria-[current=page]:font-semibold aria-[current=page]:text-[#075985]" href="#">Menu</a></li><li><a class="-ml-px block border-l-2 border-transparent px-3 py-[5px] text-[#475569] no-underline transition-colors duration-150 hover:border-[#94a3b8] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current=page]:border-[#0369a1] aria-[current=page]:font-semibold aria-[current=page]:text-[#075985]" href="#">Tabs</a></li></ul>
    <p class="mt-[18px] mb-1.5 px-1 text-[.8125rem] font-bold text-[#0f172a]">Guides</p>
    <ul class="m-0 ml-1 list-none border-l border-[#e2e8f0] p-0"><li><a class="-ml-px block border-l-2 border-transparent px-3 py-[5px] text-[#475569] no-underline transition-colors duration-150 hover:border-[#94a3b8] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current=page]:border-[#0369a1] aria-[current=page]:font-semibold aria-[current=page]:text-[#075985]" href="#">Theming</a></li><li><a class="-ml-px block border-l-2 border-transparent px-3 py-[5px] text-[#475569] no-underline transition-colors duration-150 hover:border-[#94a3b8] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current=page]:border-[#0369a1] aria-[current=page]:font-semibold aria-[current=page]:text-[#075985]" href="#">Accessibility</a></li></ul>
  </nav>
  <article class="min-w-0 flex-1 overflow-y-auto scroll-smooth px-9 pt-7 pb-[440px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:scroll-auto max-[640px]:h-[420px] max-[640px]:flex-none max-[640px]:px-5 max-[640px]:pt-[22px] max-[640px]:pb-[340px]" id="quill-doc" tabindex="0" aria-label="Installation">
      <section class="scroll-mt-6" id="q-req"><h2 class="m-0 mb-2 text-[1.5rem] leading-[1.25] font-[750] tracking-[-.02em] text-[#0f172a]">Installation</h2><p class="m-0 mb-2 text-[1rem] leading-[1.7] text-[#475569]">Quill UI ships as one npm package that holds the components, the stylesheet and the TypeScript types. It works with any bundler, or without one.</p><h3 class="mt-7 mb-2 text-[1.1rem] leading-[1.35] font-bold text-[#0f172a]">Requirements</h3><p class="m-0 mb-3 text-[.92rem] leading-[1.7] text-[#334155]">Node.js 20 or newer and a bundler that understands ES modules, such as Vite, Rollup or webpack 5. Without a build step you only need a browser released in the last two years.</p></section>
      <section class="scroll-mt-6" id="q-npm"><h3 class="mt-7 mb-2 text-[1.1rem] leading-[1.35] font-bold text-[#0f172a]">Install with npm</h3><p class="m-0 mb-3 text-[.92rem] leading-[1.7] text-[#334155]">Add the package to your project. pnpm and Yarn take the same package name.</p><pre class="m-0 mb-3 overflow-x-auto rounded-[10px] bg-[#0f172a] px-3.5 py-3 text-[.8rem] text-[#e2e8f0]"><code class="font-mono">npm install @quill-ui/core</code></pre></section>
      <section class="scroll-mt-6" id="q-cdn"><h3 class="mt-7 mb-2 text-[1.1rem] leading-[1.35] font-bold text-[#0f172a]">Use it without a build</h3><p class="m-0 mb-3 text-[.92rem] leading-[1.7] text-[#334155]">For prototypes, import the ES module build straight from a URL in a module script.</p><pre class="m-0 mb-3 overflow-x-auto rounded-[10px] bg-[#0f172a] px-3.5 py-3 text-[.8rem] text-[#e2e8f0]"><code class="font-mono">import { Button } from "https://esm.example/[email protected]";</code></pre></section>
      <section class="scroll-mt-6" id="q-styles"><h3 class="mt-7 mb-2 text-[1.1rem] leading-[1.35] font-bold text-[#0f172a]">Import the styles</h3><p class="m-0 mb-3 text-[.92rem] leading-[1.7] text-[#334155]">Components are unstyled until the stylesheet is imported once, usually in the entry file of your app.</p><pre class="m-0 mb-3 overflow-x-auto rounded-[10px] bg-[#0f172a] px-3.5 py-3 text-[.8rem] text-[#e2e8f0]"><code class="font-mono">import "@quill-ui/core/styles.css";</code></pre></section>
      <section class="scroll-mt-6" id="q-next"><h3 class="mt-7 mb-2 text-[1.1rem] leading-[1.35] font-bold text-[#0f172a]">Next steps</h3><p class="m-0 mb-3 text-[.92rem] leading-[1.7] text-[#334155]">Read Quick start to build a first screen, or go to Theming to swap the colour tokens for your own brand.</p></section>
  </article>
  <nav class="flex-none basis-[204px] border-l border-[#e2e8f0] px-[18px] py-7 text-[.8125rem] max-[900px]:hidden" aria-label="On this page" data-spy="quill-doc">
    <p class="m-0 mb-2.5 text-[.6875rem] font-bold tracking-[.08em] text-[#5b6474] uppercase">On this page</p>
    <ul class="m-0 list-none border-l border-[#e2e8f0] p-0"><li><a class="-ml-px block border-l-2 border-transparent px-3 py-1 text-[#5b6474] no-underline transition-colors duration-150 hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current]:border-[#0369a1] aria-[current]:font-semibold aria-[current]:text-[#075985]" href="#q-req">Requirements</a></li><li><a class="-ml-px block border-l-2 border-transparent px-3 py-1 text-[#5b6474] no-underline transition-colors duration-150 hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current]:border-[#0369a1] aria-[current]:font-semibold aria-[current]:text-[#075985]" href="#q-npm">Install with npm</a></li><li><a class="-ml-px block border-l-2 border-transparent px-3 py-1 text-[#5b6474] no-underline transition-colors duration-150 hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current]:border-[#0369a1] aria-[current]:font-semibold aria-[current]:text-[#075985]" href="#q-cdn">Use it without a build</a></li><li><a class="-ml-px block border-l-2 border-transparent px-3 py-1 text-[#5b6474] no-underline transition-colors duration-150 hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current]:border-[#0369a1] aria-[current]:font-semibold aria-[current]:text-[#075985]" href="#q-styles">Import the styles</a></li><li><a class="-ml-px block border-l-2 border-transparent px-3 py-1 text-[#5b6474] no-underline transition-colors duration-150 hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:transition-none aria-[current]:border-[#0369a1] aria-[current]:font-semibold aria-[current]:text-[#075985]" href="#q-next">Next steps</a></li></ul>
  </nav>
</div></div>
<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Tutorial steps that fill in as you read</span>
<div class="flex h-[560px] overflow-hidden rounded-2xl border border-[#d6dce5] bg-white shadow-[0_1px_2px_rgba(15,23,42,.05),0_18px_40px_-22px_rgba(15,23,42,.28)] max-[640px]:h-auto max-[640px]:flex-col">
  <nav class="flex-none basis-[288px] overflow-y-auto border-r border-[#e2e8f0] bg-[#fbfcfd] px-[22px] py-6 text-[.875rem] max-[640px]:basis-auto max-[640px]:border-r-0 max-[640px]:border-b" aria-label="Tutorial steps" data-spy="launch-doc">
    <p class="m-0 text-[.72rem] font-bold tracking-[.08em] text-[#0369a1] uppercase">Launchpad tutorial</p>
    <p class="mt-1 mb-0.5 text-[1.05rem] text-[#0f172a]"><b>Deploy your first app</b></p>
    <p class="m-0 mb-[18px] text-[.78rem] text-[#5b6474]">5 steps · about 10 minutes</p>
    <ol class="m-0 list-none p-0">
      <li class="group/st relative pb-3.5 before:absolute before:top-8 before:bottom-0.5 before:left-3.5 before:w-0.5 before:rounded-sm before:bg-[#e2e8f0] before:transition-colors before:duration-200 motion-reduce:before:transition-none has-[~li_[aria-current]]:before:bg-[#047857]"><a class="group flex items-start gap-3 rounded-lg text-[#475569] no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1]" href="#l-create"><span class="grid size-[30px] flex-none place-items-center rounded-full border-2 border-[#94a3b8] bg-white text-[.78rem] font-bold text-[#475569] transition-colors duration-200 motion-reduce:transition-none group-aria-[current]:border-[#0369a1] group-aria-[current]:bg-[#0369a1] group-aria-[current]:text-white group-aria-[current]:shadow-[0_0_0_4px_#e0f2fe] group-has-[~li_[aria-current]]/st:border-[#047857] group-has-[~li_[aria-current]]/st:bg-[#ecfdf5] group-has-[~li_[aria-current]]/st:text-[#047857]" aria-hidden="true">1</span><span class="pt-1"><b class="block leading-[1.35] font-semibold text-[#334155] group-hover:text-[#0f172a] group-aria-[current]:font-bold group-aria-[current]:text-[#0f172a]"><span class="sr-only">Step 1: </span>Create a project</b><small class="text-[.75rem] text-[#5b6474]">1 min</small></span></a></li>
      <li class="group/st relative pb-3.5 before:absolute before:top-8 before:bottom-0.5 before:left-3.5 before:w-0.5 before:rounded-sm before:bg-[#e2e8f0] before:transition-colors before:duration-200 motion-reduce:before:transition-none has-[~li_[aria-current]]:before:bg-[#047857]"><a class="group flex items-start gap-3 rounded-lg text-[#475569] no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1]" href="#l-connect"><span class="grid size-[30px] flex-none place-items-center rounded-full border-2 border-[#94a3b8] bg-white text-[.78rem] font-bold text-[#475569] transition-colors duration-200 motion-reduce:transition-none group-aria-[current]:border-[#0369a1] group-aria-[current]:bg-[#0369a1] group-aria-[current]:text-white group-aria-[current]:shadow-[0_0_0_4px_#e0f2fe] group-has-[~li_[aria-current]]/st:border-[#047857] group-has-[~li_[aria-current]]/st:bg-[#ecfdf5] group-has-[~li_[aria-current]]/st:text-[#047857]" aria-hidden="true">2</span><span class="pt-1"><b class="block leading-[1.35] font-semibold text-[#334155] group-hover:text-[#0f172a] group-aria-[current]:font-bold group-aria-[current]:text-[#0f172a]"><span class="sr-only">Step 2: </span>Connect a repository</b><small class="text-[.75rem] text-[#5b6474]">2 min</small></span></a></li>
      <li class="group/st relative pb-3.5 before:absolute before:top-8 before:bottom-0.5 before:left-3.5 before:w-0.5 before:rounded-sm before:bg-[#e2e8f0] before:transition-colors before:duration-200 motion-reduce:before:transition-none has-[~li_[aria-current]]:before:bg-[#047857]"><a class="group flex items-start gap-3 rounded-lg text-[#475569] no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1]" href="#l-env"><span class="grid size-[30px] flex-none place-items-center rounded-full border-2 border-[#94a3b8] bg-white text-[.78rem] font-bold text-[#475569] transition-colors duration-200 motion-reduce:transition-none group-aria-[current]:border-[#0369a1] group-aria-[current]:bg-[#0369a1] group-aria-[current]:text-white group-aria-[current]:shadow-[0_0_0_4px_#e0f2fe] group-has-[~li_[aria-current]]/st:border-[#047857] group-has-[~li_[aria-current]]/st:bg-[#ecfdf5] group-has-[~li_[aria-current]]/st:text-[#047857]" aria-hidden="true">3</span><span class="pt-1"><b class="block leading-[1.35] font-semibold text-[#334155] group-hover:text-[#0f172a] group-aria-[current]:font-bold group-aria-[current]:text-[#0f172a]"><span class="sr-only">Step 3: </span>Set environment variables</b><small class="text-[.75rem] text-[#5b6474]">3 min</small></span></a></li>
      <li class="group/st relative pb-3.5 before:absolute before:top-8 before:bottom-0.5 before:left-3.5 before:w-0.5 before:rounded-sm before:bg-[#e2e8f0] before:transition-colors before:duration-200 motion-reduce:before:transition-none has-[~li_[aria-current]]:before:bg-[#047857]"><a class="group flex items-start gap-3 rounded-lg text-[#475569] no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1]" href="#l-deploy"><span class="grid size-[30px] flex-none place-items-center rounded-full border-2 border-[#94a3b8] bg-white text-[.78rem] font-bold text-[#475569] transition-colors duration-200 motion-reduce:transition-none group-aria-[current]:border-[#0369a1] group-aria-[current]:bg-[#0369a1] group-aria-[current]:text-white group-aria-[current]:shadow-[0_0_0_4px_#e0f2fe] group-has-[~li_[aria-current]]/st:border-[#047857] group-has-[~li_[aria-current]]/st:bg-[#ecfdf5] group-has-[~li_[aria-current]]/st:text-[#047857]" aria-hidden="true">4</span><span class="pt-1"><b class="block leading-[1.35] font-semibold text-[#334155] group-hover:text-[#0f172a] group-aria-[current]:font-bold group-aria-[current]:text-[#0f172a]"><span class="sr-only">Step 4: </span>Deploy</b><small class="text-[.75rem] text-[#5b6474]">2 min</small></span></a></li>
      <li class="group/st relative pb-3.5"><a class="group flex items-start gap-3 rounded-lg text-[#475569] no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0369a1]" href="#l-domain"><span class="grid size-[30px] flex-none place-items-center rounded-full border-2 border-[#94a3b8] bg-white text-[.78rem] font-bold text-[#475569] transition-colors duration-200 motion-reduce:transition-none group-aria-[current]:border-[#0369a1] group-aria-[current]:bg-[#0369a1] group-aria-[current]:text-white group-aria-[current]:shadow-[0_0_0_4px_#e0f2fe] group-has-[~li_[aria-current]]/st:border-[#047857] group-has-[~li_[aria-current]]/st:bg-[#ecfdf5] group-has-[~li_[aria-current]]/st:text-[#047857]" aria-hidden="true">5</span><span class="pt-1"><b class="block leading-[1.35] font-semibold text-[#334155] group-hover:text-[#0f172a] group-aria-[current]:font-bold group-aria-[current]:text-[#0f172a]"><span class="sr-only">Step 5: </span>Add a custom domain</b><small class="text-[.75rem] text-[#5b6474]">2 min</small></span></a></li>
    </ol>
  </nav>
  <article class="min-w-0 flex-1 overflow-y-auto scroll-smooth px-9 pt-7 pb-[440px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#0369a1] motion-reduce:scroll-auto max-[640px]:h-[420px] max-[640px]:flex-none max-[640px]:px-5 max-[640px]:pt-[22px] max-[640px]:pb-[340px]" id="launch-doc" tabindex="0" aria-label="Deploy your first app">
      <section class="scroll-mt-6" id="l-create"><h2 class="m-0 mb-2 text-[1.5rem] leading-[1.25] font-[750] tracking-[-.02em] text-[#0f172a]">Deploy your first app</h2><p class="m-0 mb-2 text-[1rem] leading-[1.7] text-[#475569]">Five steps from an empty dashboard to a live site on your own domain.</p><h3 class="mt-7 mb-2 text-[1.1rem] leading-[1.35] font-bold text-[#0f172a]">Step 1: Create a project</h3><p class="m-0 mb-3 text-[.92rem] leading-[1.7] text-[#334155]">Open the dashboard, choose New project and give it a name. The name becomes part of the preview address, so keep it short.</p></section>
      <section class="scroll-mt-6" id="l-connect"><h3 class="mt-7 mb-2 text-[1.1rem] leading-[1.35] font-bold text-[#0f172a]">Step 2: Connect a repository</h3><p class="m-0 mb-3 text-[.92rem] leading-[1.7] text-[#334155]">Pick GitHub, GitLab or Bitbucket and select the repository. Launchpad reads the framework from package.json and suggests a build command.</p><pre class="m-0 mb-3 overflow-x-auto rounded-[10px] bg-[#0f172a] px-3.5 py-3 text-[.8rem] text-[#e2e8f0]"><code class="font-mono">npm run build</code></pre></section>
      <section class="scroll-mt-6" id="l-env"><h3 class="mt-7 mb-2 text-[1.1rem] leading-[1.35] font-bold text-[#0f172a]">Step 3: Set environment variables</h3><p class="m-0 mb-3 text-[.92rem] leading-[1.7] text-[#334155]">Add the keys your app reads at build time. Values are encrypted and never printed in build logs.</p><pre class="m-0 mb-3 overflow-x-auto rounded-[10px] bg-[#0f172a] px-3.5 py-3 text-[.8rem] text-[#e2e8f0]"><code class="font-mono">DATABASE_URL=postgres://[email protected]/shop</code></pre></section>
      <section class="scroll-mt-6" id="l-deploy"><h3 class="mt-7 mb-2 text-[1.1rem] leading-[1.35] font-bold text-[#0f172a]">Step 4: Deploy</h3><p class="m-0 mb-3 text-[.92rem] leading-[1.7] text-[#334155]">Press Deploy. A first build usually takes one to two minutes; later builds reuse the cache and finish faster.</p></section>
      <section class="scroll-mt-6" id="l-domain"><h3 class="mt-7 mb-2 text-[1.1rem] leading-[1.35] font-bold text-[#0f172a]">Step 5: Add a custom domain</h3><p class="m-0 mb-3 text-[.92rem] leading-[1.7] text-[#334155]">Under Domains, add your domain and create the CNAME record shown. The certificate is issued as soon as DNS resolves.</p></section>
  </article>
</div></div>
JavaScript
// Scroll-spy: marks the link whose section has reached the top of the scrolling article with aria-current.
document.querySelectorAll('[data-spy]').forEach(function (toc) {
  var scroller = document.getElementById(toc.getAttribute('data-spy'));
  if (!scroller) return;
  var links = Array.prototype.slice.call(toc.querySelectorAll('a[href^="#"]'));
  var targets = links.map(function (a) { return document.getElementById(a.hash.slice(1)); });
  function update() {
    var line = scroller.getBoundingClientRect().top + 72, current = 0;
    targets.forEach(function (t, i) { if (t && t.getBoundingClientRect().top <= line) current = i; });
    if (scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 2) current = links.length - 1;
    links.forEach(function (a, i) {
      if (i === current) a.setAttribute('aria-current', 'true'); else a.removeAttribute('aria-current');
    });
  }
  scroller.addEventListener('scroll', update, { passive: true });
  update();
});

About this set

Sidebars for documentation and long guides, where the navigation follows the reader. The docs layout has three columns: section navigation on the left with the current page marked by a coloured border, a scrolling article about installing a component library, and an On this page list on the right that highlights the section now at the top of the article. The tutorial layout puts five numbered steps beside a deploy guide; as you scroll, the current step fills blue with a soft ring, the steps you have passed turn green, and the line between them fills in. In plain CSS and Tailwind a short script watches the article’s scroll position and moves aria-current to the matching link; the green steps come from :has(), so the script only marks one link. The Bootstrap version uses Bootstrap’s own ScrollSpy plugin, which adds an active class, and the same CSS reads that class. Without JavaScript the links still jump to their sections. The On this page column hides below 900px, and scrolling is smooth unless the reader prefers reduced motion.

What’s included

  • Docs navigation with a bordered current page
  • On this page list that follows the scroll
  • Tutorial steps that turn green once passed, through :has()
  • Short vanilla script in CSS and Tailwind, ScrollSpy in Bootstrap
  • Links still jump to sections with JavaScript off

Accessibility

The current doc page uses aria-current="page" and the highlighted section link gets aria-current="true", so the position is announced as well as shown. The scrolling article is focusable with tabindex="0" and labelled, so keyboard users can scroll it; step numbers are hidden from screen readers and replaced by "Step 1:" text. Focus outlines are visible, and smooth scrolling and colour transitions stop under prefers-reduced-motion.

Customise it

Accent colours are #0369a1 for the current link and #047857 for finished steps; change them in .dn, .toc and the --st-* properties. In Bootstrap adjust data-bs-root-margin on the article to move the point where a section counts as current; in Tailwind edit the aria-[current]: and group-has- utilities.