Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelog App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip
ARC UI ARC Radiant Components
v3.2 Docs Components Tokens Synthesizer
Getting StartedFrameworksServer Rendering Design TokensThemingTheme SynthesizerTypographyUtilities All ComponentsAccessibilityBrowser SupportChangelogContributingStats App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip

Scroll Spy

Tracks scroll position and highlights the active navigation link.

Components Scroll Spy
navigation interactive
<arc-scroll-spy>

Overview

ScrollSpy is a sticky table-of-contents component that watches the viewport and highlights whichever navigation link corresponds to the currently visible section. It uses an IntersectionObserver under the hood, so tracking is efficient and does not block the main thread even on long pages with dozens of sections. Place it in a sidebar column next to your content and it handles the rest. Each entry in the table of contents is declared with an `<arc-spy-link>` child element whose `target` attribute matches the `id` of a section on the page. ScrollSpy reads these declarative links from its slot, builds the IntersectionObserver, and renders a compact navigation list in its shadow DOM with an "On this page" heading. When the user scrolls a target section into view, the corresponding link receives an `aria-current="true"` attribute and a blue accent background highlight. Clicking a link triggers a smooth scroll to the target element and immediately updates the active state. The component dispatches an `arc-change` custom event with the active section ID whenever the highlighted link changes, so you can synchronize other UI (like a progress bar or breadcrumb) with the current reading position. The `offset` prop lets you fine-tune the scroll detection threshold to account for sticky headers of varying heights.

Guidelines

When to use

  • Place ScrollSpy in a sidebar-right or sticky aside column next to the scrollable content
  • Give every target section a unique id attribute that matches the spy-link target
  • Set the offset prop to match the height of your sticky header or TopBar
  • Listen for the arc-change event to synchronize breadcrumbs, analytics, or URL hash updates
  • Keep spy-link labels short — they should match or abbreviate section headings

When not to use

  • Do not use ScrollSpy for primary site navigation — it is for in-page section tracking only
  • Do not forget to import arc-spy-link; ScrollSpy depends on it to collect its link definitions
  • Do not place ScrollSpy inside a scrollable container other than the document — the observer watches document-level intersections
  • Do not add dozens of spy-links to a single ScrollSpy; more than 10-12 links make the list hard to scan
  • Do not omit the target attribute on spy-links — they will be silently ignored by the observer

Features

  • IntersectionObserver-based scroll tracking with zero scroll-event overhead
  • Declarative link registration via <arc-spy-link target="id"> children
  • Sticky positioning with automatic height capping to prevent overflow
  • Smooth-scroll click navigation to target sections
  • Active link highlighting with accent-primary background and aria-current="true"
  • Configurable offset prop to account for sticky headers of varying heights
  • `arc-change` custom event dispatched when the active section changes
  • Thin scrollbar styling for long tables of contents

Preview

Overview Installation Usage API Reference Examples

Usage

This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.

<arc-scroll-spy>
  <arc-spy-link target="section-1">Section 1</arc-spy-link>
  <arc-spy-link target="section-2">Section 2</arc-spy-link>
</arc-scroll-spy>
import { ScrollSpy, SpyLink } from '@arclux/arc-ui-react';

export default function Example() {
  return (
    <ScrollSpy>
      <SpyLink target="section-1">Section 1</SpyLink>
      <SpyLink target="section-2">Section 2</SpyLink>
    </ScrollSpy>
  );
}
<script setup>
import { ScrollSpy, SpyLink } from '@arclux/arc-ui-vue';
</script>

<template>
  <ScrollSpy>
    <SpyLink target="section-1">Section 1</SpyLink>
    <SpyLink target="section-2">Section 2</SpyLink>
  </ScrollSpy>
</template>
<script>
  import { ScrollSpy, SpyLink } from '@arclux/arc-ui-svelte';
</script>

<ScrollSpy>
  <SpyLink target="section-1">Section 1</SpyLink>
  <SpyLink target="section-2">Section 2</SpyLink>
</ScrollSpy>
import { Component } from '@angular/core';
import { ScrollSpy, SpyLink } from '@arclux/arc-ui-angular';

@Component({
  imports: [ScrollSpy, SpyLink],
  template: `
    <arc-scroll-spy>
      <arc-spy-link target="section-1">Section 1</arc-spy-link>
      <arc-spy-link target="section-2">Section 2</arc-spy-link>
    </arc-scroll-spy>
  `,
})
export class MyComponent {}
import { ScrollSpy, SpyLink } from '@arclux/arc-ui-solid';

export default function Example() {
  return (
    <ScrollSpy>
      <SpyLink target="section-1">Section 1</SpyLink>
      <SpyLink target="section-2">Section 2</SpyLink>
    </ScrollSpy>
  );
}
import { ScrollSpy, SpyLink } from '@arclux/arc-ui-preact';

export default function Example() {
  return (
    <ScrollSpy>
      <SpyLink target="section-1">Section 1</SpyLink>
      <SpyLink target="section-2">Section 2</SpyLink>
    </ScrollSpy>
  );
}

API

ringRadius number 6
Ring geometry. r drives the dasharray, so the two cannot drift apart.
active string ''
The id of the currently active section. Reflects to an attribute and updates automatically as the user scrolls.
offset number 80
Pixel distance from the top of the viewport at which a section counts as current. Increase it to account for taller sticky headers.
progress 'none' | 'ring' | 'read' | 'both' 'none'
How the reader's position through the document is shown. `ring` draws a progress arc beside the heading; `read` recedes the entries already scrolled past; `both` does each. Defaults to `none`, and an unrecognized value lands there too.

Events

arc-change
Fired when the active spy target changes during scroll

See Also