Style Guide

Every design token, typographic specimen, component, and interaction pattern for The Umami Post.

Brand

The publication name is always written in full, The Umami Post, capitalised, never abbreviated to "TFT" in reader-facing copy. The icon is a serif "F" on a dark square.

Logotype

The Umami Post icon The Umami Post

Tagline

A community food publication. Tested recipes, technique guides, food journalism, and reviews -- written by cooks, for cooks. Free and open source.

Mascot

The pansy flower, a historical symbol of Food (from the French pensée, meaning "thought"), is the publication's mascot.

Colors

A warm newsprint palette in light mode; deep charcoal in dark mode. Every value is a CSS custom property in tokens.css, change once, retheme everywhere.

Surfaces, light mode

Background --color-bg · #F4F1EB Primary page background
Background alt --color-bg-alt · #EAE6DE Cards, panels, footer
Background inset --color-bg-inset · #E0DBCF Code blocks, sunken areas

Text

Ink --color-ink · #1A1A1A Primary text. 15.6:1 on bg
Ink muted --color-ink-muted · #4A4845 Secondary text, datelines
Ink faint --color-ink-faint · #5E5856 Captions, metadata. 5.1:1 on card bg

Accents & UI

Accent (Vermillion) --color-accent · #C0392B Breaking news, labels. 5.2:1
Link --color-link · #2C5F8A Body links. 6.8:1 on bg
Rule --color-rule · #C8C2B6 Borders, horizontal rules
Rule heavy --color-rule-heavy · #8A847A Strong dividers
Selection --color-selection · #6A1B9A Text selection highlight

Section colors

News#C0392B
Opinion#2C5F8A
Analysis#5D4037
Arts & Culture#6A1B9A
Science#1B5E20
History#75540A
Letters#37474F
Reviews#8B6914
Thought Experiments#1D5C6E
Trials#6B2A2A
Glossary#3D5A47
Bookshelf#7A5232

Dark mode surfaces

Background#141414
Background alt#1E1E1E
Background inset#252525
Ink#E8E4DC
Ink muted#A8A49C
Ink faint#9E9A96 · 5.5:1 on card bg
Accent (text)#E05545, text use only (4.9:1 on page bg). Do not use as a background with light text.
Link#6B9EC7
Selection#B87ACC

Typography

Four typefaces do all the work. All served from Bunny Fonts, privacy-friendly, no tracking, with system-font fallbacks.

Families

--font-masthead · Playfair Display 900
The Umami Post

Hero titles, masthead, page titles, decorative numerals.

--font-headline · Lora 700
Reason Against Orthodoxy

Article headlines, section headers, card titles.

--font-body · Source Serif 4
The examined life is the only one worth living. The Umami Post covers ideas, reason, and the long history of human beings trying to think their way past the official story, whatever the official story happens to be at the time.

Article and page body copy. 16px base, 1.7 line-height.

--font-ui · DM Sans
Navigation · Buttons · Labels · Metadata · Bylines

All UI chrome: nav, buttons, form labels, section badges, reading time.

Dates

Every human-readable date sitewide renders in Month DD, YYYY format — for example, "April 18, 2026". Server-side this is the readableDate and shortDate Eleventy filters (Luxon LLLL d, yyyy); client-side date renderers use toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }). Abbreviated months ("Apr 18") and ISO ordering ("2026-04-18") are reserved for machine-readable contexts only — the htmlDateString filter and the datetime attribute on <time> elements.

Type scale

Major Third ratio (1.25×). Base size is 16px (1rem). Tokens are defined in tokens.css, never use raw pixel values in templates.

--text-xs0.75rem / ~12px
--text-sm0.875rem / ~14px
--text-base1rem / 16px
--text-md1.25rem / ~20px
--text-lg1.563rem / ~25px
--text-xl1.953rem / ~31px
--text-2xl2.441rem / ~39px
--text-3xl3.052rem / ~49px
--text-4xl3.815rem / ~61px

Spacing

A consistent rhythm from 4px to 6rem. Always use spacing tokens, never hardcode values in templates. The scale lives in tokens.css.

--space-10.25rem / 4px
--space-20.5rem / 8px
--space-30.75rem / 12px
--space-41rem / 16px
--space-61.5rem / 24px
--space-82rem / 32px
--space-102.5rem / 40px
--space-123rem / 48px
--space-164rem / 64px
--space-205rem / 80px
--space-246rem / 96px

Buttons

Square corners (border-radius: 0). All buttons meet the 44×44px minimum touch target. Hover states have explicit color set to override parent link rules.

Variants

<a class="btn">Default</a>
<a class="btn btn--accent">Accent</a>

On dark backgrounds

Support badges

Shields.io-style two-part flat badges used for reader support CTAs. Height is fixed at 20px with vertically centered text. Tippy.js tooltips provide context on hover.

Variants

tip-badge--pub value uses background: #1E3A5F; color: #F4F1EB, deep navy, hardcoded. 10.2:1 contrast. Visually distinct from both light card bg and dark page bg (#141414). Do not change to use accent tokens.

<a class="tip-badge" href="...">
  <span class="tip-badge__label">support</span>
  <span class="tip-badge__value">Author Name</span>
</a>

With Tippy tooltip

data-tippy-content="..."
data-tippy-theme="badge"
data-tippy-placement="top"

Section badges

Small pill-style section labels used in article cards and eyebrows. Background color comes from the section-specific CSS custom property.

<a class="section-badge section-badge--news" href="/news/">News</a>

Article cards

The single canonical card component used across all section feeds, homepage grids, related-posts, author pages, tag pages, and editions. One partial, partials/article-card.njk, four size variants, consistent metadata everywhere.

Metadata, always shown on every card

FieldSourceNotes
Section badgearticle.data.sectionLinks to section index
Datearticle.dateLinks to /archives/#date
Headlinearticle.data.titleSize controlled by size variable
Descriptionarticle.data.descriptionSuppressed on xs
Authorauthors[slug].nameLinks to /author/slug/; Tippy bio tooltip
Reading timetemplateContent | readingTimee.g. "6 min read"
Word counttemplateContent | wordCounte.g. "1,847 words"
Imagearticle.data.imageOnly when showImage=true

Large, size="lg"

Homepage lead story, section lead. Used with showImage=true.









priority = true sets fetchpriority="high" and loading="eager" on the image. Use only for the single above-fold hero card to improve LCP.

Medium, size="md"

Standard feed card. Default size.







Small, size="sm"

Sidebar, related posts, article lists.







Extra-small, size="xs"

Compact lists. Description suppressed.







Horizontal, with thumbnail

Used where a compact image + text side-by-side layout is needed. Add class article-card--horizontal.

<article class="article-card article-card--horizontal">
  <img class="article-card__image" src="..." ...>
  <div>...eyebrow, headline, byline...</div>
</article>

Usage guide

SizeUse whenshowImageDescription shown
lgHomepage lead, section lead (1 per page)YesYes
mdSection feeds, newsletter gridsNoYes
smSidebar, related posts, author page listNoYes
xsCompact lists, "more from" railsNoNo
horizontalThumbnail + text rowsYes (fixed)No

Author cards

Used on the Contributors page. Centered column layout: avatar on top, body below. The avatar uses a stacked pattern, initials always rendered, photo absolutely positioned on top with onerror fallback.

Founding Editor

Jon Ajinga

Covering regulation, institutional power, and the life of ideas.

15 articles 7.8k words ~47m reading

Avatar stacking pattern

<div class="author-card__avatar">
  <div class="author-card__initials" aria-hidden="true">JA</div>
  <img src="photo.jpg" class="author-card__photo"
       onerror="this.style.opacity=0">
</div>

Initials always render. Photo is position: absolute on top. onerror hides the photo if it fails to load, revealing initials underneath.

Forms

All inputs are minimum 44px tall, set in the UI typeface, and show a distinct focus ring. Forms are handled by embedded third-party providers (Web3Forms, Fillout, Zite), never custom backends.

We'll never share this.

Tables

Data tables wrap in .table-wrap for horizontal scroll on narrow viewports. Use scope="col" on header cells and scope="row" on row headers.

Criterion The Umami Postindependent Ad-funded outletcommercial
Revenue modelReader-fundedAdvertisers
Editorial controlFullPartial
PaywallNoneOften
<div class="table-wrap">
  <table class="compare-table">…</table>
</div>

Icons

Icons are inline SVG, 24×24 viewBox, stroke-width: 2, fill: none, currentColor, they inherit text color automatically in light and dark modes.

menu
close
chevron
arrow-up
sun
moon
search
rss
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
     stroke-width="2" aria-hidden="true">…</svg>

Accessibility patterns

WCAG 2.2 Level AA minimum across all pages. Most patterns exceed AA and reach AAA.

Skip link

Press Tab on any page. The first focusable element is a "Skip to content" link that jumps past navigation to #main.

Focus ring

Every interactive element shows a 2px vermillion outline with 3px offset on keyboard focus.

Tab to focus me

Screen-reader-only content

Use .sr-only to hide content visually while keeping it accessible to assistive technology.

<span class="sr-only">Opens in new tab</span>

Reduced motion

All animations and scroll behavior honor prefers-reduced-motion: reduce. Duration drops to near-zero; auto-advance stops.

ARIA landmarks & labels

  • role="banner" on site header, role="contentinfo" on footer
  • aria-label on every icon-only button and every navigation region
  • aria-current="page" on the active nav link
  • aria-hidden="true" on all decorative SVGs and avatar initials
  • <main id="main"> as the skip-link target

Repeated link text

When multiple links share the same visible text but go to different destinations, e.g. "View All →" buttons in section strips, add aria-label to give each a distinct accessible name:

<a href="//" class="btn"
   aria-label="View all  articles">
  View All →
</a>

This satisfies Lighthouse's "Links do not have descriptive text" audit and WCAG 2.4.6. The visible text stays short; the accessible name is descriptive.

Touch targets

Every interactive element is a minimum of 44×44 CSS pixels, buttons, nav links, form inputs, theme toggle, hamburger, back-to-top, tip badges.

Glossary tooltips

Glossary terms detected in article text receive a dotted underline. Powered by Tippy.js. Keyboard accessible.

Contrast reference

All pairings target WCAG AA: 4.5:1 for normal text, 3:1 for large text and UI. Light mode has two relevant backgrounds, page (#F4F1EB) and card (#E0DBCF). The card bg is the binding constraint and is used below. Dark mode verified independently.

PairingRatioAA normalAA largeAAA
Ink on page bg (#F4F1EB)15.6 : 1PassPassPass
Ink muted on page bg8.2 : 1PassPassPass
Ink faint on card bg (#E0DBCF)5.1 : 1PassPass,
Ink faint on page bg6.3 : 1PassPassPass
Accent on page bg5.2 : 1PassPass,
Link on page bg6.8 : 1PassPassPass
White on Accent (#C0392B)5.4 : 1PassPass,
Dark mode: Ink faint on card (#252525)5.5 : 1PassPass,
Dark mode: Ink muted on page (#141414)7.4 : 1PassPassPass

Note: dark mode accent (#E05545) is for text use only, do not use it as a background with light text (3.8:1 fail). The publication tip-badge uses background: #1E3A5F; color: #F4F1EB, deep navy at 10.2:1, visually distinct from both light card bg and dark page bg (#141414).

Naming conventions

Consistent naming avoids confusion across templates, CSS, and JavaScript. These rules apply to every file in the project.

Publication name

  • Always written as The Umami Post, full, capitalised
  • Never abbreviated to "TFT" in reader-facing copy
  • Short context only: "The Times" is acceptable in conversational UI

CSS class convention

BEM-style: .block__element--modifier. No utility classes except where explicitly defined in utilities.css.

.article-card__byline          /* element */
.article-card__headline--lg    /* element + modifier */
.section-badge--news           /* block + modifier */

JavaScript

  • One file per feature, deferred, no bundler
  • LocalStorage key prefix: tft- (e.g. tft-theme, tft-reading-list)
  • No inline event listeners on elements with custom display CSS, tinyHTML can mangle them. Use onclick="" as a fallback alongside deferred listeners.

Design tokens

All tokens live in src/assets/css/tokens.css. Never use raw hex values or pixel sizes in templates or component CSS, always reference a token. Dark mode overrides are declared in the same file under [data-theme="dark"].

Content naming

  • The bookshelf project: Recommended Food Reading (compact: Recommended Reading)
  • The music feature: Food Music (never "Music Player")
  • Layouts directory: src/_includes/layouts/
  • Partials directory: src/_includes/partials/