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
| Part | Selector | Role |
|---|---|---|
| Container | .prose | Establishes 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
| Part | Selector | Role |
|---|---|---|
| Paragraph | .prose p | Standard body text |
| Lede | article .prose > p:first-of-type | Opening 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
| Part | Selector | Role |
|---|---|---|
| Article h1 | article header h1 | Page title |
| Default h1 | h1 | Non-article page titles |
| h2 | h2, .prose h2 | Section headings within content |
| h3 | h3, .prose h3 | Subsection 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 */
| Element | Size | Additional |
|---|---|---|
| Article h1 | 2rem (1.6rem at <=600px) | line-height: 1.2, margin-bottom: var(--space-sm) |
| Default h1 | 1.5rem | margin-bottom: var(--space-md) |
| h2 | 1.2rem | margin-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
rehypeSlugfor deep linking. - Headings are not wrapped in anchor tags (
rehypeAutolinkHeadingsis 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.
Links
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
Prose links
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)
| State | Property | Value |
|---|---|---|
| Default | background-size | 0% 1px, 100% 1px |
| Default (not hovered) | background-position | 100% 100%, 0% 100% |
| Hover | background-size | 100% 1px, 100% 1px |
| Hover | background-position | 0% 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.
List links
Post lists and media lists. No underline. Typographic emphasis through weight shift.
Specs (.post-list a, .media-main):
text-decoration: none
background-image: none
| State | Property | Value |
|---|---|---|
| Default | font-variation-settings | 'wght' 400 |
| Default | color | var(--text) |
| Hover | font-variation-settings | 'wght' 500 |
| Hover | color | var(--accent) |
Transition: color 0.3s ease, font-variation-settings 0.4s cubic-bezier(0.22, 1, 0.36, 1).
Nav links
Footer, side rail, breadcrumb. Color-only transitions with no underline or weight change.
Specs (.footer-nav-links a, .side-rail-nav a):
| State | Property | Value |
|---|---|---|
| Default | color | var(--text-2) or var(--text-3) |
| Hover | color | var(--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-2outline with 2px offset (global:focus-visiblerule). - 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
| Part | Selector | Role |
|---|---|---|
| Container | .prose blockquote | Quote wrapper with left rule |
| Text | .prose blockquote p | Paragraph(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:
| Mode | Value |
|---|---|
| Light | 0 -1px 0 rgba(0, 0, 0, 0.08), 0 1px 0 rgba(255, 252, 240, 0.7) |
| Dark | 0 -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.
| State | Property | Value |
|---|---|---|
| Default | font-weight | 340 |
| Hover | font-weight | 350 |
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
| Part | Selector | Role |
|---|---|---|
| Rule | .prose hr | Hides default border, provides margin |
| Dinkus | .prose hr::before | Renders 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:
| Mode | Value |
|---|---|
| Light | 0 1px 2px hsl(40deg 10% 50% / 0.06), 0 2px 4px hsl(40deg 10% 40% / 0.03) |
| Dark | 0 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
| Part | Selector | Role |
|---|---|---|
| List container | .prose ul, .prose ol | Standard list with left padding |
| List item | .prose li | Individual item |
| Nested list | .prose ul ul, .prose ol ul | Indented 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
| Part | Selector | Role |
|---|---|---|
| Table | .prose table | Container |
| Header cell | .prose th | Column header, bold |
| Data cell | .prose td | Content cell |
| Wide variant | .prose .table-wide | Breaks 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
| Part | Selector | Role |
|---|---|---|
| Container | .prose .footnotes | Wrapper with top rule |
| Heading | .prose .footnotes h2 | Section label (h3-style treatment) |
| List | .prose .footnotes ol | Numbered footnote entries |
| Item | .prose .footnotes li | Individual footnote |
| Backref link | .prose .footnotes a[data-footnote-backref] | Return-to-text link |
| Superscript link | .prose sup a | In-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
| State | Property | Value |
|---|---|---|
| Default | color | var(--text-2) |
| Hover | color | var(--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
| Part | Selector | Role |
|---|---|---|
| Note container | .margin-note | Note wrapper |
| Inline variant | .margin-note-inline | Inline-derived note |
| Footnote variant | .margin-note-footnote | Footnote-derived note |
| Bracket decoration | .margin-note-bracket | Visual bracket framing (mobile) |
| Connector | .margin-note-connector | SVG 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
| Breakpoint | Behavior |
|---|---|
| <1080px | Block callout asides. Inline and footnote notes hidden. |
| >=1080px | Absolutely positioned in right margin, 8rem wide. All types visible. |
| >=1280px | Width increases to 10rem. |
display: 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
| Part | Selector | Role |
|---|---|---|
| Container | .must-read-note | Flex wrapper, positioned in margin |
| Arrow | .must-read-arrow | 80x20 SVG, rough.js curved Bezier path |
| Label | .must-read-label | Handwritten 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
| Breakpoint | Behavior |
|---|---|
| <1080px | Hidden entirely |
| >=1080px | Positioned in right margin |
display: 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
-
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