WebberUI

Interview Spread

A magazine interview layout — bold questions in a narrow left column alternating with serif answers in a wide right column, anchored by oversized quotation marks, with a speaker marker sliding along a sidebar rail and turns emerging in sequence as you scroll.

Loading preview…
npx shadcn@latest add https://webberui.com/r/interview-spread.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.

56
<InterviewSpread />

Installation

npx shadcn@latest add https://webberui.com/r/interview-spread.json

Or, once registries are configured in components.json, install it as @webberui/interview-spread.

Usage

import {
  InterviewSpread,
  InterviewTurn,
} from "@/components/ui/interview-spread";

<InterviewSpread interviewer="Editor" interviewee="Designer">
  <InterviewTurn role="question">How do you see the role of animation in an interface?</InterviewTurn>
  <InterviewTurn role="answer">Animation is not decoration — it is narrative.</InterviewTurn>
  <InterviewTurn role="question">And what about rhythm?</InterviewTurn>
  <InterviewTurn role="answer">Rhythm is everything. I treat every easing curve as a breath.</InterviewTurn>
</InterviewSpread>;

If the interview sits inside a custom scroll container (a modal or a sidebar, say), pass the container ref to container so the rail marker and reveal detection use that container as their scroll basis:

const scrollRef = React.useRef<HTMLDivElement>(null);

<div ref={scrollRef} className="h-[300px] overflow-y-auto">
  <InterviewSpread container={scrollRef}>{/* … */}</InterviewSpread>
</div>;

Props

InterviewSpread

PropTypeDefaultDescription
childrenReactNodePut InterviewTurn children here; they are laid out in order
interviewerstring"Q"The interviewer's label (default speaker for questions)
intervieweestring"A"The interviewee's label (default speaker for answers)
railbooleantrueWhether to show the speaker marker rail on the left
railWidthnumber56Rail width (px)
containerRefObject<HTMLElement | null>Ref of a nested overflow scroll container; defaults to the window as the scroll container
oncebooleantruePlays the reveal only the first time the element enters the viewport
aria-labelstring"Interview"accessible label for the layout

InterviewTurn

PropTypeDefaultDescription
role"question" | "answer""answer"Questions get the bold narrow left column, answers the serif wide right column
speakerstringOverrides the speaker label for this turn; defaults to the container's setting
quoteMarkbooleantrue for answersWhether to show the oversized quotation mark anchoring the layout
childrenReactNodeThe spoken content

How it works

  • Questions are set in bold sans-serif and answers in a serif face, alternating left and right across narrow and wide columns to create the layout rhythm of a magazine interview.
  • Each answer is anchored at its top left by an oversized quotation mark (quoteMark, which can be turned off).
  • As you scroll, the marker on the left rail slides along its track: its position follows the scroll progress, and an IntersectionObserver detects which turn is inside the reading-line band to switch the marker's color and speaker initials.
  • Turns emerge in sequence via whileInView, with questions drifting in from the left and answers from the right as they fade in.

Accessibility

  • The rail marker is decorative only (aria-hidden), and the speaker information is also presented as a text label at the start of each turn.
  • Answers are marked up semantically as blockquote; the reading order is the natural order of the questions and answers.
  • The layout container carries role="group" and an aria-label (defaulting to "Interview", which can be overridden).
  • When the user has "reduce motion" enabled at the system level, the reveal and rail-sliding animations are disabled and the full static layout is presented directly.
  • The rail marker's position measurement and scroll watching all clean up their ResizeObserver / IntersectionObserver and event listeners on unmount.

On this page