Design system
One file holds the site's tokens, fonts, element styles, button looks and page transition. The block workspace, the inspector, agents and the CSS all read from it.
On this page
The design system lives in content/design/tokens.json, next to your content, so it is versioned with the site and readable by agents. A site without one uses Lettern's defaults until you save.
{
"name": "Acme",
"tokens": {
"color": {
"$type": "color",
"background": { "$value": "#ffffff", "$extensions": { "cms": { "dark": "#0c0c0b" } } },
"foreground": { "$value": "#161614", "$extensions": { "cms": { "dark": "#f2f2ee" } } },
"primary": { "$value": "#1d4ed8" }
},
"font": { "sans": { "$value": ["Inter", "ui-sans-serif", "system-ui", "sans-serif"] } },
"text": { "base": { "$value": { "min": "1rem", "max": "1.0625rem" } }, "5xl": { "$value": { "min": "2.75rem", "max": "5rem" } } },
"space": { "4": { "$value": "1rem" }, "section": { "$value": { "min": "4rem", "max": "8rem" } } },
"radius": { "md": { "$value": "0.625rem" } }
},
"fonts": [
{ "family": "Inter", "src": "fonts/InterVariable.woff2", "weight": "100 900", "style": "normal", "preload": true }
],
"styles": {
"body": { "fontFamily": "token:font.sans", "fontSize": "token:text.base", "color": "token:color.foreground", "backgroundColor": "token:color.background" },
"h1": { "fontSize": "token:text.5xl", "lineHeight": "token:leading.tight", "textWrap": "balance" },
"h1 em": { "fontFamily": "token:font.display", "fontStyle": "italic" }
},
"prose": { "h2": { "fontSize": "token:text.2xl" } },
"buttons": {
"primary": { "base": { "backgroundColor": "token:color.primary", "color": "token:color.primary-foreground" }, "hover": { "opacity": 0.9 } }
},
"transition": { "type": "fade", "duration": 0.25 }
}#Tokens
Tokens use the Design Tokens format: groups of tokens with a $value and an optional $type.
| Group | CSS property prefix | Used for |
|---|---|---|
color | --color- | Text, fills, borders, shadows |
font | --font- | Font stacks |
text | --text- | Font sizes |
leading, tracking, weight | --leading-, --tracking-, --font-weight- | Line height, letter spacing, font weight |
space | --space- | Padding, margins, gaps, offsets |
radius, shadow | --radius-, --shadow- | Corners and shadows |
container | --container- | Content widths |
breakpoint | — | Screen sizes for responsive styles |
duration, ease | --duration-, --ease- | Transitions |
- Fluid values —
{ "min": "1rem", "max": "1.25rem" }— scale smoothly between a 360px and a 1280px screen. - Dark mode — a token's
$extensions.cms.darkvalue applies when the visitor prefers dark, or when the page is switched with the Light / dark mode action. - Refer to a token anywhere a style takes a value:
token:color.primary, with opacitytoken:color.primary/20, or inside a longer value:0 8px 24px token:color.foreground/15.
Design → Design system in the admin edits all of it: tokens (Color, Type, Space, Shape) with a live preview in light and dark, Fonts, Elements (element, prose and button styles), Motion (page transitions, duration and easing tokens) and Code. A change restyles every page and rebuilds the site.
#Element styles and prose
styles sets defaults for HTML elements — html, body, h1–h6, p, a, a:hover, small, strong, em, mark, code, kbd, pre, blockquote, hr, ul, ol, li, img, figcaption, table, th, td, button, input, textarea, select, label, ::selection, :focus-visible, and h1 em, h2 em, h3 em, h1 strong, h2 strong for mixed-style headings. Keys are CSS properties in camelCase, like everywhere else in Lettern.
prose styles the same elements inside long-form text: a Page content block with the class cms-prose (the starter kit's article template uses it). Lettern ships sensible spacing for prose; override what you want.
Element styles sit in a low CSS layer, so a block's own styles always win.
#Buttons
buttons defines the looks of the Button block. Each key is a variant editors pick; each variant has base, hover, active and focus-visible styles. The core blocks offer primary, secondary, ghost and link; define more and they appear in the picker.
#Fonts
Fonts are self-hosted: files live in content/design/fonts/ and are served from /_cms/fonts/, with preload for the ones the first screen needs. Add them on the Fonts tab, without touching files:
- Add a Google font — type a family name (
Inter Tight). Lettern downloads the latin files (variable weights when the family has them) and adds them tofonts; visitors never contact Google. Agents usedesign/fonts-import. - Upload a font — a
.woff2file, with its family name. Agents usedesign/fonts-upload(the file as base64, a family, a weight such as400or100 900, and a style).
Then point a font token at the family.
#Page transitions
transition animates between pages with the browser's View Transitions: fade, slide, scale or none, with a duration in seconds. It works on the static site with no JavaScript, and is skipped for visitors who prefer reduced motion.
#Custom CSS and JavaScript
content/design/custom.cssis appended after everything else — use it for the rare thing the inspector can't express.content/design/custom.jsloads as an ES module on every page. Use it to register Alpine components for behavior the builder can't express: listen forcms:alpineondocument(itsdetailis Alpine), then give an element that component under Interact → Code component (logic.component).
document.addEventListener('cms:alpine', (e) => {
e.detail.data('pricing', () => ({
yearly: false,
get price() { return this.yearly ? 190 : 19 },
}))
})Both are edited on the Code tab, or with the design/code ability; both need the Edit custom CSS and JavaScript (site.code) permission — JavaScript runs on every page, so give it only to people you trust.
#For agents
design/get returns the tokens, fonts, element styles, buttons, transition, breakpoints, custom code and the templates entries can use; the cms://site/design context resource holds the same design system. design/update changes tokens ({"tokens": {"color.primary": "#0f766e"}}, dark, remove) and replaces whole sections (styles, prose, buttons, fonts, transition).