Skip to content

Hanging Quote Section

A pull quote with the opening mark optically aligned — measured from the real glyph, not a guessed em value.

DOM + CSS0 deps1 file · 5 KB TSX

ProEditorialeditorialminimal
Preview

Props

The source is part of Pro

$27/month or $179 once. See Pro

No runtime dependencies
"use client";

import { useEffect, useId, useRef, useState } from "react";

export function HangingQuoteSection({
  quote = "We design the parts people actually touch. The hinge, the handle, the moment a thing answers back.",
  attribution = "Ana Reis",
  role = "Founder, Blackhorse Yard",
}: {
  quote?: string;
  attribution?: string;
  role?: string;
}) {
  const cls = "hq-" + useId().replace(/:/g, "");
  const measureRef = useRef<HTMLSpanElement>(null);
  const [advance, setAdvance] = useState(0);

  useEffect(() => {

  // ---------------------------------------------------------------
  // Full source available with a viberdy Pro subscription.
  // https://viberdy.dev/pricing
  // ---------------------------------------------------------------
}
Details

About this section

Optical alignment is the detail that separates typeset copy from typed copy: an opening quotation mark carries so much white space inside its own box that leaving it in the text block makes the left edge look broken, so it hangs into the margin instead. The usual implementation hardcodes a negative indent of about four tenths of an em, which is correct for exactly one typeface at one weight — the advance of an opening quote varies enormously between faces, and again between the curly and the straight form. Measuring the actual character, rendered in the same face, size and weight, and feeding the result to a custom property makes it right for whatever font the page ends up using, and re-measuring once webfonts resolve fixes the case where the first measurement belonged to a fallback. Two smaller decisions matter as much. The offset is applied with text-indent rather than a negative margin, because text-indent moves the first line only, which is precisely what hanging a quote requires, while a margin drags the whole block and every line under it. And the native hanging-punctuation property — still Safari-only — is layered in with a feature query rather than a JavaScript test, both because the two must never apply together and double the hang, and because a JS branch would render differently on the server than on the client.

Curator’s note

A hardcoded -0.4em is right for exactly one typeface. Measure the glyph you are actually rendering.

Pairs well with

Pro
Pro