Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Text

Pro

Paper Fold Text for React

Text that unfolds like folded paper, the upper half of each letter swinging up into place. For React with Base UI, when a title should arrive with a crafted, tactile feel.

Framework

Paper Fold Text unfolds a line a letter at a time: the lower half of each letter waits on the page while its upper half, folded flat along the middle, swings up into place, in shadow until it faces you. It plays once, when the text scrolls into view.

Use it for a title, a headline on an editorial page or a label on a departures-board design. For letters that slide into place, use Splitting Text; to open each letter through a circle, Iris Text; for letters that roll over from one text to another, Rolling Text. Each letter is two copies, the upper one turned in 3D by a CSS keyframe animation the install adds to your stylesheet, with no animation library; once a letter has unfolded it's a single copy again. With reduced motion the text is simply there.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import { PaperFoldText } from "@/components/ui/paper-fold-text";
<h2>
  <PaperFoldText text="Unfold the story" />
</h2>

Demos

Words

by="words" folds each word as one, 200 ms apart: for a longer line, and for scripts whose letters join, such as Arabic, which a split between letters would break.

Preview width

Timing

duration is how long one letter takes to unfold, 600 ms by default, and stagger the time between one letter and the next, 70 ms. Short ones suit a small label; long ones, a slow, deliberate title.

Preview width

Accessibility

Screen readers read the whole text once, as plain text: the folding letters are hidden from them, and a visually hidden copy carries it. Words wrap whole, and the line keeps its direction on a right-to-left page. With reduced motion every letter is whole from the start.

FAQ

How does the fold work?

Each letter is drawn twice in one cell: a lower half that stays put, and an upper half, clipped along the middle, that turns from flat to upright about that line in 3D, with a perspective on the cell. When it's done both clips go and the upper copy shows the whole letter.

Is there a line through the letters once they've unfolded?

No. Two halves drawn edge to edge can leave a hairline where they meet, so once a letter has unfolded the lower half hides and the upper copy, unclipped, is the whole letter.

What shows before it plays?

The lower half of each letter, with its upper half folded out of sight, until the text scrolls into view. A page without JavaScript shows the whole text at once; with trigger="mount" it plays as soon as the page shows it, from CSS alone.

Can the fold look deeper or flatter?

In your CSS, --paper-fold-text-perspective is how close the viewer is to each letter, 4em by default: shorter makes the fold steeper and more 3D, longer flattens it.

How do I run something after it finishes?

Pass onComplete: it's called once, when the last letter or word has unfolded.

What happens with reduced motion?

Nothing folds: every letter is whole from the start, fully readable.

API Reference

PaperFoldText

<span> data-slot="paper-fold-text"

PropType
textstring

The text.

by?"characters" | "words"

What unfolds on its own: each character, or each word (for scripts whose letters join, such as Arabic).

Type
"characters" | "words"
Default
"characters"
duration?number

How long one character or word takes to unfold, in milliseconds. @default 600.

onComplete?() → void

Called once the last character or word has unfolded.

Returns
void
stagger?number

The time between one character or word and the next, in milliseconds.

Default
70 (characters), 200 (words)
trigger?"mount" | "view"

When it plays: as soon as it's on the page, or once it scrolls into view.

Type
"mount" | "view"
Default
"view"
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-last?string
data-view?string

"in" when visible is true, otherwise "out".

CSS variableType
--paper-fold-text-durationstring

One unit's unfolding.

--paper-fold-text-perspectivestring

How deep the fold looks: shorter is steeper (4em).

All 97 text effects