Page Sections

Magazine Pull Quote

A magazine-spread React testimonial with an oversized quotation mark, a large serif pull quote, a drop cap and two-column body copy; on entry the mark scales into place and the text reveals line by line, with page turning for multiple testimonials.

Lays out a customer testimonial as a magazine spread: the left page is a full-bleed portrait (a two-tone gradient and an abstract bust silhouette stand in when there is no photo) with a caption, and the right page stacks a section kicker, an oversized quotation mark tucked over the top left of the pull quote, a large serif pull quote, a hairline rule, two-column case-study copy that opens with a drop cap, and the attribution with a page number — a gutter shadow down the middle binds the two pages. When it scrolls into view, the left image unfolds outward from the gutter, the quotation mark scales down and settles, the pull quote rises out of a mask one line at a time and the body copy fades in last. With several testimonials, previous / next controls in the bottom right turn the whole spread like a sheet of paper.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add magazine-pull-quote.en

New project or installation trouble? Start here →

Playground

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

700
Active edited usageDefault props
<MagazinePullQuote/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/magazine-pull-quote.en.json

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

Usage

import { MagazinePullQuote } from "@/components/ui/magazine-pull-quote";

<MagazinePullQuote
  eyebrow="Customer Story"
  title="Inside this issue's customer interviews"
  testimonials={[
    {
      quote: "Since the redesign,\nguests book a table by scanning a code at the door.",
      name: "Maya Lin",
      role: "Head of Operations",
      company: "Islet Coffee",
      highlight: "scanning a code at the door",
      photoCredit: "Ian Chou",
      story:
        "Our three locations each ran their own booking sheet, and the same table was often booked twice on weekends.\n\nIn the first month after launch, the weekend waitlist was cut in half.",
    },
    {
      quote: "Drivers open the dispatch screen and know the next stop.",
      name: "Owen Chen",
      role: "Dispatch Lead",
      company: "Northstar Freight",
      photoSrc: "/images/team/chen.jpg",
    },
  ]}
  pageLabel="Customer Interview"
  startPage={24}
  onIndexChange={(i) => console.log(`Turned to ${i + 1}`)}
/>

Props

PropTypeDefaultDescription
testimonialsMagazinePullQuoteItem[]—The testimonials; page controls appear in the bottom right when there is more than one
eyebrowstring—Section kicker at the top of the right page, set in uppercase with wide tracking
titlestring—Section heading above the spread; when provided it becomes the section's aria-labelledby
subtitlestring—Supporting text under the heading
italicbooleanfalseWhether the pull quote is set in italics
showPhotobooleantrueWhether to show the left-page portrait; when off, only the right page is shown, centred
durationnumber700Line reveal duration for the pull quote (ms); falls back to --wb-magazine-pull-quote-duration
indexnumber—Controlled current index; managed internally when omitted
defaultIndexnumber0Initial index in uncontrolled mode
onIndexChange(index: number) => void—Called when the index changes
pageLabelstring"Customer Interview"Section name in the page footer; also the section's aria-label when there is no title (the Chinese build defaults to "客戶專訪", Customer Interview)
startPagenumber24Page number of the first spread; each turn adds 2
classNamestring—Passed through to the root section

MagazinePullQuoteItem

FieldTypeDefaultDescription
quotestring—Pull quote text; break lines with \n, otherwise it is split after sentence-ending punctuation
namestring—Name, shown in the attribution and the caption
rolestring—Job title
companystring—Company
ratingnumber—Rating (0–5), shown next to the attribution
avatarHuenumber—Hue of the placeholder portrait; derived from the index by the golden angle (137.508°) when omitted
highlightstring—A fragment of the quote to mark in the accent colour
metric{ value: string; label: string }—A key figure paired with the quote, shown next to the attribution
storystring—Longer case-study copy set in two columns; separate paragraphs with a blank line, and the first paragraph opens with a drop cap
photoSrcstring—Portrait photo URL; a gradient and abstract silhouette stand in when omitted
photoCreditstring—Photographer credit shown in the caption

How it works

  • Serif type is used only for the quotation mark, pull quote, drop cap and figures, via Tailwind's font-serif, so the actual face comes from your site's fontFamily.serif and the component loads no font files; everything else stays font-sans.
  • The timing variable --wb-magazine-pull-quote-duration (default 700ms) sets how long each pull-quote line takes to rise, with an 80ms stagger between lines. Passing duration writes it into that variable, and the component reads it back from the computed style after mounting, so overriding the variable higher up also works.
  • The rest of the timing is fixed: the quotation mark springs (stiffness 180, damping 18) down from 1.6× and a −6° tilt; the body copy fades in and rises 8px after a 300ms delay; the left image unfolds outward from the gutter over 900ms; on a page turn the right page hands off with rotateY(-8deg) around its left edge plus opacity over 600ms while the left image crossfades.
  • The entrance plays once, the first time the spread scrolls into view (useInView once).
  • The accent colour only comes from --wb-magazine-pull-quote-accent (default #0d9488 in light mode, #2dd4bf in dark mode) and is used for the kicker, quotation mark, highlighted fragment and figures.
  • From md up the two pages sit side by side; below md they stack and the gutter shadow is hidden.

Accessibility

  • The root element is a section labelled by the heading via aria-labelledby when a title is given; the spread itself is an article, and the quote uses figure, blockquote and figcaption
  • The oversized quotation mark and the placeholder silhouette are aria-hidden, the portrait image is decorative (alt=""), and the name and title are given as text in the caption and attribution
  • The drop cap is done with ::first-letter rather than wrapping the first letter in its own element, so screen readers read the sentence as a whole
  • Previous / next are type="button" with an aria-label; with focus inside the spread the left and right arrow keys turn the page, and after a turn an aria-live="polite" region announces "2 of 3" and the name
  • When the user has turned on reduce motion: no scaling, tilting, sliding, clip reveal or page-turn effect — everything becomes a 150ms fade, and the page controls still work