Navigation
Navigation on this site borrows from the structure of books, not software. The header is a nameplate, not a nav bar. The breadcrumb is a table of contents, not a toolbar. The side rail is a spine label, not a sidebar. The footer is a colophon, not a sitemap.
Every navigation element uses typography alone to establish hierarchy. No backgrounds, no borders (except the footer’s single rule), no icons in the primary flow.
For exact CSS values, see the implementation spec and tokens.json.
Header / nameplate
Purpose
The header anchors every page with the author’s name. On the homepage, the name stands alone at display scale as the sole identification. On interior pages, it shrinks to breadcrumb size and becomes a link home. Same element, different scales. The name is the brand.
Anatomy
| Part | Selector | Description |
|---|---|---|
| Header container | .site-header | Flex row, baseline-aligned |
| Nameplate (homepage) | .site-header .name:not(a) | Non-linked display name |
| Nameplate (interior) | .site-header a.name | Linked name, breadcrumb scale |
Specs
Homepage nameplate renders in Signifier at 2.5rem, weight 400, letter-spacing: -0.02em, line-height: 1.2. It uses --text color. No link, no tagline. The intro section below handles context.
Interior nameplate renders at 1.1rem, weight 400, with font-variation-settings: 'wght' 400. A fixed width: 4.9em on the inline-block element prevents layout reflow when the variable weight shifts on hover.
The header container uses margin-bottom: var(--space-xl) to separate the nameplate from content.
States
| State | Property | Value |
|---|---|---|
| Default | color | var(--text) |
| Default | font-variation-settings | 'wght' 400 |
| Hover (interior) | color | var(--accent) |
| Hover (interior) | font-variation-settings | 'wght' 500 |
The weight shift from 400 to 500 on hover is the signature interaction for navigation links. It uses the site’s characteristic easing: 0.4s cubic-bezier(0.22, 1, 0.36, 1) for the weight, 0.3s ease for color.
Responsive behavior
| Breakpoint | Change |
|---|---|
<= 600px | Homepage nameplate drops to 1.8rem |
Accessibility
The homepage nameplate is a <span>, not a heading. It carries no semantic role because the page’s <h1> comes from the content below. Interior pages use the nameplate as a navigation link with visible focus styling.
Code example
Homepage:
<header class="site-header">
<span class="name">Alex Priest</span>
</header>
Interior page:
<header class="site-header">
<nav class="breadcrumb">
<a href="/" class="name">Alex Priest</a>
<span class="separator">→</span>
<span class="current">Writing</span>
</nav>
</header>
Breadcrumb
Purpose
Wayfinding that reads like navigating a book’s table of contents. The breadcrumb shows where you are in the site’s structure: name, arrow, section, and optionally a second arrow and page title. The breadcrumb above this page — “Alex Priest → Colophon → Navigation” — is the pattern described here.
Anatomy
| Part | Selector | Description |
|---|---|---|
| Container | .breadcrumb | Flex row, baseline-aligned, gap: 0.75em |
| Name link | .breadcrumb a.name | Link home (see nameplate above) |
| Separator | .breadcrumb .separator | Arrow character (→), decorative |
| Current segment | .breadcrumb .current | Current section or page label |
| Intermediate link | .breadcrumb a:not(.name):not(.current) | Section link on detail pages |
Specs
The breadcrumb uses Signifier, not Montreuil. This is deliberate. You’re navigating a book’s structure, reading your way through sections and chapters. The reading voice is correct here, even though it’s technically navigation.
All breadcrumb text renders at 1.1rem. The separator uses --text-3 color and sits top: -0.05em relative to maintain optical baseline alignment with the text.
The .current segment uses --text-2 and font-variation-settings: 'wght' 400. When the current segment is a link (a.current), it gains the same hover behavior as the name link.
Intermediate links (.breadcrumb a:not(.name):not(.current)) share the same treatment: --text-2 color, Signifier at 1.1rem, weight 400, with the same weight-shift hover.
States
Name link: see header states above.
Current segment (when linked):
| State | Property | Value |
|---|---|---|
| Default | color | var(--text-2) |
| Default | text-decoration | none |
| Hover | color | var(--accent) |
| Hover | font-variation-settings | 'wght' 500 |
Intermediate link:
| State | Property | Value |
|---|---|---|
| Default | color | var(--text-2) |
| Hover | color | var(--accent) |
| Hover | font-variation-settings | 'wght' 500 |
Usage guidance
Breadcrumbs appear on every page except the homepage. A section index page (e.g., /writing) shows Name → Section. A detail page (e.g., /writing/some-post) shows Name → Section → Title, where “Section” is an intermediate link.
Code example
Section index:
<nav class="breadcrumb">
<a href="/" class="name">Alex Priest</a>
<span class="separator">→</span>
<span class="current">Writing</span>
</nav>
Detail page:
<nav class="breadcrumb">
<a href="/" class="name">Alex Priest</a>
<span class="separator">→</span>
<a href="/writing" class="current">Writing</a>
<span class="separator">→</span>
<span class="current">Post title</span>
</nav>
Side rail
Purpose
Peripheral navigation for wide screens. The side rail provides section links and controls (theme toggle, accent picker) without leaving the reading flow. It sits in the reader’s peripheral vision like a book’s spine label, present but undemanding. If your screen is wide enough (1080px+), the side rail is visible to your right now.
Anatomy
| Part | Selector | Description |
|---|---|---|
| Container | .side-rail | Fixed-position column, right edge |
| Navigation group | .side-rail-nav | Vertical link stack |
| Section group | .side-rail-group | Subgroup within nav (e.g., sections, current) |
| Navigation link | .side-rail-nav a | Vertical text link |
| Controls group | .side-rail-controls | Theme toggle + accent picker |
Specs
The side rail is fixed to the right edge at right: calc(2.5rem - 4px), top: 2.5rem. It stacks vertically with gap: 1.5rem between the nav and controls groups.
Links use writing-mode: vertical-rl so text reads top-to-bottom along the right edge. Font is Montreuil at 0.75rem, letter-spacing: 0.02em, line-height: 1.4. Color is --text-3, the decorative tier. This is intentional: the side rail should recede unless you look for it.
The container starts with opacity: 0 and pointer-events: none, fading in via the .visible class when JavaScript determines the viewport is wide enough and the page has scrollable content. The transition is opacity 0.4s ease.
States
Container:
| State | opacity | pointer-events |
|---|---|---|
| Default | 0 | none |
.visible | 1 | auto |
Links:
| State | Property | Value |
|---|---|---|
| Default | color | var(--text-3) |
| Hover | color | var(--text-2) |
Links explicitly reset background-image and background-size to prevent the prose link underline from appearing on these navigation items.
Responsive behavior
| Breakpoint | Change |
|---|---|
<= 1079px | display: none |
>= 1080px | Visible (when .visible class applied) |
display: none !important |
The 1080px threshold ensures the side rail only appears when there is enough horizontal space that it won’t overlap the content column.
Accessibility
Side rail links are standard <a> elements, fully keyboard-navigable. The vertical text orientation is purely visual; screen readers see normal link text. The pointer-events: none on the hidden state prevents accidental focus on invisible elements.
Code example
<div class="side-rail">
<nav class="side-rail-nav">
<div class="side-rail-group">
<a href="/writing">Writing</a>
<a href="/shortlist">Shortlist</a>
<a href="/links">Links</a>
<a href="/books">Books</a>
<a href="/now">Now</a>
</div>
</nav>
<div class="side-rail-controls">
<!-- Theme toggle and accent picker components -->
</div>
</div>
Footer
Purpose
The footer is a colophon. Copyright, settings, navigation. Modeled on the imprint page of a well-made book: the essential credits and tools, composed with quiet typographic care. No background color, no multi-column layout density. A single rule separates it from the content above.
Anatomy
| Part | Selector | Description |
|---|---|---|
| Container | .site-footer | Rule-topped section |
| Nav groups | .footer-nav-groups | Flex row of link groups |
| Nav group | .footer-nav-group | Column with label + links |
| Group label | .footer-nav-label | Uppercase sans label |
| Group links | .footer-nav-links | Flex row of links with dot separators |
| Dot separator | .footer-nav-links .dot | Accent-colored middle dot |
| RSS link | .footer-rss | Icon link to RSS feed |
| Colophon line | .footer-colophon | Copyright + settings row |
| Settings group | .footer-settings | Theme toggle, accent picker, font size |
Specs
The footer container uses margin-top: var(--space-xl), padding: var(--space-lg) 0 0, and border-top: 1px solid var(--ui). The single rule is the only structural border on the page outside of code blocks and tables.
Nav groups use Montreuil at 0.9rem with gap: var(--space-xl) between groups. Group labels are 0.7rem, weight 600, uppercase, letter-spacing: 0.05em, --text-2 color. Links within each group are separated by accent-colored dot characters at 40% opacity.
Colophon sits below the nav in Montreuil at 0.75rem, --text-3 color, with margin-top: var(--space-md). It contains the copyright notice and the settings group (theme toggle, accent picker, font size control) separated by gap: 12px.
States
Footer links:
| State | Property | Value |
|---|---|---|
| Default | color | var(--text-2) |
| Hover | color | var(--text) |
Transition: color 0.2s ease, background-size 0.3s cubic-bezier(0.22, 1, 0.36, 1).
RSS link:
| State | Property | Value |
|---|---|---|
| Default | color | var(--text-3) |
| Hover | color | var(--text) |
Responsive behavior
| Breakpoint | Change |
|---|---|
<= 600px | Footer nav centers with justify-content: center |
<= 600px | Footer link padding increases to var(--space-sm) var(--space-sm) for touch targets |
<= 600px | Footer settings center with justify-content: center |
<= 600px | Dot separators hidden |
<= 600px | Nav links stack vertically (flex column) |
| Nav groups and settings hidden |
Accessibility
Footer links meet minimum touch target sizing at the <= 600px breakpoint via increased padding. The settings controls (theme toggle, accent picker) have their own accessibility notes in Controls.
Code example
<footer class="site-footer">
<div class="footer-nav-groups">
<div class="footer-nav-group">
<span class="footer-nav-label">Site</span>
<div class="footer-nav-links">
<a href="/">Home</a>
<span class="dot">·</span>
<a href="/writing">Writing</a>
<span class="dot">·</span>
<a href="/now">Now</a>
</div>
</div>
<div class="footer-nav-group">
<span class="footer-nav-label">Subscribe</span>
<div class="footer-nav-links">
<a href="/feed.xml" class="footer-rss">RSS</a>
</div>
</div>
</div>
<p class="footer-colophon">
<span>© 2026 Alex Priest</span>
<span class="footer-settings">
<!-- Theme toggle, accent picker, font size -->
</span>
</p>
</footer>
Scroll progress bar
Purpose
A 2px accent-colored bar at the top of the viewport that fills left-to-right as the reader scrolls through an article. Reading progress made visible using the accent color, which signals liveness. The accent-colored line at the top of your viewport is filling as you scroll this page.
Anatomy
| Part | Selector | Description |
|---|---|---|
| Progress bar | .scroll-progress | Fixed bar at viewport top |
Specs
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: var(--accent);
transform-origin: left;
transform: scaleX(0);
z-index: 100;
The bar uses scaleX rather than width for smooth sub-pixel rendering. transform-origin: left ensures the bar grows from left to right.
Where supported, it uses the CSS animation-timeline: scroll() API for zero-JavaScript progress tracking:
@supports (animation-timeline: scroll()) {
.scroll-progress {
animation: scroll-fill linear both;
animation-timeline: scroll();
}
}
@keyframes scroll-fill {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
Browsers without animation-timeline support fall back to a JavaScript scroll listener that updates transform: scaleX() directly.
Responsive behavior
| Breakpoint | Change |
|---|---|
display: none !important |
The bar remains visible at all screen sizes. Its 2px height is thin enough to be unobtrusive on mobile.
Accessibility
The progress bar is purely decorative. It carries no ARIA role and is invisible to screen readers. The pointer-events are inherited (not explicitly set to none), but the 2px height makes accidental interaction impossible.
Color contrast is not relevant here because the bar communicates through position, not text. It works for users who cannot perceive the accent color because the growing width itself conveys progress.
Code example
<div class="scroll-progress"></div>