WebberUI

Pull Quote Lift

When a sentence inside a paragraph scrolls into the viewport, a shared-element animation lifts it out and scales it up into a full-width pull quote, dropping it back inline when it leaves the viewport.

Loading preview…
npx shadcn@latest add https://webberui.com/r/pull-quote-lift.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

<PullQuoteLift />

Installation

npx shadcn@latest add https://webberui.com/r/pull-quote-lift.json

Or, once registries are configured in components.json, install it as @webberui/pull-quote-lift.

Usage

import { PullQuoteLift, LiftQuote } from "@/components/ui/pull-quote-lift";

<PullQuoteLift>
  <p>
    Magazine editors pull the single most important sentence out and blow it up into a pull quote.
    <LiftQuote>Animation is not decoration — it is how an interface speaks to its user.</LiftQuote>
    The same technique works just as well on the web.
  </p>
</PullQuoteLift>

PullQuoteLift wraps the whole article and LiftQuote wraps the sentence to lift. When the sentence scrolls into the viewport it "floats out" and scales up into a full-width pull quote (with decorative quotation marks and rules above and below), and drops back to its inline position in the body text when it leaves the viewport.

Props

PullQuoteLift

PropTypeDefaultDescription
childrenReact.ReactNodeThe article content, whose paragraphs may contain one or more LiftQuote
classNamestringThe className of the outer container

LiftQuote

PropTypeDefaultDescription
childrenReact.ReactNodeThe sentence to lift into a pull quote
quoteClassNamestringAppended to the lifted blockquote's className

How it works

  • How layoutId FLIP works: the inline motion.span and the lifted motion.blockquote share the same layoutId. On the switch, Motion snapshots the old element's position and size and transitions the new element from the old bounding box to its final position using transforms (FLIP), which is why the sentence appears to "fly" out of the line and into the quote block.
  • Font-size scale morph: FLIP interpolates two boxes of very different sizes with scale, so during the transition the text is temporarily scaled and distorted (morphing from the inline font size to the large quote font size). This is inherent to shared-element animation, and exact typography is restored once the animation ends.
  • Where the pull quote renders: the lifted blockquote is rendered through a portal into a slot at the end of the article rather than inside the <p> that holds the sentence — a <blockquote> cannot be nested inside a <p>, and the portal also lets the inline and quote sides swap within the same commit, which is what makes the FLIP snapshots line up.
  • Placeholder strategy: a dimmed copy of the original sentence stays inline after the lift, so the paragraph never reflows and the element useInView observes keeps a stable size, avoiding oscillation at the viewport boundary.
  • Nested scroll containers: useInView defaults to the browser viewport as its root, and IntersectionObserver automatically accounts for clipping by ancestor overflow, so it triggers correctly inside a fixed-height scroll container too. If the container scrolls while the animation is running, making the container a motion element with layoutScroll is recommended so FLIP measures correctly (see the demo source).
  • Round-trip animation: the quote block is wrapped in AnimatePresence, so scrolling back out of the viewport hands its position back to the remounting inline span as it exits, cross-fading between the two.

Accessibility

  • When the user has "reduce motion" enabled at the system level, nothing animates: the original sentence stays in the paragraph and the pull quote is shown statically
  • The sentence always stays inside the paragraph (as a dimmed version while lifted), so the article's reading order never changes; the lifted blockquote is a decorative copy carrying aria-hidden, so the screen reader does not announce it twice

On this page