OpenSourceUIOpenSourceUIOpensource UI
GitHub—
  1. Home
  2. Components
  3. Text
  4. Editorial Quote
Back to TextComponents / Text

Editorial Quote

Free Text React component — EditorialQuoteCard. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.

Pull quote with issue metadata and a highlighted word inside the line — author and role at the bottom. Built for case studies and long reads.

Logo

Your logo on every docs page

Platinum founding · 5 spots left

Claim

Preview

Issue 06 · Design
Good design is as little design as possible — but every pixel must earn its place.

Dieter Rams

Industrial Designer

Setup

How to use

Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.

  1. 1

    Run in your terminal:

    $npm install clsx tailwind-merge lucide-react
  2. 2

    Copy lib/cn.ts below. Skip if you already have cn().

  3. 3

    Copy the code below and create components/text/editorial-quote-card.tsx in your project.

  4. 4

    Import and render:

    Example

    import { EditorialQuoteCard } from "@/components/text/editorial-quote-card";

    <EditorialQuoteCard />

lib/cn.ts

1
2
3
4
5
6
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

components/text/editorial-quote-card.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import { forwardRef, type ComponentPropsWithoutRef } from "react";
 
import { cn } from "@/lib/cn";
 
import { Quote } from "lucide-react";
 
export type EditorialQuoteCardProps = Readonly<
{
quote?: string;
author?: string;
role?: string;
issue?: string;
accentWord?: string;
} & ComponentPropsWithoutRef<"div">
>;
 
// Production-ready Editorial Quote component — styled with Tailwind CSS.
export const EditorialQuoteCard = forwardRef<
HTMLDivElement,
EditorialQuoteCardProps
>(
(
{
className,
quote = "Good design is as little design as possible — but every pixel must earn its place.",
author = "Dieter Rams",
role = "Industrial Designer",
issue = "Issue 06 · Design",
accentWord = "pixel",
...props
},
ref,
) => {
const parts = quote.split(new RegExp(`(${accentWord})`, "i"));
let segmentOffset = 0;
const segments = parts.map((part) => {
const segment = {
id: `quote-part-${segmentOffset}`,
part,
};
segmentOffset += part.length;
return segment;
});
 
return (
<div
ref={ref}
data-slot="editorial-quote-card"
className={cn(
"w-sm border border-neutral-200 bg-[#f6f2eb] p-5 font-sans md:p-6",
className,
)}
{...props}
>
<div
data-slot="editorial-quote-card-meta"
className="mb-4 flex items-center justify-between border-b border-neutral-200 pb-3"
>
<span className="font-mono text-[10px] font-semibold tracking-widest text-neutral-500 uppercase">
{issue}
</span>
<Quote size={18} className="text-neutral-400" />
</div>
 
<blockquote
data-slot="editorial-quote-card-quote"
className="text-lg leading-snug font-semibold tracking-tight text-neutral-700 md:text-xl"
>
{segments.map(({ id, part }) =>
part.toLowerCase() === accentWord.toLowerCase() ? (
<span
key={id}
className="rounded-sm bg-neutral-300/60 px-1 text-neutral-800"
>
{part}
</span>
) : (
<span key={id}>{part}</span>
),
)}
</blockquote>
 
<div
data-slot="editorial-quote-card-author"
className="mt-5 flex items-center gap-3"
>
<div className="h-px flex-1 bg-neutral-200" />
<div className="text-right">
<p className="text-sm font-medium text-neutral-600">{author}</p>
<p className="text-[11px] text-neutral-500">{role}</p>
</div>
</div>
</div>
);
},
);
 
EditorialQuoteCard.displayName = "EditorialQuoteCard";
PreviousNext

On this

page

Jump to a section on this page.

  • lib/cn.ts
  • components/text/editorial-quote-card.tsx
Platinum slotAvailable

Feature your product here.

Sticky card next to every component — highest-intent placement.

Claim this card