docs / patterns / pullquote

Pullquote

a centered quote in display weight, with an optional attribution tied to it and a decorative ornament slot above.

intensity · structuresrsc · server

provenance

shaped from personal-website

layer

core / patterns

intensity

structures · organizes a region, stays out of the content

composition

long-form prose, landing sections, a manifesto beat between blocksany decorative node in the ornament slot, KajoSphere includednot for testimonials in a grid. one quote per sectionno interactive content in the ornament, it is aria-hidden

a11y

attributed quotes render figure + figcaption · the ornament is aria-hiddenjest-axe
live demo · try it out
Beauty that stays usable.
usva, design principles
customize
childrenthe quote itself
attributionempty leaves a bare blockquote
ornamentdecorative flourish in the 80px slot
usagecopy
import { Pullquote } from "usva/patterns/pullquote";

<Pullquote
  attribution="usva, design principles"
>  Beauty that stays usable.
</Pullquote>

props

proptypedefaultnotes
childrenReactNodethe quote. rendered as a blockquote.
attributionReactNodeupgrades the wrapper to a figure and becomes its figcaption.
ornamentReactNodedecorative flourish above the quote, in a fixed 80px slot.

get it

npx shadcn add https://usva.build/r/pullquote.jsoncopy
source · components/ui/pullquote.tsxexactly what this command copiescopy
import * as React from "react";
import { cn } from "@/lib/utils";

export interface PullquoteProps
  extends Omit<React.HTMLAttributes<HTMLElement>, "cite"> {
  attribution?: React.ReactNode;
  /**
   * Decorative flourish above the quote. kajo passes a FogSphere here, which is
   * Pro-licensed and cannot ship from this package. Keeping it a slot is what
   * lets the quote itself be public.
   */
  ornament?: React.ReactNode;
  children: React.ReactNode;
}

/**
 * A centered pull quote. Renders a `figure` only when attributed, so the
 * attribution is programmatically tied to the quote rather than floating
 * beneath it as a loose paragraph.
 */
export const Pullquote = React.forwardRef<HTMLElement, PullquoteProps>(
  ({ className, attribution, ornament, children, ...props }, ref) => {
    const body = (
      <>
        {ornament != null && (
          <div
            aria-hidden="true"
            data-pullquote-ornament=""
            className="mx-auto mb-4 size-20"
          >
            {ornament}
          </div>
        )}
        <blockquote className="text-balance text-[clamp(1.125rem,2.5vw,1.625rem)] font-bold leading-[1.4] tracking-[-0.01em] text-ink">
          {children}
        </blockquote>
      </>
    );

    const shell = cn("mx-auto max-w-3xl py-4 text-center", className);

    if (attribution == null)
      return (
        <div
          ref={ref as React.Ref<HTMLDivElement>}
          className={shell}
          {...props}
        >
          {body}
        </div>
      );

    return (
      <figure ref={ref as React.Ref<HTMLElement>} className={shell} {...props}>
        {body}
        <figcaption className="mt-3 font-mono text-sm text-muted">
          {attribution}
        </figcaption>
      </figure>
    );
  },
);
Pullquote.displayName = "Pullquote";