Quest Map Section
A winding quest map section — as you scroll, a marker travels the path clearing stations, and each one it reaches pops its content open, raises a flag, and stamps itself complete.
npx shadcn@latest add https://webberui.com/r/quest-map-section.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<QuestMapSection />
Installation
npx shadcn@latest add https://webberui.com/r/quest-map-section.jsonOr, once registries are configured in components.json, install it as @webberui/quest-map-section.
Usage
Wrap a number of QuestNodes in a QuestMapSection; the stations automatically alternate left and right, and a single winding path strings all the nodes together. As you scroll, the marker advances along the path and the stations it reaches light up in order.
import {
QuestMapSection,
QuestNode,
} from "@/components/ui/quest-map-section";
<QuestMapSection className="text-indigo-500">
<QuestNode title="Create an account" icon={<Compass className="h-4 w-4" />} flag="Start">
Sign up in thirty seconds.
</QuestNode>
<QuestNode title="First deploy" icon={<Rocket className="h-4 w-4" />} flag="Cleared">
Push the first build and a preview URL is generated instantly.
</QuestNode>
</QuestMapSection>The accent color is inherited through currentColor: pass any text-* in QuestMapSection's className to recolor the whole thing (path, marker, dots, flags, and stamps all follow).
Inside a scroll container
If the map sits in a nested container with overflow-y-auto, pass that container's ref to container so that scroll progress maps correctly:
const scrollerRef = React.useRef<HTMLDivElement>(null);
<div ref={scrollerRef} className="h-[300px] overflow-y-auto">
<QuestMapSection container={scrollerRef}>{/* ...QuestNode */}</QuestMapSection>
</div>Props
QuestMapSection
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Put QuestNode children here, one per station |
container | RefObject<HTMLElement | null> | — | Ref of the nested scroll container; defaults to the window as scroll container |
offset | [string, string] | ["start 0.85", "end 0.55"] | The range useScroll progress maps to, deciding when the marker starts and stops advancing |
aria-label | string | "Quest map" | Accessible label applied to the list container |
className | string | — | Outer styles; pass text-* to set the accent color |
QuestNode
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | — | Station title |
children | ReactNode | — | Station content description |
icon | ReactNode | — | Icon before the title (a lucide-react icon is recommended) |
flag | ReactNode | — | Short label shown on the flag raised when the station is reached |
id | string | — | Anchor id applied to the card |
className | string | — | Class applied to the outer element of the whole station row |
How it works
- Stations alternate left and right automatically by index, and the path is strung into a smooth S curve from the vertical control points of adjacent nodes; measurement uses real DOM coordinates, so it stays flush even when content heights differ.
- The progress track is normalized with
pathLength={1}, andstrokeDashoffsetshrinks from 1 to 0 with scroll so the fill stops exactly at the marker. - Node coordinates and sizes are watched with a
ResizeObserver, so the path is recomputed automatically after fonts load or the container resizes.
Accessibility
- When the user has "reduce motion" enabled at the system level, all stations render as already complete, the path is fully filled, the marker is hidden, and the travel and stamp animations do not play.
- The container is a
role="list"and each station arole="listitem", carryingdata-completeplus "(complete) / (locked)" status text for announcement. - The path SVG and the travelling marker are both
aria-hidden, so they do not interfere with assistive technology announcing the station content.
Depth Mask Hero
A lock-screen depth hero — a cut-out subject partially occludes the oversized headline, and on scroll the subject, headline, and background separate at different rates to produce real depth.
Trophy Case
An achievement trophy wall — a grid of glass cabinets displays your achievements; unlocking one raises the trophy, sweeps a highlight across it, and bursts a little confetti, while hovering lets you examine it in 3D and see its requirement and progress.