Horizontal Scroll

A tall section whose pinned row of cards travels sideways as the page scrolls down, one pixel of scroll for one pixel of travel.

Examples

Cards with scenes

Larkspur coast path

Eight stops over 18 km, scroll down to walk the route

Gull Point

0 km

Start at the car park above the dunes

Saltmarsh Inlet

3.4 km

A boardwalk over the reed beds

Heron Cliffs

6.1 km

The steepest climb of the day

Old Signal House

8.8 km

Water tap and a bench facing west

Fernbay

11.2 km

Harbour café, open until four

Pine Ridge

14.0 km

A shaded path through the pines

Night ferry

16.5 km

The last crossing leaves at nine

Larkspur Sound

18.3 km

End of the path, bus back to town

End of the route

Progress under the row

Open a market season in five steps

Harbor Lane organisers set it up on a Sunday evening

Step 1 of 5

Draw the street

Place every pitch on the map of the street, with its size and whether it is covered

Step 2 of 5

Pick the Saturdays

Choose the market days of the season and the time traders can set up

Step 3 of 5

Invite the traders

Send one link to last season’s traders, who keep their usual pitch

Step 4 of 5

Collect the fees

Traders pay at the end of the week, once they have seen the forecast

Step 5 of 5

Plan for rain

One switch moves everyone to a covered pitch and sends them the new time

The season is open

Installation

npx shadcn@latest add https://ui.ndekocode.dev/r/motion-horizontal-scroll.json

Component API

HorizontalScroll

The section is as tall as the frame plus the width the row overflows, so the row has finished its travel exactly when the section ends. With reduced motion, the frame stops pinning and the row becomes a normal horizontally scrollable row. When a card or a link inside it receives keyboard focus, the page scrolls to the point where it is in view.

PropTypeDefaultDescription
childrenReact.ReactNodeThe cards of the row. Give them a fixed width and shrink-0.
containerRefRefObject<HTMLElement | null>The scrolling element, when it is not the page. It needs a non-static position.
frameClassNamestringh-svhClasses of the pinned frame. Its height should match the height of the scrolling area.
trackClassNamestringClasses of the row, merged over flex w-max gap-4 px-6.
showProgressbooleanfalseShows a thin bar under the row that fills as the row travels.
aria-labelstringA name for the section, read by screen readers.
classNamestringundefinedOptional CSS class for the section.