Reference

Style guide

Generated from src/config/theme.ts — every value below is the real token, not a copy.

Default theme: lightUse the toggle in the header to compare light and dark.

Colour

Edit these in the light and dark objects insrc/config/theme.ts. Each one generates two CSS variables — the hex (--accent) and an RGB triplet (--accent-rgb) forrgba() use — plus Tailwind utilities like bg-accent andtext-muted/60.

Brand

Primary drives links, buttons and active states. Alt is a second accent — badges, charts, highlights. Never body text.

accent

light #57634e
dark  #a3b096

accentHover

light #3b4536
dark  #bcc7b1

accentAlt

light #9a7449
dark  #c99f6d

accentAltHover

light #7a5b37
dark  #dab588

Surfaces

Page and card backgrounds. `surfaceAlt` is for insets — form fields, code blocks, footers.

surface

light #fbfaf7
dark  #161613

surfaceAlt

light #f2efe9
dark  #1e1e1a

Text

Three weights of emphasis, plus one special case. Headings lead with `ink`, paragraphs sit on `body`, metadata recedes to `muted`. `onImage` is for copy over a photograph, where the background is no longer a known flat colour — pushed harder than `body` in both themes for exactly that reason.

ink

light #1b1a17
dark  #f2f0ea

body

light #3d3b35
dark  #d7d4cb

muted

light #6f6b61
dark  #98948a

onImage

light #26241f
dark  #fbfaf7

Structure & status

Hairlines, and the two feedback colours used by the contact form.

line

light #e2ded4
dark  #2e2d28

success

light #3f6b46
dark  #8fc39a

danger

light #9c3b30
dark  #e39288

Rounding

Named for what they wrap, not for their size — so restyling the whole site's feel means editing four values. Available as rounded-button, rounded-input,rounded-card, rounded-panel and rounded-pill.

0.125rem

rounded-button

0.125rem

rounded-input

0.125rem

rounded-card

0.125rem

rounded-panel

9999px

rounded-pill

Typography

Configured in src/config/fonts.ts. Astro self-hosts and subsets each family, so there are no third-party font requests at runtime. Only the families registered there appear below — the template ships with one, and the heading andbrand roles fall back to it until you add your own.

body · Atkinson Hyperlegible · preloaded

Aa Bb Cc 123

--typeface-body

h1

Heading 1

h2

Heading 2

h3

Heading 3

h4

Heading 4

bodyBody copy at the base size.
overlineSection label
brandExample

Choosing a typeface

A shortlist of Google Fonts that suit this template, kept in src/config/fonts.ts. All of them are open-licensed — free to use commercially and free to self-host, which is what Astro does with them at build time. Nothing is loaded from Google at runtime, so no visitor IP address is ever sent there.

Every sample below is set in the font it names — really loaded, not an image. That costs one file per family, so it happens on this page only: the shortlist is handed to the layout through fontVariables, and Astro emits the@font-face rules where they're rendered. No other page in the site requests any of them. Each is fetched at a single weight, which is enough to judge the letterforms and not enough to typeset with.

pairings

One face, two jobs

Handgloves & quartz

The quick brown fox jumps over the lazy dog — 0123456789, Il1 O0.

Atkinson Hyperlegiblefor headings ·Atkinson Hyperlegiblefor body

What's installed. Hierarchy comes from size, spacing and case instead of from a second typeface — the hardest version to get wrong, and the least likely to look dated in three years.

export const fonts: FontDefinition[] = [
	{
		role: 'body',
		cssVariable: '--typeface-body',
		provider: 'google',
		family: 'Atkinson Hyperlegible',
		weights: [400, 700],
		fallbacks: ['system-ui', 'sans-serif'],
		preload: true,
	},
];

Editorial

Handgloves & quartz

The quick brown fox jumps over the lazy dog — 0123456789, Il1 O0.

Instrument Seriffor headings ·Interfor body

A high-contrast display serif over a neutral sans. This is the combination behind most of the minimal hospitality and studio sites people point at as a reference.

export const fonts: FontDefinition[] = [
	{
		role: 'body',
		cssVariable: '--typeface-body',
		provider: 'google',
		family: 'Inter',
		weights: [400, 500, 600, 700],
		fallbacks: ['system-ui', 'sans-serif'],
		preload: true,
	},
	{
		role: 'heading',
		cssVariable: '--typeface-heading',
		provider: 'google',
		family: 'Instrument Serif',
		weights: [400],
		fallbacks: ['Georgia', 'serif'],
		preload: false,
	},
];

Warm

Handgloves & quartz

The quick brown fox jumps over the lazy dog — 0123456789, Il1 O0.

Frauncesfor headings ·Work Sansfor body

Same structure, softer voice. Fraunces has enough character to carry a heading on its own, so the sans underneath can stay completely plain.

export const fonts: FontDefinition[] = [
	{
		role: 'body',
		cssVariable: '--typeface-body',
		provider: 'google',
		family: 'Work Sans',
		weights: [400, 500, 700],
		fallbacks: ['system-ui', 'sans-serif'],
		preload: true,
	},
	{
		role: 'heading',
		cssVariable: '--typeface-heading',
		provider: 'google',
		family: 'Fraunces',
		weights: [400, 600],
		fallbacks: ['Georgia', 'serif'],
		preload: false,
	},
];

Bookish

Handgloves & quartz

The quick brown fox jumps over the lazy dog — 0123456789, Il1 O0.

Newsreaderfor headings ·Newsreaderfor body

Serif throughout, at two sizes. Suits a site that is mostly writing — and it means one family to load rather than two.

export const fonts: FontDefinition[] = [
	{
		role: 'body',
		cssVariable: '--typeface-body',
		provider: 'google',
		family: 'Newsreader',
		weights: [400, 500, 600],
		fallbacks: ['Georgia', 'serif'],
		preload: true,
	},
];

the shortlist

Atkinson Hyperlegible

Handgloves 0123456789 Il1 O0

sans · body, heading, brand · 400/700

What this template ships with. Designed by the Braille Institute to keep letterforms distinguishable at small sizes — b/d, I/l/1 and O/0 are all unmistakable. Only two weights.

Inter

Handgloves 0123456789 Il1 O0

sans · body, heading · 400/500/600/700

The default neutral UI sans. Enormously capable and completely unopinionated, which is both why it works everywhere and why a site set in it looks like every other site.

Public Sans

Handgloves 0123456789 Il1 O0

sans · body, heading · 400/600/700

Neutral, slightly squarer than Inter, drawn for the US government design system. A good body face when the headings are doing the talking.

Work Sans

Handgloves 0123456789 Il1 O0

sans · body, heading · 400/500/700

A grotesque with slightly warmer, less mechanical curves. Sits well under a serif heading without going cold.

Newsreader

Handgloves 0123456789 Il1 O0

serif · body, heading · 400/500/600

A text serif built for reading on screen. One of the few serifs as comfortable in body copy at 18px as it is in a heading.

EB Garamond

Handgloves 0123456789 Il1 O0

serif · body, heading · 400/500/600

A classical book serif. Runs small for its nominal size, so set it a step larger than you would a sans.

Lora

Handgloves 0123456789 Il1 O0

serif · body, heading · 400/600

A sturdy workhorse serif with a touch of calligraphic contrast. Holds up in long-form text better than most Google serifs.

Fraunces

Handgloves 0123456789 Il1 O0

display · heading, brand · 400/600

A variable soft-serif with real personality — wonky, warm, and adjustable for optical size. Excellent large, busy small.

Instrument Serif

Handgloves 0123456789 Il1 O0

display · heading, brand · 400

High-contrast, tightly spaced, one weight only. The current shorthand for expensive-looking minimalism — which also means it will date as that look does.

Cormorant Garamond

Handgloves 0123456789 Il1 O0

display · heading, brand · 400/500

Very fine, very high contrast, very elegant. Falls apart below about 24px — hairlines that thin disappear on a low-density screen.

Playfair Display

Handgloves 0123456789 Il1 O0

display · heading, brand · 400/600

The classic luxury-brand serif. Effective and instantly legible as a signal, and so widely used that it reads as a category rather than a choice.

Syne

Handgloves 0123456789 Il1 O0

display · brand · 400/700

Geometric and deliberately strange, from the art world. A wordmark face — never set a paragraph in it.

JetBrains Mono

Handgloves 0123456789 Il1 O0

mono · body · 400/700

For code blocks. Tall x-height and clearly distinct l/1/I. Only needed if you replace the CSS monospace stack with a real font.

suits is the part worth paying attention to. A high-contrast display serif set as body copy is the most common way a site with good taste in fonts ends up hard to read — and each weight you list is another file, so take only the ones you'll use.

Buttons

<Button> renders an <a> when givenhref and a <button> otherwise, so a link that looks like a button is still a real link.

variant

size

icon & pill

External link

Cards

Static card

A plain div. No hover on the title, because it doesn't go anywhere.

Link card

Given href, renders as an anchor and highlights its title on hover.

New

With a badge

For a category, a status, or a short callout.

With texture

Pass texture to add the dot grid. Off by default.

No icon

The icon is optional.

Custom content

The default slot replaces description when you need more than a line of text.

Article cards

<ArticleCard> — a picture, a title and a description, in three layouts. None of them draws a box: the image supplies the edge, and the space around the text does the rest. All three collapse to text on their own when a post has noheroImage, and none of them stacks on a phone — narrow the window to see it.

Motion

Two reveal mechanisms. Load-in is pure CSS and fires on paint — use it above the fold. Scroll uses an IntersectionObserver — use it below. Both are switched off entirely under prefers-reduced-motion.

loadIn

distance 14px

duration 0.8s

easing  cubic-bezier(0.16, 1, 0.3, 1)

scroll

distance 10px

duration 0.55s

easing  cubic-bezier(0.16, 1, 0.3, 1)

stagger  80ms

staggered scroll reveal (index 0-3)

index=0
index=1
index=2
index=3

Layout

Content measures, available on <Section width="..."> and as Tailwindmax-w-* utilities.

page · 58rem
prose · 40rem
wide · 80rem
headerHeight · 5rem

Prose

The .prose-copy class, used for long-form content and rendered Markdown.

A heading inside prose

A paragraph with a link, some bold text, and a bit ofinline code.

A blockquote, marked with a left rule in the accent colour.
  • An unordered list item
  • Another one
  1. An ordered list item
  2. Another one
const tokens = themeCss();