Table of Contents

Introduction

Spruce CSS is a Sass-based framework. Scroll this area and watch the table of contents follow you.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante venenatis dapibus.

Installation

Install the package and configure it once.

With npm

Run npm install sprucecss. Donec ullamcorper nulla non metus auctor fringilla. Vestibulum id ligula porta felis euismod semper.

From a CDN

Load the compiled CSS from jsDelivr. Cras mattis consectetur purus sit amet fermentum. Maecenas faucibus mollis interdum.

Configuration

Set your colors and tokens. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo.

Etiam porta sem malesuada magna mollis euismod. Curabitur blandit tempus porttitor.

Next steps

Explore the components, the blocks and the pages. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.

Morbi leo risus, porta ac consectetur ac, vestibulum at eros.

@use 'sprucecss/scss/spruce' as *;

.table-of-contents-demo {
  display: grid;
  gap: spacer('l');

  @include breakpoint('md') {
    grid-template-columns: 14rem 1fr;
  }

  &__content {
    border: config('border-width-base', $display) solid color('border');
    border-radius: radius('lg');
    max-block-size: 22rem;
    overflow-y: auto;
    padding: spacer('m');
    @include layout-stack('s');
  }
}

.table-of-contents {
  align-self: start;
  font-size: config('font-size-sm', $typography);
  @include layout-stack('xs');

  &__title {
    color: color('heading');
    font-weight: 700;
  }

  ol {
    list-style: none;
    padding: 0;
    @include layout-stack('xxs');
  }

  ol ol {
    margin-block-start: spacer('xxs');
    padding-inline-start: spacer('s');
  }

  a {
    border-inline-start: 2px solid color('border');
    color: color('muted');
    display: block;
    padding-block: spacer('xxs');
    padding-inline-start: spacer('s');
    text-decoration: none;
    @include transition($property: 'border-color, color');

    &:hover {
      color: color('heading');
    }

    &[aria-current='true'] {
      border-color: color('primary');
      color: color('primary');
      font-weight: 700;
    }
  }
}
<div class="table-of-contents-demo">
    <nav class="table-of-contents" aria-labelledby="toc-title" data-toc>
        <p class="table-of-contents__title" id="toc-title">On this page</p>
        <ol class="table-of-contents__list" role="list">
            <li><a href="#toc-introduction">Introduction</a></li>
            <li><a href="#toc-installation">Installation</a>
                <ol role="list">
                    <li><a href="#toc-npm">With npm</a></li>
                    <li><a href="#toc-cdn">From a CDN</a></li>
                </ol>
            </li>
            <li><a href="#toc-configuration">Configuration</a></li>
            <li><a href="#toc-next-steps">Next steps</a></li>
        </ol>
    </nav>
    <div class="table-of-contents-demo__content" tabindex="0" aria-label="Article (scrollable)">
        <h2 id="toc-introduction">Introduction</h2>
        <p>Spruce CSS is a Sass-based framework. Scroll this area and watch the table of contents follow you.</p><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante venenatis dapibus.</p>
        <h2 id="toc-installation">Installation</h2>
        <p>Install the package and configure it once.</p>
        <h3 id="toc-npm">With npm</h3>
        <p>Run npm install sprucecss. Donec ullamcorper nulla non metus auctor fringilla. Vestibulum id ligula porta felis euismod semper.</p>
        <h3 id="toc-cdn">From a CDN</h3>
        <p>Load the compiled CSS from jsDelivr. Cras mattis consectetur purus sit amet fermentum. Maecenas faucibus mollis interdum.</p>
        <h2 id="toc-configuration">Configuration</h2>
        <p>Set your colors and tokens. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo.</p><p>Etiam porta sem malesuada magna mollis euismod. Curabitur blandit tempus porttitor.</p>
        <h2 id="toc-next-steps">Next steps</h2>
        <p>Explore the components, the blocks and the pages. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</p><p>Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>
    </div>
</div>
(() => {
  document.querySelectorAll('[data-toc]').forEach((toc) => {
    const links = [...toc.querySelectorAll('a[href^="#"]')];
    const sections = links.map((link) => document.getElementById(link.hash.slice(1))).filter(Boolean);
    const scrollRoot = sections[0]?.closest('[tabindex="0"]') ?? null;

    const setCurrent = (id) => {
      links.forEach((link) => {
        if (link.hash === `#${id}`) {
          link.setAttribute('aria-current', 'true');
        } else {
          link.removeAttribute('aria-current');
        }
      });
    };

    const observer = new IntersectionObserver((entries) => {
      const visible = entries.filter((entry) => entry.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);

      if (visible.length) {
        setCurrent(visible[0].target.id);
      }
    }, { root: scrollRoot, rootMargin: '0px 0px -60% 0px' });

    sections.forEach((section) => observer.observe(section));
    setCurrent(sections[0]?.id);
  });
})();

Table of Contents

On this page

An "On this page" navigation that highlights the section you are reading (scroll spy).

Technical Details

  • A labelled nav with an ordered, nested list of in-page links. Without JavaScript, it is a working list of anchor links.
  • An IntersectionObserver marks the link of the current section with aria-current="true"; no scroll listeners.
  • The active indicator is a border-inline-start, so it sits on the right in RTL.
  • In the demo the article scrolls inside a box; on a real page, observe the document and make the navigation position: sticky. The Spruce CSS documentation uses the same pattern.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.