For AI agents and crawlers: a structured index of this site is available at https://danny.is/llms.txt.
Foundations
Site styleguide
This page is the primary reference for the site's foundational design system. It includes the colour system, the design tokens for spacing, type, borders, radii and shadows, and the global utility classes.
Colour System
The colour system is rooted in a set of foundational colours, some of which are adaptive and appear differently in light and dark modes (via light-dark()), and some of
which are absolute and appear the same in both modes. These are used to create a set of semantic role-based tokens that are primarily used across the site. All colours are defined in OKLCH. The adaptive colours use lower lightness and higher chroma in light mode and have increased
lightness and lower chroma in dark mode.
light mode
dark mode
The table below shows the semantic colours currently in use. Components should use these (or
derivatives of them via CSS functions like color-mix()) wherever their use case fits their
semantic role. In situations where a foundational colour is more semantic (eg a "blue callout")
then use the foundational colour instead. I deliberately try to keep these semantic colours to a
minimum to avoid ending up with a large unwieldy palette of very specific colours that are only
used in one place.
| Token | Light | Dark | Variable | Usage |
|---|---|---|---|---|
| Accent & interaction | ||||
| Accent | | | --color-accent | The primary brand colour, used in many places including for links |
| Visited | | | --color-visited | Visited links, because purple is an affordance people are used to |
| Highlight | | | --color-highlight | Highlighted text and occasionally other forms of highlight |
| Focus ring | | | --color-focus-ring | The focus ring for interactive elements |
| Backgrounds & surfaces | ||||
| Primary background | | | --color-background | The primary background colour |
| Secondary background | | | --color-background-secondary | A secondary background colour for things like cards and panels |
| Code background | | | --color-background-code | Background for inline code and some other code-like elements |
| Text & borders | ||||
| Text | | | --color-text | Body text |
| Text secondary | | | --color-text-secondary | Muted or secondary text |
| Border | | | --color-border | Subtle borders and dividers |
Design tokens
As with colours, I've tried to keep the number of generic design tokens to a minimum, while still providing a wide enough set of options for most use cases.
Borders, radii and shadows
Five CSS custom properties are available for setting border widths and radii. Border widths are set in pixels so they don't scale with the base font size while radii are mostly set in rems so they do.
| Variable | Value | Preview | Notes |
|---|---|---|---|
--border-width-hairline | max(0.0625rem, 1px) | 1px at the default root size but never falls to a sub-pixel when scaling up with the root font size | |
--border-width-base | 2px | Standard border width for most elements | |
--border-width-thick | 4px | Usually only applied to one edge of an element | |
--border-width-heavy | 6px | Usually only applied to one edge of an element | |
--border-width-accent | 1rem | Used as a design element, so scales with the root font size |
| Variable | Value | Preview | Notes |
|---|---|---|---|
--radius-xs | 0.125rem | Very subtle radius for softening corners slightly | |
--radius-sm | 0.25rem | Standard radius for most rounded elements | |
--radius-md | 0.5rem | ||
--radius-lg | 0.75rem | ||
--radius-full | calc(infinity * 1px) | Infinite radius for fully rounding into a pill or circle regardless of size. |
I don't use shadows heavily but there are two custom properties available if I need to create depth and elevation.
| Variable | Preview |
|---|---|
--shadow-small | |
--shadow-medium |
Typography & Spacing Tokens
Typography and spacing design tokens provide consistent rhythm and visual hierarchy across the site.
Font Families
Typography on this site serves five distinct usage contexts, covered by four font stacks.
Short-form prose and UI elements use Figtree (--font-ui) which works well for
both use cases and is the default face. Long-form prose uses Literata (--font-prose), while "display" type uses Geist (--font-display). Code examples and other
monospace fonts use a version of Fira Code.
The quick brown fox jumps over the lazy dog
Mollit amet velit reprehenderit. Proident aliqua officia nisi officia sint sint elit commodo pariatur voluptate nisi duis occaecat mollit. Dolor sint id velit ipsum excepteur pariatur adipisicing. Irure laborum ea nulla esse aute aliquip dolor reprehenderit sit laborum consequat esse ad. Duis pariatur laborum ut veniam exercitation in ut. Ullamco irure Lorem sit consequat adipisicing do fugiat occaecat consectetur commodo pariatur amet ut amet.
The quick brown fox jumps over the lazy dog
Mollit amet velit reprehenderit. Proident aliqua officia nisi officia sint sint elit commodo pariatur voluptate nisi duis occaecat mollit. Dolor sint id velit ipsum excepteur pariatur adipisicing. Irure laborum ea nulla esse aute aliquip dolor reprehenderit sit laborum consequat esse ad. Duis pariatur laborum ut veniam exercitation in ut. Ullamco irure Lorem sit consequat adipisicing do fugiat occaecat consectetur commodo pariatur amet ut amet.
The quick brown fox jumps over the lazy dog
Mollit amet velit reprehenderit. Proident aliqua officia nisi officia sint sint elit commodo pariatur voluptate nisi duis occaecat mollit. Dolor sint id velit ipsum excepteur pariatur adipisicing. Irure laborum ea nulla esse aute aliquip dolor reprehenderit sit laborum consequat esse ad. Duis pariatur laborum ut veniam exercitation in ut. Ullamco irure Lorem sit consequat adipisicing do fugiat occaecat consectetur commodo pariatur amet ut amet.
The quick brown fox jumps over the lazy dog
Mollit amet velit reprehenderit. Proident aliqua officia nisi officia sint sint elit commodo pariatur voluptate nisi duis occaecat mollit. Dolor sint id velit ipsum excepteur pariatur adipisicing. Irure laborum ea nulla esse aute aliquip dolor reprehenderit sit laborum consequat esse ad. Duis pariatur laborum ut veniam exercitation in ut. Ullamco irure Lorem sit consequat adipisicing do fugiat occaecat consectetur commodo pariatur amet ut amet.
Typographic contexts
Most of the site renders with prose-style typography by default, set in Figtree. Individual areas can opt in or out of that baseline. These four contexts show up as tabs throughout the rest of the styleguide, so you can preview an element or component in each:
- Default (flow) — the global prose-style defaults plus the
.flowutility for vertical rhythm between block-level elements. - Long form Prose — wrapped in
<LongFormProseTypography>, which opts in to Literata and the bookish enhancements (oldstyle numerals, tighter optical sizing) used in articles. - Default (no flow) — the global prose-style defaults on their own, with no wrapper.
- UI Style — the
.ui-styleutility, which opts out of prose styling (no underlines, no heading borders, no list markers) for navigation, cards and other interface areas.
Font Sizes
The base type is generated with Utopia and uses
clamp() to appropriately scale with the viewport width. While the CSS variables below
are used in most places, I also use ems in certain contexts where I want text sized
relative to that of its parent context.
| Preview | Variable | Notes |
|---|---|---|
| Aa | --font-size-xs | Occasional use in captions, labels, fine print etc |
| Aa | --font-size-sm | Used for secondary text, metadata and anywhere else I want smaller copy |
| Aa | --font-size-base | Default for all body text and H4–6 |
| Aa | --font-size-md | Anywhere we want slightly larger copy. Default for H3 |
| Aa | --font-size-lg | Default for H2 |
| Aa | --font-size-xl | Default for H1 |
| Aa | --font-size-2xl | Used for large display copy and some page titles |
| Aa | --font-size-3xl | Used for large display copy |
Spacing Scale
The spacing scale is generated with Utopia using the same base values as the type scale. Using the custom properties below to set margins, padding and the like will ensure they scale in concert with the fluid type.
| Variable | Preview |
|---|---|
--space-3xs | |
--space-2xs | |
--space-xs | |
--space-s | |
--space-m | |
--space-l | |
--space-xl | |
--space-2xl | |
--space-3xl |
Font Weights
All four families are variable fonts and therefore not limited to the weights defined below (Geist: 100-900, Figtree: 300-900, Literata: 200-900, Fira Code: 300-700). These design tokens provide common stops to help keep the weight scale consistent across the site, but it's fine to use intermediate weights when needed.
| Literata | Geist | Figtree | Variable | Value |
|---|---|---|---|---|
| Aa | Aa | Aa | --font-weight-light | 300 |
| Aa | Aa | Aa | --font-weight-normal | 350 |
| Aa | Aa | Aa | --font-weight-regular | 400 |
| Aa | Aa | Aa | --font-weight-medium | 500 |
| Aa | Aa | Aa | --font-weight-semibold | 600 |
| Aa | Aa | Aa | --font-weight-bold | 700 |
| Aa | Aa | Aa | --font-weight-extrabold | 800 |
| Aa | Aa | Aa | --font-weight-heavy | 900 |
Leading & Tracking
Line height and letter-spacing can be controlled via --leading-* and --tracking-* CSS custom properties.
| Variable | Value | Preview |
|---|---|---|
--leading-none | 0.9 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos. |
--leading-tight | 1.1 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos. |
--leading-snug | 1.2 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos. |
--leading-normal | 1.55 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos. |
--leading-loose | 1.7 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos. |
| Variable | Value | Preview |
|---|---|---|
--tracking-tight | -0.02ch | Hello there |
--tracking-normal | 0 | Hello there |
--tracking-wide | 0.05ch | Hello there |
--tracking-wider | 0.1ch | Hello there |
Measure
A single token, --measure-standard (70ch), sets the standard measure
— the maximum line length for comfortable reading, around 70 characters. I use it to cap the
width of prose columns and most content areas.
Utility Classes
In addition to the foundational custom properties above, there are a few global utility classes available.
| Class | Purpose |
|---|---|
.flow |
Adds consistent vertical rhythm between block-level children — catch-all margins for
elements not covered globally, and tighter spacing between adjacent headings. Applied to
most prose containers like articles and notes. Margins are em-based, so they
scale with font size.
|
.ui-style | Opt-out of default styles meant for prose-like content. Will remove margins, padding etc. Think of this like a mini "reset". |
.dark-surface | Forces dark background with light text regardless of theme. Used for always-dark areas like the footer. |
.surface-white |
Sets a white surface context in light mode (or dark surface in dark mode) and redefines
--color-background-secondary for children to ensure proper contrast. Used for some card-like components.
|
.cq |
Establishes container query context (container-type: inline-size).
|
.all-caps | Uppercase text with wide letter-spacing. |
.center, .right, .left | Text alignment utilities. Useful for table cells and other contexts. |
.top, .middle, .bottom | Vertical alignment utilities. Useful for table cells and inline elements. |
.content-trim | Removes top margin from first child and bottom margin from last child. Use inside padded containers with slotted content. |
.img-cover |
Makes image fill container with object-fit: cover.
|
.sr-only |
Visually hidden but accessible to screen readers. Also available as
.hidden-microformat for hiding microformat metadata.
|
.external-arrow | Subtle arrow indicator for external/offsite links in UI contexts. |
.list-reset | Removes list styling for navigation/UI lists. |