SectionJumper

A compact page-section navigator that expands into a popover for fast jumps through long content.

Quick Preview

Overview

A three-bedroom terrace designed around bright shared spaces and a private courtyard.

Amenities

The estate includes 24/7 power, security, a playground, and landscaped walkways.

Location

Eight minutes from the Lekki–Epe expressway, close to schools and everyday shopping.

Pricing

Choose an outright purchase or spread payments across a 24-month plan.

Usage

SectionJumper supports two sources: explicit items inside ScrollSpy, or headings discovered automatically by SectionJumperProvider. Use the provider for blog and CMS markup where headings should remain ordinary HTML.

import { SectionJumper } from "@giddaa-housing/ui/section-jumper";
import {
  ScrollSpy,
  ScrollSpyContent,
} from "@giddaa-housing/ui/scroll-spy";

const sections = [
  { value: "overview", label: "Overview" },
  { value: "gallery", label: "Gallery" },
  { value: "amenities", label: "Amenities" },
];

export function PropertyDetails() {
  return (
    <ScrollSpy defaultValue="overview" scrollOffset={72}>
      <SectionJumper
        items={sections}
        className="sticky top-20 z-20 ml-auto"
      />

      {sections.map((section) => (
        <ScrollSpyContent key={section.value} value={section.value}>
          <h2>{section.label}</h2>
        </ScrollSpyContent>
      ))}
    </ScrollSpy>
  );
}

When it is rendered inside ScrollSpy, every item should have a matching ScrollSpyContent.

Examples

Responsive behavior

On desktop, the resting control is a vertical indicator rail. Hovering or activating the rail opens the labelled menu in a popover. On mobile, it becomes a horizontal indicator chip showing the current section; activating it opens the same popover primitive with a wider, touch-friendly menu. The chip never grows past the viewport — the dots hold their size and the label gives up the remaining room.

The default breakpoint is 1024px. Change mobileBreakpoint when the surrounding layout switches earlier or later.

Overview

Content for the overview section.

Gallery

Content for the gallery section.

Amenities

Content for the amenities section.

Location

Content for the location section.

Purchase Options to Buy a Home

Content for the purchase options to buy a home section.

Payment Plans

Content for the payment plans section.

Rent-to-Own

Content for the rent-to-own section.

Neighbourhood and Schools

Content for the neighbourhood and schools section.

Final Thoughts

Content for the final thoughts section.

Long pages

An article can have more sections than a rail can usefully draw, and labels longer than a fixed-width menu can hold. Both are handled without configuration:

  • The rail windows. Past maxDots sections it draws a sliding window of dots centred on the current one, pinned at either end so the first and last sections stay reachable. The dots at an end the window hasn't reached yet are drawn smaller, so it reads as "there is more this way". Defaults to 12 dots on desktop and 7 on mobile, where they share a row with the section label.
  • The menu scrolls. The popover stops at whatever room it has on screen — it never runs off the bottom — and the section list inside it scrolls, with the heading pinned. It opens with the current section already in view.
  • The menu sizes to its labels. On desktop the panel grows with the longest label between 240px and 320px instead of truncating everything to a fixed width. On mobile it stays a full-width sheet-like panel, clamped to the viewport.

The menu always lists every section — maxDots only affects the resting rail.

About Renewed Hope

Content for About Renewed Hope.

How It Came About

Content for How It Came About.

Where Are the Renewed Hope Cities?

Content for Where Are the Renewed Hope Cities?.

Renewed Hope City, Abuja

Content for Renewed Hope City, Abuja.

Renewed Hope City, Ibeju-Lekki

Content for Renewed Hope City, Ibeju-Lekki.

Renewed Hope City, Kano

Content for Renewed Hope City, Kano.

Who Can Buy?

Content for Who Can Buy?.

Purchase Options to Buy a Home

Content for Purchase Options to Buy a Home.

1. Outright Purchase

Content for 1. Outright Purchase.

2. Payment Plans

Content for 2. Payment Plans.

3. MREIF Mortgage

Content for 3. MREIF Mortgage.

4. Rent-to-Own

Content for 4. Rent-to-Own.

Benefits of Renewed Hope

Content for Benefits of Renewed Hope.

Why Buy Into Renewed Hope?

Content for Why Buy Into Renewed Hope?.

Helpful Links

Content for Helpful Links.

Final Thoughts

Content for Final Thoughts.

Blog and CMS content

For CMS article markup, wrap the jumper and article in SectionJumperProvider. The provider discovers headings, uses their text as labels, and generates IDs when the CMS did not provide them. No item array or ScrollSpyContent wrappers are required.

<SectionJumperProvider selectors={["h2", "h3"]} scrollOffset={80}>
  <SectionJumper />
  <article>
    <h2>Introduction</h2>
    <p>...</p>
    <h2>Financing</h2>
    <h3>Mortgage options</h3>
  </article>
</SectionJumperProvider>;

Introduction

A guide to the decisions that shape a first home purchase. These headings are ordinary CMS output with no IDs or navigation metadata.

Buying a home

Start with location, space, and the trade-offs that matter to your household.

Viewing checklist

Inspect the finishes, utilities, access roads, and shared spaces.

Financing

Compare the deposit, monthly commitment, fees, and total cost before choosing a plan.

Next steps

Shortlist suitable homes, prepare your documents, and schedule inspections.

The default selector is h2. Pass any selector array needed by the article structure, such as selectors={["h2", "h3"]}. Empty headings are ignored. Discovery reruns when CMS content changes, and the nearest vertical scrolling container is tracked automatically.

Controlled open state

Use open and onOpenChange when another control or application event needs to coordinate with the popover. For ordinary use, omit them and optionally seed the component with defaultOpen.

const [open, setOpen] = useState(false);

<SectionJumper
  items={sections}
  open={open}
  onOpenChange={setOpen}
/>;

Selecting a section closes the popover after delegating the jump to ScrollSpy.

Props

PropTypeDefaultDescription
itemsArray<{ value: string; label: ReactNode; targetId?: string }>provider itemsOrdered page sections. Omit inside SectionJumperProvider; otherwise match values to ScrollSpyContent or provide DOM targets.
labelstring"On this page"Accessible control label and popover heading.
openboolean-Controlled popover state.
defaultOpenbooleanfalseInitial state when uncontrolled.
onOpenChange(open: boolean) => void-Called whenever the popover opens or closes.
mobileBreakpointnumber1024Viewport width below which the mobile trigger and popover layout are used.
maxDotsnumber12 desktop / 7 mobileMost dots the resting rail draws before it windows around the active section. The menu always lists every section.
scrollOffsetnumber0Sticky-header offset in standalone target-ID mode.
classNamestring-Positioning and local layout overrides for the root.
native propsComponentProps<"div">-Passed to the root element.

SectionJumperProvider props

PropTypeDefaultDescription
selectorsstring[]["h2"]Heading selectors discovered inside the provider, in document order.
idPrefixstring"section-jumper-heading"Prefix for IDs generated on headings that do not already have one.
scrollOffsetnumber0Sticky-header offset inherited by the nested SectionJumper.
native propsComponentProps<"div">-Passed to the provider's root element.

Accessibility

  • The trigger announces the current section on desktop.
  • The active menu item uses aria-current="location" as well as visual styling.
  • Menu buttons reference their matching content regions with aria-controls.
  • The underlying Popover manages focus, dismissal, and keyboard interaction.
  • Section changes retain ScrollSpy's reduced-motion behavior and focus handoff.

Guidance

  • Use it for navigation within one long page, not for moving between routes.
  • Keep exactly one section active and keep item order aligned with document order.
  • Prefer short labels that remain recognizable in the compact mobile trigger.
  • Position the jumper with sticky or fixed in the consuming layout; the component deliberately does not impose page positioning.

On this page