UI component library

The small, reusable building blocks — page headers, buttons, dividers, empty states, breadcrumbs — that sit between design tokens and full page layouts.

Design & Branding Updated April 23, 2026 v1.0.0

Between the design tokens (colors, type, spacing) and the page layouts (base, article, page) sits a layer of small reusable components. These are the building blocks most pages stitch together. Each is defined once in src/assets/css/components.css and composed by class name anywhere in templates.

Page header

Every content page that isn't an article uses the same header pattern: optional eyebrow label, title, subtitle, and a rule. Rather than redeclare these styles per page, the .page-header component handles them uniformly.

<header class="page-header">
  {% if eyebrow %}<p class="page-header__eyebrow">{{ eyebrow }}</p>{% endif %}
  <h1 class="page-header__title">{{ title }}</h1>
  {% if subtitle %}<p class="page-header__subtitle">{{ subtitle }}</p>{% endif %}
</header>

The component scales the title with fluid clamp() type, renders the eyebrow in the UI font with letter-spacing, and puts a --color-rule-heavy bottom border on the whole block. On article pages, the layout emits a richer header with kicker, byline, and dateline; .page-header is for everything else.

Modifier classes:

  • .page-header--centered — centers text; typically for marketing / landing pages
  • .page-header--narrow — constrains to ~36rem, suits form pages

Button variants

Two primary button variants + two secondary / utility variants. All four are .btn plus a modifier; they share sizing, padding, corners, and focus ring, and differ only in color treatment.

.btn--primary   { background: var(--color-ink); color: var(--color-paper); }
.btn--secondary { background: transparent;      color: var(--color-ink);    border: 1px solid var(--color-ink); }
.btn--ghost     { background: transparent;      color: var(--color-ink);    }
.btn--danger    { background: var(--vermillion);color: #fff;                }

Hover states darken or invert for primary/secondary; ghost gets a subtle paper-tinted background. Every variant respects prefers-reduced-motion — the transform-on-hover is dropped for readers who set that preference.

Icon + label spacing

When a button contains both an icon SVG and a label, use the .btn__icon child class. The icon is sized to 1em and gets margin-right: 0.5em in LTR locales. Without it, SVGs either overwhelm the label or stick to its edge.

Dividers

Three divider utilities for visual separation between sections:

ClassUse
.divider-thin1 px --color-rule — inside a card or between tightly related items
.divider1 px --color-rule-heavy — between sections on the same page
.divider-accent3 px --vermillion — masthead-style rule above a major section

All three render as <hr> with the class. Avoid using them inside body text — for prose, let heading rhythm and whitespace do the work.

Empty-state pattern

Many pages need to render "there's nothing here yet" gracefully: an author with no articles, a topic with no tags, a search with no results, a reading list the reader hasn't added to. The .empty-state component standardises this:

<div class="empty-state">
  <p class="empty-state__icon" aria-hidden="true">📰</p>
  <h2 class="empty-state__title">No articles yet</h2>
  <p class="empty-state__body">This author's first piece will appear here.</p>
  <a class="btn btn--secondary" href="/">Browse the archive</a>
</div>

The pattern centers in the page with --color-ink-muted body text and dimmed icon. It has a min-height so short empty states don't look cramped, and it scales the icon down on narrow viewports.

Breadcrumbs

Breadcrumbs render as a single line above the page header on deep pages (section › subsection › article, or docs category › article). The active / current item uses the [aria-current="page"] attribute selector:

.breadcrumb a            { color: var(--color-ink-muted); }
.breadcrumb a:hover      { color: var(--color-ink); }
.breadcrumb [aria-current="page"] {
  color: var(--color-ink);
  font-weight: 500;
}

The active state no longer relies on a .breadcrumb--active class — aria-current is both semantically correct and eliminates the need to track a separate visual-state class.

Where to define new components

New site-wide components go in src/assets/css/components.css, grouped by role. Page-scoped styles belong in src/assets/css/pages/<page>.css. Avoid adding utility classes (margin helpers, text helpers) in components.css — those live in utilities.css.

Related

  • Design tokens — the raw color and spacing variables these components consume.
  • Layout reference — the page-level scaffolds that embed these components.
  • Dark mode — how components adapt to the theme toggle.
Still need help?

Browse Support for community channels and paid support options, or book a call if you'd like me to set it up for you.