Theming
Tune the look with config tokens, override any CSS variable in theme.css, or use Tailwind utilities.
Blume’s theme is token-driven and works in light and dark mode out of the box. Reach for as little or as much as you need: a few config tokens for the common cases, a theme.css to override any design token, or Tailwind utilities for custom components.
Config tokens
The everyday knobs live under theme in your config:
theme: {
accent: "teal", // a named preset or any CSS color
radius: "md", // none | sm | md | lg
mode: "system", // system | light | dark
fonts: { // self-hosted Google Fonts
display: "inter-tight",
body: "inter",
mono: "ibm-plex-mono",
},
}
Accent
The accent color tints interactive and highlighted elements — step markers, active tabs, badges, card hovers, and more. Use a named preset or any CSS color:
theme: {
accent: "#ff0066", // hex, oklch(), rgb()… anything CSS understands
}
Named presets: blue (default), green, orange, pink, purple, red, and teal.
A string applies to both color modes; pass an object for a different accent per mode.
Radius
radius sets the corner rounding shared by cards, code blocks, callouts, and inputs — none, sm, md (default), or lg.
Color mode
mode sets the initial color scheme:
system(default) — follow the reader’s OS preferencelight/dark— default to one scheme
A toggle in the header always lets readers switch, and their choice is remembered across visits. Dark mode is applied with a data-theme="dark" attribute on the <html> element.
Fonts
fonts sets the typefaces for three roles:
display— headings (h1–h6)body— body text, UI, and prosemono— code blocks and inline code
Each defaults to a curated Google Font, so Blume looks intentional out of the box:
theme: {
fonts: {
display: "inter-tight", // default
body: "inter", // default
mono: "ibm-plex-mono", // default
},
}
Set only the roles you want to change — the rest keep their defaults:
theme: {
fonts: { display: "geist" }, // body + mono stay Inter / IBM Plex Mono
}
Fonts are self-hosted: Blume downloads them at build time and serves them from your own site, so there’s no runtime request to Google and no layout shift (Astro generates fallback-metric faces automatically).
Each value is a Google Fonts slug from the curated set below:
| Category | Slugs |
|---|---|
| Sans | dm-sans figtree geist ibm-plex-sans inter inter-tight manrope open-sans plus-jakarta-sans roboto source-sans-3 space-grotesk work-sans |
| Serif | ibm-plex-serif lora merriweather playfair-display source-serif-4 |
| Mono | fira-code geist-mono ibm-plex-mono jetbrains-mono roboto-mono source-code-pro space-mono |
Need a font that isn’t listed, or want to drop back to the system stack? Override the --blume-font-* tokens directly in theme.css.
Dark-mode colors
accent and background follow one rule: a string applies to both color modes, and a { light, dark } object sets each mode individually:
theme: {
accent: { light: "blue", dark: "teal" },
background: {
light: "#ffffff",
dark: "#0a0a0a",
},
}
Each color takes a named preset or any CSS color. For background (and backgroundImage) either key can be omitted to override a single mode — background: { dark: "#0a0a0a" } keeps the default light background.
Action color
action is a secondary accent for primary calls to action and the action Tailwind utilities (bg-action, text-action). It defaults to your accent:
theme: {
action: "#ff0066",
}
Background image
Set a background image behind your content with backgroundImage — a URL or a path under public/. Like the colors, a string applies to both modes and a { light, dark } object sets each mode’s image:
theme: {
backgroundImage: {
light: "/bg-light.svg",
dark: "/bg-dark.svg",
},
}
theme.css
Drop a theme.css in your project root to override any design token. It’s the last layer in the cascade, so it wins over the defaults and config tokens:
:root {
--blume-accent: oklch(0.68 0.14 180);
--blume-radius: 0.5rem;
}
:root[data-theme="dark"] {
--blume-background: oklch(0.16 0 0);
}
Set a token under :root for light mode and under :root[data-theme="dark"] for dark mode. Color tokens have distinct built-in dark values declared at the dark selector’s higher specificity, so a :root-only override of --blume-accent, --blume-background, and friends applies to light mode only — declare the dark block too when both modes should change.
Design tokens
| Token | Controls |
|---|---|
--blume-background |
Page background |
--blume-foreground |
Body text |
--blume-muted |
Subtle surfaces — callouts, table headers |
--blume-muted-foreground |
Secondary text |
--blume-border |
Borders and dividers |
--blume-accent |
Accent color |
--blume-accent-foreground |
Text and icons on an accent background |
--blume-action |
Secondary accent (defaults to accent) |
--blume-code-background |
Code block surface |
--blume-radius |
Corner radius |
--blume-font-display |
Heading font |
--blume-font-body |
Body / UI font |
--blume-font-mono |
Code font |
Set a --blume-font-* token to any font stack to use a font outside the curated list, or to fall back to the system stack:
:root {
--blume-font-body: ui-sans-serif, system-ui, sans-serif;
}
Tailwind utilities
Blume’s theme is built with Tailwind v4 internally, and your project’s .astro, .tsx, and .jsx files are scanned too — so you can style custom components and pages with utility classes, no Tailwind setup required. Every token is exposed as a utility, so your components track the theme automatically:
| Token | Utilities |
|---|---|
--blume-background |
bg-background |
--blume-foreground |
text-foreground |
--blume-muted |
bg-muted |
--blume-muted-foreground |
text-muted-foreground |
--blume-border |
border-border |
--blume-accent |
bg-accent, text-accent |
--blume-accent-foreground |
text-accent-foreground |
--blume-action |
bg-action, text-action |
--blume-radius |
rounded-blume |
--blume-font-display |
font-display |
--blume-font-body |
font-sans |
--blume-font-mono |
font-mono |
Cascade order
Styles resolve in three layers, each overriding the last:
Base
Config tokens
--blume-accent, --blume-radius, and the --blume-font-* tokens from
theme.
theme.css