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.
Gallery
Explore the living areas, bedrooms, exterior finishes, and community spaces.
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
maxDotssections 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
| Prop | Type | Default | Description |
|---|---|---|---|
items | Array<{ value: string; label: ReactNode; targetId?: string }> | provider items | Ordered page sections. Omit inside SectionJumperProvider; otherwise match values to ScrollSpyContent or provide DOM targets. |
label | string | "On this page" | Accessible control label and popover heading. |
open | boolean | - | Controlled popover state. |
defaultOpen | boolean | false | Initial state when uncontrolled. |
onOpenChange | (open: boolean) => void | - | Called whenever the popover opens or closes. |
mobileBreakpoint | number | 1024 | Viewport width below which the mobile trigger and popover layout are used. |
maxDots | number | 12 desktop / 7 mobile | Most dots the resting rail draws before it windows around the active section. The menu always lists every section. |
scrollOffset | number | 0 | Sticky-header offset in standalone target-ID mode. |
className | string | - | Positioning and local layout overrides for the root. |
| native props | ComponentProps<"div"> | - | Passed to the root element. |
SectionJumperProvider props
| Prop | Type | Default | Description |
|---|---|---|---|
selectors | string[] | ["h2"] | Heading selectors discovered inside the provider, in document order. |
idPrefix | string | "section-jumper-heading" | Prefix for IDs generated on headings that do not already have one. |
scrollOffset | number | 0 | Sticky-header offset inherited by the nested SectionJumper. |
| native props | ComponentProps<"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
stickyorfixedin the consuming layout; the component deliberately does not impose page positioning.