Skip to main content Skip to docs navigation

Enhance navigation with Chassis CSS's intelligent scrollspy component that automatically highlights active links based on viewport scroll position and user interaction.

Introduction

Scrollspy in Chassis CSS provides intelligent navigation feedback by automatically updating active states as users scroll through content. Built with modern Intersection Observer API for optimal performance, scrollspy seamlessly integrates with navigation components while maintaining accessibility and smooth user experiences.

How it works

Chassis CSS scrollspy operates through intelligent intersection detection using the Intersection Observer API to efficiently track element visibility without performance overhead. As users scroll through content, the system automatically toggles the .active class on navigation links when their corresponding content sections come into view.

The component works by linking navigation anchors to content sections through ID matching. Active states cascade through parent navigation elements, making it perfect for hierarchical navigation structures. Scrollspy integrates seamlessly with nav components, list groups, and custom anchor elements, with optional smooth scroll behavior to enhance the user experience.

Before implementing scrollspy, ensure these requirements are met:

  • A navigation component (nav, list group, or anchor links) plus a scrollable container
  • The scrollable container can be the <body> element or a custom element with defined height and overflow-y: scroll
  • Add data-cx-spy="scroll" and data-cx-target="#navId" to the scrollable container
  • Include tabindex="0" if no focusable elements exist within the container
  • Navigation links must reference valid id targets in the DOM structure
  • Hidden target elements are automatically ignored by the intersection observer

Chassis CSS respects user accessibility preferences by automatically disabling animations when the prefers-reduced-motion media query is detected. See the reduced motion guidelines in our accessibility documentation for implementation details.

Examples

Explore scrollspy implementations across different navigation patterns, demonstrating intelligent active state management and smooth scrolling integration. These examples showcase Chassis CSS's flexible scrollspy system with various component types.

Scrollspy seamlessly integrates with navbar components, providing dynamic highlighting for navigation links and dropdown items as users scroll through content sections.

First heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Second heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Third heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Fourth heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Fifth heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

<nav id="navbar-example2" class="navbar bg-evident px-medium mb-medium">
  <a class="navbar-brand" href="#">Navbar</a>
  <ul class="nav nav-pills">
    <li class="nav-item">
      <a class="nav-link" href="#scrollspyHeading1">First</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" href="#scrollspyHeading2">Second</a>
    </li>
    <li class="nav-item dropdown">
      <a class="nav-link dropdown-toggle" data-cx-toggle="dropdown" href="#" role="button" aria-expanded="false">Dropdown</a>
      <ul class="dropdown-menu">
        <li><a class="dropdown-item" href="#scrollspyHeading3">Third</a></li>
        <li><a class="dropdown-item" href="#scrollspyHeading4">Fourth</a></li>
        <li><hr class="dropdown-separator"></li>
        <li><a class="dropdown-item" href="#scrollspyHeading5">Fifth</a></li>
      </ul>
    </li>
  </ul>
</nav>
<div data-cx-spy="scroll" data-cx-target="#navbar-example2" data-cx-root-margin="0px 0px -40%" data-cx-smooth-scroll="true" class="scrollspy-example bg-evident p-medium rounded-2" tabindex="0">
  <h4 id="scrollspyHeading1">First heading</h4>
  <p>...</p>
  <h4 id="scrollspyHeading2">Second heading</h4>
  <p>...</p>
  <h4 id="scrollspyHeading3">Third heading</h4>
  <p>...</p>
  <h4 id="scrollspyHeading4">Fourth heading</h4>
  <p>...</p>
  <h4 id="scrollspyHeading5">Fifth heading</h4>
  <p>...</p>
</div>

Nested navigation

Chassis CSS scrollspy supports hierarchical navigation structures with intelligent parent-child active state cascading for complex content organization.

Item 1

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.

Item 1-1

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.

Item 1-2

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.

Item 2

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.

Item 3

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.

Item 3-1

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.

Item 3-2

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Keep in mind that the JavaScript plugin tries to pick the right element among all that may be visible. Multiple visible scrollspy targets at the same time may cause some issues.

<div class="row">
  <div class="col-4">
    <nav id="navbar-example3" class="h-100 flex-column align-items-stretch pe-large border-end">
      <nav class="nav nav-pills flex-column">
        <a class="nav-link" href="#item-1">Item 1</a>
        <nav class="nav nav-pills flex-column">
          <a class="nav-link ms-medium my-2xsmall" href="#item-1-1">Item 1-1</a>
          <a class="nav-link ms-medium my-2xsmall" href="#item-1-2">Item 1-2</a>
        </nav>
        <a class="nav-link" href="#item-2">Item 2</a>
        <a class="nav-link" href="#item-3">Item 3</a>
        <nav class="nav nav-pills flex-column">
          <a class="nav-link ms-medium my-2xsmall" href="#item-3-1">Item 3-1</a>
          <a class="nav-link ms-medium my-2xsmall" href="#item-3-2">Item 3-2</a>
        </nav>
      </nav>
    </nav>
  </div>

  <div class="col-8">
    <div data-cx-spy="scroll" data-cx-target="#navbar-example3" data-cx-smooth-scroll="true" class="scrollspy-example-2" tabindex="0">
      <div id="item-1">
        <h4>Item 1</h4>
        <p>...</p>
      </div>
      <div id="item-1-1">
        <h5>Item 1-1</h5>
        <p>...</p>
      </div>
      <div id="item-1-2">
        <h5>Item 1-2</h5>
        <p>...</p>
      </div>
      <div id="item-2">
        <h4>Item 2</h4>
        <p>...</p>
      </div>
      <div id="item-3">
        <h4>Item 3</h4>
        <p>...</p>
      </div>
      <div id="item-3-1">
        <h5>Item 3-1</h5>
        <p>...</p>
      </div>
      <div id="item-3-2">
        <h5>Item 3-2</h5>
        <p>...</p>
      </div>
    </div>
  </div>
</div>

List group integration

Scrollspy extends beyond navigation components to work seamlessly with list groups, providing consistent active state management across different interface patterns.

Item 1

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Item 2

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Item 3

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Item 4

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

<div class="row">
  <div class="col-4">
    <div id="list-example" class="list-group">
      <a class="list-item list-action" href="#list-item-1">Item 1</a>
      <a class="list-item list-action" href="#list-item-2">Item 2</a>
      <a class="list-item list-action" href="#list-item-3">Item 3</a>
      <a class="list-item list-action" href="#list-item-4">Item 4</a>
    </div>
  </div>
  <div class="col-8">
    <div data-cx-spy="scroll" data-cx-target="#list-example" data-cx-smooth-scroll="true" class="scrollspy-example" tabindex="0">
      <h4 id="list-item-1">Item 1</h4>
      <p>...</p>
      <h4 id="list-item-2">Item 2</h4>
      <p>...</p>
      <h4 id="list-item-3">Item 3</h4>
      <p>...</p>
      <h4 id="list-item-4">Item 4</h4>
      <p>...</p>
    </div>
  </div>
</div>

Custom anchor elements

Scrollspy flexibility extends to any anchor elements in your document, enabling custom navigation patterns beyond traditional components while maintaining consistent behavior.

Item 1

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Item 2

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Item 3

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Item 4

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Item 5

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

<div class="row">
  <div class="col-4">
    <div id="simple-list-example" class="d-flex flex-column gap-xsmall simple-list-example-scrollspy text-center">
      <a class="p-2xsmall rounded" href="#simple-list-item-1">Item 1</a>
      <a class="p-2xsmall rounded" href="#simple-list-item-2">Item 2</a>
      <a class="p-2xsmall rounded" href="#simple-list-item-3">Item 3</a>
      <a class="p-2xsmall rounded" href="#simple-list-item-4">Item 4</a>
      <a class="p-2xsmall rounded" href="#simple-list-item-5">Item 5</a>
    </div>
  </div>
  <div class="col-8">
    <div data-cx-spy="scroll" data-cx-target="#simple-list-example" data-cx-offset="0" data-cx-smooth-scroll="true" class="scrollspy-example" tabindex="0">
      <h4 id="simple-list-item-1">Item 1</h4>
      <p>...</p>
      <h4 id="simple-list-item-2">Item 2</h4>
      <p>...</p>
      <h4 id="simple-list-item-3">Item 3</h4>
      <p>...</p>
      <h4 id="simple-list-item-4">Item 4</h4>
      <p>...</p>
      <h4 id="simple-list-item-5">Item 5</h4>
      <p>...</p>
    </div>
  </div>
</div>

Accessibility

Scrollspy components in Chassis CSS maintain full accessibility compliance through semantic markup, keyboard navigation support, and assistive technology compatibility. These features ensure navigation enhancement doesn't compromise user experience for any user group.

Screen readers

Scrollspy updates maintain semantic navigation structure while providing clear context for assistive technologies. Active state changes are communicated through standard ARIA practices and focus management.

Keyboard navigation

Chassis CSS scrollspy provides comprehensive keyboard accessibility, ensuring equal access for all users through focus management and intuitive navigation patterns.

  • Tab navigation through scrollspy links maintains logical order and visual focus indicators
  • Enter and Space keys trigger smooth scrolling to target sections when enabled
  • Active state changes don't interfere with keyboard user navigation patterns

Non-visible elements

Target elements that aren't visible will be ignored and their corresponding nav items won't receive an .active class. Scrollspy instances initialized in a non-visible wrapper will ignore all target elements. Use the refresh method to check for observable elements once the wrapper becomes visible.

document.querySelectorAll('#nav-tab>[data-cx-toggle="tab"]').forEach(el => {
  el.addEventListener('shown.cx.tab', () => {
    const target = el.getAttribute('data-cx-target')
    const scrollElem = document.querySelector(`${target} [data-cx-spy="scroll"]`)
    chassis.ScrollSpy.getOrCreateInstance(scrollElem).refresh()
  })
})

JavaScript API

Control scrollspy behavior programmatically with Chassis CSS's comprehensive JavaScript interface, featuring efficient intersection observation, dynamic content tracking, and smooth scrolling integration.

Data attributes

Configure scrollspy behavior declaratively through data attributes for simple implementation without JavaScript initialization.

To easily add scrollspy behavior to your navigation, add data-cx-spy="scroll" to the element you want to monitor (typically the <body> or a scrollable container). Then add the data-cx-target attribute with the id or class name of the parent element containing your navigation component.

<body data-cx-spy="scroll" data-cx-target="#navbar-example">
  ...
  <div id="navbar-example">
    <ul class="nav nav-tabs" role="tablist">
      ...
    </ul>
  </div>
  ...
</body>

Initialization

Initialize scrollspy instances programmatically for dynamic content or advanced configuration requirements.

const scrollSpy = new chassis.ScrollSpy(document.body, {
  target: '#navbar-example'
})

Options

Configure scrollspy behavior through comprehensive options that control intersection detection, smooth scrolling, and targeting strategies for optimal user experience.

Chassis components offer flexible configuration through both data attributes and JavaScript. To use data attributes, prefix any option with data-cx- followed by the option name in kebab-case format (using hyphens instead of camelCase). For example, write data-cx-custom-class="my-class" rather than data-cx-customClass="my-class".

For more complex configurations, Chassis provides the data-cx-config attribute which accepts a JSON string of multiple settings. This approach simplifies markup when configuring several options at once. If the same option appears in both data-cx-config and as a separate data attribute (like data-cx-title), the individual attribute takes precedence. You can also use JSON values in individual attributes, such as data-cx-delay='{"show":100,"hide":200}' for more granular control.

When initializing components, Chassis merges configurations from multiple sources in this priority order: default settings, data-cx-config values, individual data-cx-* attributes, and finally any JavaScript object options. Values defined later in this sequence override earlier ones.

NameTypeDefaultDescription
rootMarginstring0px 0px -25%Intersection Observer rootMargin valid units, when calculating scroll position.
smoothScrollbooleanfalseEnables smooth scrolling when a user clicks on a link that refers to ScrollSpy observables.
targetstring, DOM elementnullSpecifies element to apply Scrollspy plugin.
thresholdarray[0.1, 0.5, 1]IntersectionObserver threshold valid input, when calculating scroll position.

Deprecated Options

Up until v5.1.3 we were using offset & method options, which are now deprecated and replaced by rootMargin. To keep backwards compatibility, we will continue to parse a given offset to rootMargin, but this feature will be removed in v6.

Methods

Chassis CSS provides essential methods for programmatic scrollspy control, enabling dynamic content management and instance lifecycle handling.

MethodDescription
disposeDestroys an element's scrollspy. (Removes stored data on the DOM element)
getInstanceStatic method to get the scrollspy instance associated with a DOM element.
getOrCreateInstanceStatic method to get the scrollspy instance associated with a DOM element, or to create a new one in case it wasn't initialized.
refreshWhen adding or removing elements in the DOM, you'll need to call the refresh method.

Here's an example using the refresh method:

const dataSpyList = document.querySelectorAll('[data-cx-spy="scroll"]')
dataSpyList.forEach(dataSpyEl => {
  chassis.ScrollSpy.getInstance(dataSpyEl).refresh()
})

Events

Listen to scrollspy events to integrate with your application logic and create responsive navigation interactions.

EventDescription
activate.cx.scrollspyThis event fires on the scroll element whenever an anchor is activated by the scrollspy.
const firstScrollSpyEl = document.querySelector('[data-cx-spy="scroll"]')
firstScrollSpyEl.addEventListener('activate.cx.scrollspy', () => {
  // do something...
})