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

PartSelectorDescription
Header container.site-headerFlex row, baseline-aligned
Nameplate (homepage).site-header .name:not(a)Non-linked display name
Nameplate (interior).site-header a.nameLinked 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

StatePropertyValue
Defaultcolorvar(--text)
Defaultfont-variation-settings'wght' 400
Hover (interior)colorvar(--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

BreakpointChange
<= 600pxHomepage 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">&rarr;</span>
    <span class="current">Writing</span>
  </nav>
</header>

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

PartSelectorDescription
Container.breadcrumbFlex row, baseline-aligned, gap: 0.75em
Name link.breadcrumb a.nameLink home (see nameplate above)
Separator.breadcrumb .separatorArrow character (), decorative
Current segment.breadcrumb .currentCurrent 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):

StatePropertyValue
Defaultcolorvar(--text-2)
Defaulttext-decorationnone
Hovercolorvar(--accent)
Hoverfont-variation-settings'wght' 500

Intermediate link:

StatePropertyValue
Defaultcolorvar(--text-2)
Hovercolorvar(--accent)
Hoverfont-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">&rarr;</span>
  <span class="current">Writing</span>
</nav>

Detail page:

<nav class="breadcrumb">
  <a href="/" class="name">Alex Priest</a>
  <span class="separator">&rarr;</span>
  <a href="/writing" class="current">Writing</a>
  <span class="separator">&rarr;</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

PartSelectorDescription
Container.side-railFixed-position column, right edge
Navigation group.side-rail-navVertical link stack
Section group.side-rail-groupSubgroup within nav (e.g., sections, current)
Navigation link.side-rail-nav aVertical text link
Controls group.side-rail-controlsTheme 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:

Stateopacitypointer-events
Default0none
.visible1auto

Links:

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

Links explicitly reset background-image and background-size to prevent the prose link underline from appearing on these navigation items.

Responsive behavior

BreakpointChange
<= 1079pxdisplay: none
>= 1080pxVisible (when .visible class applied)
Printdisplay: 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>

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

PartSelectorDescription
Container.site-footerRule-topped section
Nav groups.footer-nav-groupsFlex row of link groups
Nav group.footer-nav-groupColumn with label + links
Group label.footer-nav-labelUppercase sans label
Group links.footer-nav-linksFlex row of links with dot separators
Dot separator.footer-nav-links .dotAccent-colored middle dot
RSS link.footer-rssIcon link to RSS feed
Colophon line.footer-colophonCopyright + settings row
Settings group.footer-settingsTheme 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:

StatePropertyValue
Defaultcolorvar(--text-2)
Hovercolorvar(--text)

Transition: color 0.2s ease, background-size 0.3s cubic-bezier(0.22, 1, 0.36, 1).

RSS link:

StatePropertyValue
Defaultcolorvar(--text-3)
Hovercolorvar(--text)

Responsive behavior

BreakpointChange
<= 600pxFooter nav centers with justify-content: center
<= 600pxFooter link padding increases to var(--space-sm) var(--space-sm) for touch targets
<= 600pxFooter settings center with justify-content: center
<= 600pxDot separators hidden
<= 600pxNav links stack vertically (flex column)
PrintNav 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">&middot;</span>
        <a href="/writing">Writing</a>
        <span class="dot">&middot;</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>&copy; 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

PartSelectorDescription
Progress bar.scroll-progressFixed 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

BreakpointChange
Printdisplay: 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>

Specimens