Presets
A preset is a complete visual identity: colour for light and dark mode, a font pairing, a corner radius rule and the range of the signature width-axis effect. Components never hard-code any of these, so switching preset restyles every page at once.
Switch preset
Section titled “Switch preset”theme: { preset: 'forest', switcher: false,},That is the whole change. With switcher: false only the active preset’s CSS ships, so unused presets cost nothing.
The four presets
Section titled “The four presets”The samples below are drawn from src/config/presets.ts when these docs are built, and the contrast ratios are calculated from the same values. They cannot drift from the code.
signal
Paper, ink and a signal orange. Square corners, expanded grotesk.
- Display
- Archivo
- Body
- Archivo
- Radius
- 0 / 0
- Width
- 100% to 125%
light
Work that ships.
Body copy in Archivo, muted notes below.
| ink | #111113 | 16.2:1 | AAA |
|---|---|---|---|
| ink-muted | #54555A | 6.4:1 | AA |
| signal-text | #B83510 | 5.1:1 | AA |
| signal-ink | #111113 | 4.9:1 on signal | AA |
| danger | #B02020 | 5.9:1 | AA |
dark
Work that ships.
Body copy in Archivo, muted notes below.
| ink | #ECECE8 | 16.3:1 | AAA |
|---|---|---|---|
| ink-muted | #9E9FA4 | 7.3:1 | AAA |
| signal-text | #FF5E33 | 6.3:1 | AA |
| signal-ink | #0E0E10 | 6.3:1 on signal | AA |
| danger | #FF786E | 7.5:1 | AAA |
cobalt
Cool white and cobalt blue. Pill buttons, square images.
- Display
- Instrument Sans
- Body
- Instrument Sans
- Radius
- 999px / 0
- Width
- 84% to 100%
light
Work that ships.
Body copy in Instrument Sans, muted notes below.
| ink | #0B1024 | 17.1:1 | AAA |
|---|---|---|---|
| ink-muted | #4A5068 | 7.2:1 | AAA |
| signal-text | #2447F5 | 5.8:1 | AA |
| signal-ink | #F3F4F6 | 5.8:1 on signal | AA |
| danger | #B4212E | 6.0:1 | AA |
dark
Work that ships.
Body copy in Instrument Sans, muted notes below.
| ink | #ECEEF5 | 16.9:1 | AAA |
|---|---|---|---|
| ink-muted | #9AA0B6 | 7.5:1 | AAA |
| signal-text | #708AFF | 6.3:1 | AA |
| signal-ink | #090B16 | 6.3:1 on signal | AA |
| danger | #FF8080 | 8.1:1 | AAA |
forest
Deep green and bone with an amber accent. Soft 12px corners.
- Display
- Bricolage Grotesque
- Body
- Instrument Sans
- Radius
- 12px / 12px
- Width
- 84% to 100%
light
Work that ships.
Body copy in Instrument Sans, muted notes below.
| ink | #14241C | 13.6:1 | AAA |
|---|---|---|---|
| ink-muted | #4A5A50 | 6.1:1 | AA |
| signal-text | #8A5708 | 5.1:1 | AA |
| signal-ink | #14241C | 7.4:1 on signal | AAA |
| danger | #A8261E | 6.0:1 | AA |
dark
Work that ships.
Body copy in Instrument Sans, muted notes below.
| ink | #E6EAE2 | 15.3:1 | AAA |
|---|---|---|---|
| ink-muted | #9CA89F | 7.6:1 | AAA |
| signal-text | #ECB048 | 9.7:1 | AAA |
| signal-ink | #0C1410 | 9.7:1 on signal | AAA |
| danger | #FF8A7A | 8.2:1 | AAA |
mono
Strict black and white, monospaced body. No accent at all.
- Display
- Archivo
- Body
- JetBrains Mono
- Radius
- 0 / 0
- Width
- 100% to 125%
light
Work that ships.
Body copy in JetBrains Mono, muted notes below.
| ink | #0C0C0D | 17.4:1 | AAA |
|---|---|---|---|
| ink-muted | #555558 | 6.6:1 | AA |
| signal-text | #0C0C0D | 17.4:1 | AAA |
| signal-ink | #F2F2F0 | 17.4:1 on signal | AAA |
| danger | #B02020 | 6.1:1 | AA |
dark
Work that ships.
Body copy in JetBrains Mono, muted notes below.
| ink | #EDEDEB | 16.7:1 | AAA |
|---|---|---|---|
| ink-muted | #A0A0A3 | 7.5:1 | AAA |
| signal-text | #EDEDEB | 16.7:1 | AAA |
| signal-ink | #0C0C0D | 16.7:1 on signal | AAA |
| danger | #FF786E | 7.6:1 | AAA |
Colour roles
Section titled “Colour roles”Every preset defines the same ten roles for light and for dark. Components only ever use the roles, never raw colours.
| Role | Tailwind | Use |
|---|---|---|
paper |
bg-paper |
Page background |
paperRaised |
bg-paper-raised |
Alternate sections, the footer |
paperSunk |
bg-paper-sunk |
Image placeholders, skeletons |
ink |
text-ink |
Text and primary buttons |
inkMuted |
text-ink-muted |
Secondary text. Must pass 4.5:1 on paper |
inkFaint |
text-ink-faint |
Decoration only, never text |
signal |
bg-signal |
The accent as a fill |
signalInk |
text-signal-ink |
Text on a signal fill. Must pass 4.5:1 on signal |
signalText |
text-accent |
The accent as text, icons, rules and focus rings |
danger |
text-danger |
Form errors |
signal and signalText are separate because a bright fill is often too light to read as text. Forest’s amber is the clearest case: #E3A33B as a button, #8A5708 as a link.
Make your own
Section titled “Make your own”-
Open
src/config/presets.ts, copy the preset closest to what you want and give the copy a new key, for examplestudio. -
Change the colours for
lightanddark. Keep every text role at 4.5:1 or better against the surface it sits on; the comments in the file show the ratios of the built-in presets. WebAIM’s contrast checker is a quick way to test a pair. -
Set the fonts.
displayshould be a variable font with a width (wdth) axis if you want the stretch effect. Add the font as described in Typography. -
Set
radius.ui(buttons, chips) andradius.media(images, cards).'0'gives hairline-divided tile grids, anything else gives separated rounded tiles. -
Set
stretch: the width at rest, on hover, and the two extremes the footer wordmark animates between. Keep them inside your display font’s width range. -
Select it in
site.tswithpreset: 'studio'. The TypeScript type updates automatically, so a typo in the name is an error.
How it works
Section titled “How it works”presetCss() turns a preset into CSS custom properties scoped to html[data-preset='name']. Dark values apply under data-theme='dark', and under prefers-color-scheme: dark for visitors without JavaScript. Tailwind reads the properties through tailwind.config.mjs, which is why bg-ink/10 and every other alpha modifier works in every preset.
The demo switcher stores a visitor’s choice in localStorage under swift-preset. It is applied before first paint, so there is no flash of the wrong preset.