Quote card
A pulled quote with its attribution, and an oversized quote mark drawn behind it.
QuoteCard, defaults
Installation
npx shadcn add https://www.reelcn.dev/r/quote-card.jsonFiles land in src/reelcn/ with the items it builds on.
Usage
<Center>
<QuoteCard quote="We cut a week of editing down to an afternoon." name="Grace Hopper" role="Head of Video" />
</Center>Props
PropTypeDescription
delaynumberFrames to wait before entering.
durationnumberEnter duration in frames. Defaults to 0.6s. Some items reuse
duration for an unrelated local timing. See that item's own prop docs.exitnumber | booleanExit at the end of the parent `<Sequence>`.
false keeps it on screen, a number sets the exit length in frames.motionMotionPersonalityOverride the theme's motion personality.
posterbooleanRenders fully entered, with no exit: for a still frame or thumbnail. Defaults to
false.holdFramesnumberDelays the start of the exit window by this many frames, for a minimum on-screen hold. Defaults to
0.quote*string
name*string
rolestring
avatarReactNodeAvatar image; pass an `<Img>`. Initials are drawn when omitted.
widthnumber Default
820.sizenumber Default
44.styleCSSProperties
classNamestring
Built from
Use
- Customer praise and testimonials
- Lifting one line out of an interview
Avoid
- A social post with engagement counts. Use
post-card
Guides
Components take their colors from a theme, their timing from motion props and their layout from the canvas.
Source
registry/items/quote-card.tsx
/**
* @title Quote Card
* @category social
* @description A pulled quote with its attribution, and an oversized quote mark drawn behind it.
* @duration 40
* @use Customer praise and testimonials
* @use Lifting one line out of an interview
* @avoid A social post with engagement counts. Use `post-card`
* @tags quote, testimonial, praise, card
* @example
* <Center>
* <QuoteCard quote="We cut a week of editing down to an afternoon." name="Grace Hopper" role="Head of Video" />
* </Center>
*/
import type React from "react";
import { alpha, graphemeInitial, type MotionProps, tween, useMotion, useTheme, useViewport } from "./core";
export type QuoteCardProps = MotionProps & {
quote: string;
name: string;
role?: string;
/** Avatar image; pass an `<Img>`. Initials are drawn when omitted. */
avatar?: React.ReactNode;
width?: number;
size?: number;
style?: React.CSSProperties;
className?: string;
};
export function QuoteCard({
quote,
name,
role,
avatar,
width = 820,
size = 44,
style,
className,
...motion
}: QuoteCardProps) {
const theme = useTheme();
const { u } = useViewport();
const m = useMotion(motion);
const enter = Math.min(m.enter, 1);
const attribution = Math.min(
tween(m.frame, m.fps, { from: m.delay + Math.round(m.fps * 0.35), duration: m.enterFrames, motion: m.preset }),
1,
);
return (
<div
className={className}
style={{
position: "relative",
width: u(width),
maxWidth: "100%",
padding: u(36),
borderRadius: u(theme.radius),
background: theme.colors.surface,
border: `1px solid ${theme.colors.border}`,
color: theme.colors.foreground,
overflow: "hidden",
opacity: enter * (1 - m.exit),
translate: `0 ${(1 - enter) * u(22)}px`,
...style,
}}
>
<div
aria-hidden="true"
style={{
position: "absolute",
top: u(-30),
left: u(12),
fontFamily: theme.fonts.heading,
fontSize: u(220),
lineHeight: 1,
color: alpha(theme.colors.accent, 0.16),
}}
>
“
</div>
<div
style={{
position: "relative",
fontFamily: theme.fonts.heading,
fontWeight: theme.headingWeight,
fontSize: u(size),
lineHeight: 1.28,
letterSpacing: "-0.02em",
textWrap: "pretty",
}}
>
{quote}
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: u(14),
marginTop: u(26),
opacity: attribution,
translate: `0 ${(1 - attribution) * u(10)}px`,
}}
>
<div
style={{
width: u(52),
height: u(52),
borderRadius: "50%",
overflow: "hidden",
background: theme.colors.accent,
color: theme.colors.accentForeground,
display: "grid",
placeItems: "center",
fontSize: u(22),
fontWeight: 700,
}}
>
{avatar ?? graphemeInitial(name)}
</div>
<div>
<div style={{ fontSize: u(24), fontWeight: 650 }}>{name}</div>
{role ? <div style={{ fontSize: u(20), color: theme.colors.muted }}>{role}</div> : null}
</div>
</div>
</div>
);
}