Folio

The page tips one way as you scroll down, the other as you scroll up, then springs flat when you stop.

A single sheet. Scroll down and it tips a few degrees one way; scroll up and it tips the other. The lean stays around 16° at a flick so a long page does not swing through the camera. Blur rides the traveling edge. Stop and it springs to the original plane. At the bottom the down-lean lets go; at the top the up-lean does.

Folio

Scroll

The page leans back

Wheel through this sheet. Scroll down and it tips one way; scroll up and it tips the other. Stop, and it springs to flat.

Issue 23

A long sheet, on purpose

Folio is not a card trick. It is the whole page as a plane — the same surface you would set type on, tilted by the speed of your hand. The copy below is here so the lean has miles of paper to work against.

Keep the wheel moving. Each stretch of this sheet can show the same move: down-lean, idle, spring. Then the opposite on the way back up.

Hinge

Origin follows the visible viewport. On the way down the hinge sits at the bottom of the frame and the top edge is the one that travels. On the way up it flips: hinge at the top, the foot of the page is what moves.

Blur lives on that traveling edge. The hinge stays sharp. When the spring settles, both go to zero.

Velocity, not distance. How far you have scrolled does not matter. How fast you are moving does.

From the notes

Down

Flick toward the colophon. The sheet tips away from the reading line, blur gathering at the head. Hold the wheel and the pose holds with you.

Up

Reverse the stroke. The lean inverts. Same spring home when you let go — the page does not care which way you came from.

Room to read the move

Short demos lie. A tilt that looks clever on one screen goes cheap when the page has nowhere to go. This sheet is long so you can forget the control chrome, travel, and still find the same lean waiting.

Try a slow crawl. Then a hard flick. Slow travel barely leans; a flick reaches about sixteen degrees. The spring is what makes them feel different.

Midway

You are past the fold and the type is still the same size. That is the point. Folio does not zoom the story. It only changes the plane the story sits on.

If the lean vanished here, the page would feel broken. It should feel like paper that remembers your hand.

What to ignore

Ignore the preview chrome. Ignore the sliders for a minute. Treat this as a site: a headline, a few columns of notes, a quiet end. The effect is only honest if it can live under real copy.

Reduced motion turns it off. That is also honest. A plane that cannot sit still is a problem, not a feature.

Still going

Another screen of travel. The lean should not get tired. If it does, the mapping is wrong — we keyed it to speed so the bottom of the essay can still surprise you.

Near the end the down-lean fades. That is deliberate. A page should be allowed to rest on the last line.

The last stretch

Keep scrolling. The move is the same the whole way until the floor. Then it lets go, and the sheet is just a sheet again.

Scroll back up from here and the other tilt arrives — hinge flipped, blur on the foot of the page, same spring when you stop.

Almost there. One more quiet field of type so the ending has a runway, not a cliff.

End of the sheet. The down-lean lets go here. Scroll back up and it tips the other way.

Props
Blur
4
Perspective
1000
Return
520

Installation

pnpm dlx shadcn@latest add @23rd/folio

Or install straight from the public GitHub source registry:

pnpm dlx shadcn@latest add radiumcoders/23rd.dev/folio

Usage

Use it as the scroll viewport. Children are the page. Wheel, touch, or trackpad tilts the sheet with the scroll — down one way, up the other. Idle returns it. Honors prefers-reduced-motion (no tilt, no blur).

"use client"

import { Folio } from "@/components/ui/folio"

export function Page() {
  return (
    <Folio className="h-svh bg-background">
      <main className="px-10 py-24 text-foreground">
        <h1 className="text-5xl font-medium tracking-tight">
          The page leans back
        </h1>
        <p className="mt-6 max-w-md text-sm leading-relaxed text-foreground/70">
          Put the site in children. Folio is the scroller.
        </p>
      </main>
    </Folio>
  )
}

On a full document (window scroll), pass windowScroll and mark the page root with data-folio-page:

"use client"

import { Folio } from "@/components/ui/folio"

export function App() {
  return (
    <>
      <div data-folio-page>{/* page content */}</div>
      <Folio windowScroll />
    </>
  )
}

Props

PropTypeDefault
childrenReactNode / snippet
blurnumber4 — peak blur on the traveling edge
perspectivenumber1000 — floor 1000
returnMsnumber520 — time for the lean to come back after the bottom
windowScrollbooleanfalse
contentSelectorstring[data-folio-page]
labelstringTilting page
classNamestringReact only
classstringSvelte only
80