Content

Content components handle everything inside the reading column: the prose container, headings, body text, links, blockquotes, pull quotes, section breaks, code, lists, tables, footnotes, and margin notes. Together they form the typographic surface the reader spends time with.

For exact CSS values and selectors, see the implementation spec. For the type scale, see typography foundations.


Prose container

Purpose

.prose wraps all reading content on article and standalone pages1. It establishes the typographic baseline: Signifier, --text-2, vertical rhythm through adjacent sibling spacing.

Anatomy

PartSelectorRole
Container.proseEstablishes font, color, spacing context
Element spacing.prose > * + *Vertical rhythm between direct children

Specs

font-family: var(--font-serif)
font-size: 1rem
color: var(--text-2)
text-wrap: pretty
hanging-punctuation: first last
position: relative

Adjacent sibling spacing: .prose > * + * applies margin-top: var(--space-md). This single rule creates consistent vertical rhythm without element-specific margins for most cases. Headings, blockquotes, and section breaks override it with larger values where more air is needed.

Usage guidance

Wrap all editorial content in .prose. Do not apply prose styles outside the content column. The container assumes --content-width (42rem) through its parent .site-wrapper.


Paragraphs and lede

Purpose

Body paragraphs are the default reading unit. The lede paragraph distinguishes the opening of an article with slightly larger type and primary color, a newspaper convention that signals the entry point.

Anatomy

PartSelectorRole
Paragraph.prose pStandard body text
Ledearticle .prose > p:first-of-typeOpening paragraph, emphasized

Specs

Paragraph:

margin-bottom: var(--space-md)

Inherits font-family, size, and color from .prose.

Lede paragraph:

font-size: 1.1rem
color: var(--text)
line-height: 1.55

The lede uses --text (primary ink) rather than the container’s --text-2, elevating it above surrounding prose. This is unified with the .intro p:first-of-type sizing on the homepage so both entry points feel the same weight.

Variants

  • Now page (article.now-page): Lede resets to inherited styles. No special first-paragraph treatment.
  • Shortlist post (article.shortlist-post): Lede resets to inherited styles. No drop cap.
  • Epigraph opening (article .prose > blockquote:first-child + p): When a post opens with a blockquote, the following paragraph resets to normal size. The epigraph already serves as the entry point.

Code example

<article class="post has-dropcap">
  <div class="prose">
    <p>This first paragraph becomes the lede — 1.1rem in primary color.</p>
    <p>Subsequent paragraphs render at the standard 1rem in secondary color.</p>
  </div>
</article>

Headings

Purpose

Headings create hierarchy through typeface contrast. h1 and h2 use Signifier (the reading voice). h3 uses Montreuil all-caps (the functional voice). The typeface shift IS the hierarchy. No borders, no backgrounds, no decorative treatment beyond the letterpress text-shadow.

Anatomy

PartSelectorRole
Article h1article header h1Page title
Default h1h1Non-article page titles
h2h2, .prose h2Section headings within content
h3h3, .prose h3Subsection headings, functional voice

Specs

h1 and h2 (shared):

font-family: var(--font-serif)
font-weight: 400
line-height: 1.3
letter-spacing: -0.01em
color: var(--text)
text-shadow: 0 1px 0 rgba(255, 252, 240, 0.7),
             0 -1px 0 rgba(16, 15, 15, 0.08)   /* light */
ElementSizeAdditional
Article h12rem (1.6rem at <=600px)line-height: 1.2, margin-bottom: var(--space-sm)
Default h11.5remmargin-bottom: var(--space-md)
h21.2remmargin-top: var(--space-xl), margin-bottom: var(--space-md)

h3:

font-family: var(--font-sans)
font-weight: 600
font-size: 0.75rem
line-height: 1.4
letter-spacing: 0.08em
text-transform: uppercase
color: var(--text-2)
margin-top: var(--space-xl)
margin-bottom: var(--space-sm)

h3 also gets the letterpress text-shadow in both modes, matching h1/h2 treatment.

Dark mode text-shadow (h1, h2, h3):

text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.4),
             0 1px 0 rgba(206, 205, 195, 0.06)

Accessibility

  • Heading IDs are added automatically by rehypeSlug for deep linking.
  • Headings are not wrapped in anchor tags (rehypeAutolinkHeadings is not used).

Usage guidance

Use h2 for top-level sections within prose. Use h3 for subsections. The jump from Signifier serif (h2) to Montreuil sans all-caps (h3) creates clear hierarchy at a smaller size. Never add background fills or borders to distinguish heading levels.


Purpose

Links use three distinct behavioral patterns depending on context. Each matches the interaction expectations of its environment. The behaviors are a system: consistent in intent (signal interactivity), varied in expression (match their surroundings).

Variants

Links within .prose content. A faint --ui-2 underline is always visible, with an accent-colored sweep on hover.

Specs:

color: var(--text)
text-decoration: none
background-image:
  linear-gradient(var(--accent), var(--accent)),
  linear-gradient(var(--ui-2), var(--ui-2))
background-position: 0% 100%, 0% 100%
background-repeat: no-repeat, no-repeat
background-size: 0% 1px, 100% 1px
padding-bottom: 1px
transition: background-size 0.3s cubic-bezier(0.22, 1, 0.36, 1)
StatePropertyValue
Defaultbackground-size0% 1px, 100% 1px
Default (not hovered)background-position100% 100%, 0% 100%
Hoverbackground-size100% 1px, 100% 1px
Hoverbackground-position0% 100%, 0% 100%

The direction trick: background-position shifts to 0% 100% on hover (accent sweeps left-to-right) and resets to 100% 100% when not hovered (retracts right-to-left). The persistent --ui-2 underline remains beneath both states.

External link indicator (.prose a[href^="http"]:not([href*="alexpriest.com"])::after):

content: "\2060↗"
font-size: 0.7em
margin-left: 0.15em
color: color-mix(in srgb, var(--accent) 60%, transparent)
text-decoration: none

The word-joiner character (\2060) prevents line breaks before the arrow.

Post lists and media lists. No underline. Typographic emphasis through weight shift.

Specs (.post-list a, .media-main):

text-decoration: none
background-image: none
StatePropertyValue
Defaultfont-variation-settings'wght' 400
Defaultcolorvar(--text)
Hoverfont-variation-settings'wght' 500
Hovercolorvar(--accent)

Transition: color 0.3s ease, font-variation-settings 0.4s cubic-bezier(0.22, 1, 0.36, 1).

Footer, side rail, breadcrumb. Color-only transitions with no underline or weight change.

Specs (.footer-nav-links a, .side-rail-nav a):

StatePropertyValue
Defaultcolorvar(--text-2) or var(--text-3)
Hovercolorvar(--text) or var(--text-2)

Transition: color 0.2s ease.

Responsive behavior

At <=600px, prose link sweep animation is disabled. The persistent --ui-2 underline remains, and the accent underline appears instantly on tap without the left-to-right transition. This prevents animation lag on touch interactions.

Accessibility

  • All link types maintain sufficient color contrast against their backgrounds.
  • Focus state uses a 2px --text-2 outline with 2px offset (global :focus-visible rule).
  • External link indicators are decorative (the arrow is appended via ::after, so assistive technology reads the link text without the symbol).

Blockquotes

Purpose

Standard quoted passages. Indented italic with a thin left rule. No background, no box-shadow, no border-radius. A common typographic convention in well-set books.

Anatomy

PartSelectorRole
Container.prose blockquoteQuote wrapper with left rule
Text.prose blockquote pParagraph(s) within the quote

Specs

padding-left: 1.5em
border-left: 2px solid var(--ui)
font-style: italic
color: var(--text-2)
margin: var(--space-lg) 0
hanging-punctuation: first

.prose blockquote p:last-child has margin-bottom: 0.

Usage guidance

Use blockquotes for attributed quotations and cited passages. For standalone punchy statements extracted from the text, use a pull quote instead. Blockquotes are citations; pull quotes are editorial emphasis.

The reader deserves an author who is fully present, not one who is merely performing the role of writer.

The blockquote above demonstrates the style: italic Signifier, 2px left rule in --ui, indented 1.5em.

Code example

<blockquote>
  <p>Quoted text here.</p>
</blockquote>

Pull quotes

Purpose

Editorial emphasis pulled from the surrounding text. Two variants serve different purposes: centered for punchy standalone statements, light for reflective passages with letterpress treatment.

Variants

Centered (.pull-quote)

Short, punchy. Centered, no borders, primary ink color.

Specs:

font-family: var(--font-serif)
font-style: italic
font-size: 1.25rem
line-height: 1.5
color: var(--text)
border-left: none
padding: var(--space-lg) 0
text-align: center
margin: var(--space-xl) 0
hanging-punctuation: first

.prose .pull-quote p has margin: 0.

Design is the art of making decisions visible.

Code example:

<div class="pull-quote">
  <p>Short punchy statement.</p>
</div>

Light (.pull-quote-light)

Longer, reflective. Left-aligned with a hanging open-quote and letterpress text-shadow. The lighter weight (340) and secondary color create a contemplative quality.

Specs:

font-family: var(--font-serif)
font-style: italic
font-weight: 340
font-size: 1.35rem
line-height: 1.5
color: var(--text-2)
border-left: none
padding: var(--space-md) 0
padding-left: 3rem
text-align: left
text-wrap: pretty
hanging-punctuation: first
margin: var(--space-lg) 0
position: relative

Letterpress text-shadow:

ModeValue
Light0 -1px 0 rgba(0, 0, 0, 0.08), 0 1px 0 rgba(255, 252, 240, 0.7)
Dark0 -1px 0 rgba(0, 0, 0, 0.5), 0 1px 0 rgba(255, 255, 255, 0.06)

Hanging open-quote (::before):

content: "\201C"
font-family: var(--font-serif)
font-style: normal
font-size: 3rem
line-height: 1
color: var(--text-2)
opacity: 0.5
position: absolute
top: 0.15em
left: 1rem

As link (a.pull-quote-light): When wrapped in an anchor, clicking generates a share image and opens the share modal. The link strips visual link styling and adds a subtle weight shift on hover.

StatePropertyValue
Defaultfont-weight340
Hoverfont-weight350

Transition: font-weight 0.3s ease. No underline, no background-image, cursor: pointer.

The confidence that comes from not needing to prove anything is the quietest and most powerful form of authority.

Code example:

<div class="pull-quote-light">
  <p>Longer reflective passage with letterpress treatment.</p>
</div>

<!-- As shareable link -->
<a class="pull-quote-light" href="#" data-share-quote>
  <p>A passage worth sharing.</p>
</a>

Section breaks

Purpose

The <hr> element renders as a dinkus: three em-spaced asterisks. The ornament signals a thematic break between sections without introducing a heading.

Anatomy

PartSelectorRole
Rule.prose hrHides default border, provides margin
Dinkus.prose hr::beforeRenders the three-asterisk ornament

Specs

.prose hr {
  border: none;
  margin: var(--space-xl) 0;
  text-align: center;
  line-height: 1;
}

.prose hr::before {
  content: "*\2003*\2003*";
  font-family: var(--font-serif);
  color: var(--text-3);
  font-size: 1.25rem;
  letter-spacing: 0.3em;
  opacity: 0.65;
}

The asterisks are separated by em-spaces (\2003). In Signifier, the upright asterisk renders as a six-pointed star glyph. This is the same glyph used in the homepage live indicator, creating a quiet visual motif across the site. The --text-3 color and 65% opacity keep the ornament subdued. The ornaments between each section on this page are live examples.

Usage guidance

Use <hr> for thematic breaks only. Do not use it as a visual separator between unrelated sections (that’s what headings and spacing are for). In Obsidian, a standard --- converts to <hr> during sync.


Code

Purpose

Code elements use the monospace voice (SF Mono) on a recessed surface (--code-bg) with a subtle border and shadow. The surface treatment echoes a paper strip or card laid on the page.

Variants

Inline code (.prose code)

font-family: var(--font-mono)
font-size: 0.85em
background: var(--code-bg)
color: var(--code-text)
padding: 0.15em 0.35em
border-radius: 3px
border: 1px solid var(--ui)
box-shadow: 0 0.5px 1px hsl(40deg 10% 50% / 0.04)

Block code (.prose pre)

background: var(--code-bg)
padding: var(--space-md)
border-radius: 4px
white-space: pre-wrap
overflow-wrap: break-word
margin: var(--space-lg) 0
border: 1px solid var(--ui)
counter-reset: line

Box-shadow by mode:

ModeValue
Light0 1px 2px hsl(40deg 10% 50% / 0.06), 0 2px 4px hsl(40deg 10% 40% / 0.03)
Dark0 1px 2px hsl(30deg 5% 20% / 0.3), 0 2px 4px hsl(30deg 5% 5% / 0.2)

Code inside pre (.prose pre code): Strips inline code styling (background: none; padding: 0; border: none; box-shadow: none; font-size: 0.85rem).

Line numbers (.prose pre .line): CSS counter-based. Each .line increments the counter. The ::before pseudo-element displays the number: width: 2.5em, margin-right: 1em, right-aligned, color: var(--text-3), user-select: none.

Code example

<!-- Inline -->
<p>Use <code>var(--accent)</code> for interactive elements.</p>

<!-- Block -->
<pre><code><span class="line">const greeting = 'hello';</span>
<span class="line">console.log(greeting);</span></code></pre>

Lists

Purpose

Standard ordered and unordered lists for body content. Nested lists use arrow markers to visually distinguish depth.

Anatomy

PartSelectorRole
List container.prose ul, .prose olStandard list with left padding
List item.prose liIndividual item
Nested list.prose ul ul, .prose ol ulIndented sublists with arrow markers

Specs

Top-level:

padding-left: 1.25em
margin: var(--space-sm) 0 var(--space-md) 0

List items:

margin-bottom: 0.35em
padding-left: 0.25em

.prose li:last-child: margin-bottom: 0.

Nested lists:

margin-top: 0.35em
margin-bottom: 0
list-style: none
padding-left: 1em

Nested list items (.prose ul ul li::before, .prose ol ul li::before):

content: "→"
color: var(--text-3)
display: inline-block
width: 1.25em
margin-left: -1.25em

Arrow markers in --text-3 replace standard bullets at the second level, providing visual distinction without adding weight.

Code example

<ul>
  <li>First item
    <ul>
      <li>Nested item with arrow marker</li>
      <li>Another nested item</li>
    </ul>
  </li>
  <li>Second item</li>
</ul>

Tables

Purpose

Data tables for structured content within prose. Full-width, collapsed borders, clean separators. No header background tinting.

Anatomy

PartSelectorRole
Table.prose tableContainer
Header cell.prose thColumn header, bold
Data cell.prose tdContent cell
Wide variant.prose .table-wideBreaks out of content column

Specs

width: 100%
border-collapse: collapse
margin: var(--space-lg) 0
font-size: 0.9rem

Cells (.prose th, .prose td):

text-align: left
vertical-align: top
padding: var(--space-sm) var(--space-sm)
border-bottom: 1px solid var(--ui)

Header (.prose th): font-weight: 600; color: var(--text). No background color.

Last row: .prose tr:last-child td removes the bottom border.

Wide variant (.prose .table-wide):

width: calc(100% + min(6rem, 6vw))
margin-left: calc(-1 * min(3rem, 3vw))
margin-right: calc(-1 * min(3rem, 3vw))

Responsive behavior

At <=600px, wide tables collapse to a smaller breakout: width: calc(100% + 2rem); margin-left: -1rem; margin-right: -1rem.

Code example

<table>
  <thead>
    <tr>
      <th>Property</th>
      <th>Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Font family</td>
      <td>Signifier</td>
    </tr>
  </tbody>
</table>

<!-- Wide variant -->
<div class="table-wide">
  <table>...</table>
</div>

Footnotes

Purpose

Endnote-style footnotes at the bottom of articles, separated from body content by a rule. On wide screens (>=1080px), footnotes can be converted to margin notes via JavaScript.

Anatomy

PartSelectorRole
Container.prose .footnotesWrapper with top rule
Heading.prose .footnotes h2Section label (h3-style treatment)
List.prose .footnotes olNumbered footnote entries
Item.prose .footnotes liIndividual footnote
Backref link.prose .footnotes a[data-footnote-backref]Return-to-text link
Superscript link.prose sup aIn-text footnote reference

Specs

Container:

margin-top: var(--space-xl)
padding-top: var(--space-lg)
border-top: 1px solid var(--ui)
font-size: 0.85rem
color: var(--text-2)

Heading (uses h3-style functional voice treatment):

font-family: var(--font-sans)
font-size: 0.7rem
font-weight: 600
text-transform: uppercase
letter-spacing: 0.08em
color: var(--text-2)
margin-bottom: var(--space-md)

Footnotes list: padding-left: 1.5em.

Footnotes items: margin-bottom: var(--space-sm).

Backref link: text-decoration: none; background-image: none; margin-left: 0.25em. Color --text-2, hover transitions to --accent.

Superscript link (.prose sup a):

font-size: 0.75em
text-decoration: none
background-image: none
color: var(--text-2)
padding: 0 0.1em
transition: color 0.2s ease
StatePropertyValue
Defaultcolorvar(--text-2)
Hovercolorvar(--accent)

Responsive behavior

At >=1080px, JavaScript can convert footnotes to margin notes. When active: .footnote-ref-hidden { display: none } hides inline superscript references, and .footnotes.margin-active { display: none } hides the footnotes section entirely.

Code example

<!-- In-text reference -->
<p>A claim that needs citation.<sup><a href="#fn1" id="fnref1">1</a></sup></p>

<!-- Footnotes section -->
<section class="footnotes">
  <h2>Footnotes</h2>
  <ol>
    <li id="fn1">
      <p>The citation source. <a href="#fnref1" data-footnote-backref>↩</a></p>
    </li>
  </ol>
</section>

Margin notes

Purpose

Annotations that appear as positioned marginalia on wide screens and callout asides on mobile. Three types exist: block notes (always visible on mobile), inline notes, and footnote-derived notes (both hidden on mobile, visible on desktop).

Anatomy

PartSelectorRole
Note container.margin-noteNote wrapper
Inline variant.margin-note-inlineInline-derived note
Footnote variant.margin-note-footnoteFootnote-derived note
Bracket decoration.margin-note-bracketVisual bracket framing (mobile)
Connector.margin-note-connectorSVG connector line (desktop)

Specs

Mobile (default):

display: block
position: relative
font-family: var(--font-sans)
font-size: 0.8rem
line-height: 1.45
color: var(--text-2)
padding: 0.25rem 1rem
margin: var(--space-sm) 0

Inline and footnote-derived notes are hidden on mobile (display: none). Only block notes show as callout asides.

Bracket decorations (.margin-note-bracket): Positioned absolutely at top: 0, left bracket at left: -4px, right bracket at right: -4px.

Desktop (>=1080px):

position: absolute
right: 0
transform: translateX(calc(100% + 1.5rem))
width: 8rem
font-size: 0.75rem
padding: 0
margin: 0

At >=1280px: width: 10rem.

All note types become visible. Links within margin notes use color: var(--text-2). The desktop left bracket repositions to left: -0.75rem; top: 0; the right bracket hides.

Responsive behavior

BreakpointBehavior
<1080pxBlock callout asides. Inline and footnote notes hidden.
>=1080pxAbsolutely positioned in right margin, 8rem wide. All types visible.
>=1280pxWidth increases to 10rem.
Printdisplay: none !important

Accessibility

Margin notes use the functional voice (Montreuil) at a smaller size to distinguish them from body prose. On mobile, bracket decorations provide visual framing. Content remains accessible in DOM order regardless of visual positioning.

Code example

<!-- Block margin note (visible on mobile) -->
<aside class="margin-note">
  <svg class="margin-note-bracket margin-note-bracket-left">...</svg>
  <p>An annotation about the adjacent content.</p>
  <svg class="margin-note-bracket margin-note-bracket-right">...</svg>
</aside>

<!-- Inline margin note (desktop only) -->
<aside class="margin-note margin-note-inline">
  <p>A note derived from inline markup.</p>
</aside>

Must-read marginalia

Purpose

Editorial annotations for link posts flagged as especially important. A hand-drawn arrow and handwritten label sit in the right margin on wide screens, pointing toward the title.

Anatomy

PartSelectorRole
Container.must-read-noteFlex wrapper, positioned in margin
Arrow.must-read-arrow80x20 SVG, rough.js curved Bezier path
Label.must-read-labelHandwritten text in Caveat

Specs

Hidden by default (display: none). Visible at >=1080px:

display: flex
align-items: center
gap: 0.35rem
position: absolute
right: 0
top: 0
transform: translateX(calc(100% + 0.15rem))
white-space: nowrap

Requires position: relative on the parent element.

Arrow: 80x20 SVG drawn at page load using rough.js. Curved Bezier path pointing left with a two-line arrowhead. Stroke color reads --text-2 via getComputedStyle at draw time. Redrawn on theme change via MutationObserver on data-theme.

Label:

font-family: 'Caveat', cursive
font-size: 1rem
font-weight: 600
color: var(--text-2)

Responsive behavior

BreakpointBehavior
<1080pxHidden entirely
>=1080pxPositioned in right margin
Printdisplay: none !important

Usage guidance

Used on /links/ index, /feed/, and the homepage links section. The Caveat typeface is the third voice in the system (editorial/handwritten), used exclusively for these author annotations. Do not use Caveat elsewhere.

Footnotes

  1. Including this page. The footnote you’re reading right now uses the footnotes styling described below.


Specimens

Headings

The Shape of Decisions

Structural clarity

Design principles

Body Text

Good prose has a rhythm you feel before you understand it. Every sentence earns its place through necessity, not decoration. The best writing disappears — you notice the thinking, not the craft behind it.

Lede Paragraph

Fifteen years of making hard decisions clear taught me that restraint is the hardest skill and the most valuable one.

Links

Read the original essay on craft or view the source material⁠↗ for context.

Blockquote

The reader deserves an author who is fully present, not one who is merely performing the role of writer.

Every unnecessary word is a small act of disrespect.

Pull Quote (Centered)

Design is the art of making decisions visible.

Pull Quote (Light)

The confidence that comes from not needing to prove anything is the quietest and most powerful form of authority.

Section Break


Code

Set the content width with --content-width: 42rem for optimal readability.

:root {
  --font-serif: 'Signifier', Georgia, serif;
  --font-sans: 'Montreuil', system-ui, sans-serif;
  --accent: #BC5215;
}

Lists

  • Typography creates all necessary hierarchy
    • Size and weight for emphasis
    • Typeface shift for role distinction
  • Color serves function, not decoration
  • Spacing creates rhythm, not density