Components / 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.
Preview
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
Run in your terminal:
$npm install clsx tailwind-merge lucide-react - 2
Copy
lib/cn.tsbelow. Skip if you already havecn(). - 3
Copy the code below and create
components/text/editorial-quote-card.tsxin your project. - 4
Import and render:
Exampleimport { EditorialQuoteCard } from "@/components/text/editorial-quote-card";
lib/cn.ts
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
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 (<divref={ref}data-slot="editorial-quote-card"className={cn("w-sm border border-neutral-200 bg-[#f6f2eb] p-5 font-sans sm:p-6",className,)}{...props}><divdata-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><blockquotedata-slot="editorial-quote-card-quote"className="text-lg leading-snug font-semibold tracking-tight text-neutral-700 sm:text-xl">{segments.map(({ id, part }) =>part.toLowerCase() === accentWord.toLowerCase() ? (<spankey={id}className="rounded-sm bg-neutral-300/60 px-1 text-neutral-800">{part}</span>) : (<span key={id}>{part}</span>),)}</blockquote><divdata-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";