Theme

The theme is the set of design choices every page draws from: colours, fonts, spacing, how round corners are, how wide content may grow, button styles, form fields and the colours of code samples. You change it in one place and the whole site follows.

The choices are stored as tokens. A token is a named value, such as the colour brand or the spacing step lg. A block never holds a colour or a gap as a raw number. Its fields offer the theme's tokens by name, so pages stay consistent and one edit restyles them all.

After this chapter you can change colours and fonts, adjust spacing and buttons, restyle form fields and pick a colour scheme for code. Admins can do all of it in the admin. Developers can do the same in theme.ts.

Change the theme in the admin

  1. Open Settings → Theme. Only admins see Settings.
  2. Edit a value in any card: Colours, Typography, Spacing, Radius, Layout, Buttons, Form fields or Code. The Preview card on the right redraws as you type.
  3. Press Save theme. Every page changes at once, and no deploy is needed. Colour, spacing and style fields in the page editor offer the new tokens straight away.

Until the first save, the site uses the theme in theme.ts, and the Save card says so. After a save, the saved theme is used instead, and changes to theme.ts have no effect until you press Reset to theme.ts. Reset deletes the saved theme. The History card at the bottom of the screen keeps each value that a save replaced, with a Restore button, so a change can be undone.

Colours

Every colour has a name and a value. The name is the token that blocks refer to. brand-dark shows as Brand Dark in every colour field in the page editor.

  1. In the Colours card, press Add colour.
  2. Type a name in lowercase letters, digits and hyphens, such as venue.
  3. Pick the colour with the swatch, or type any CSS colour in the value box, such as #0f766e or rgb(15 118 110).
  4. Press Save theme.

You cannot remove a colour that the theme's buttons, text styles or form fields still use. Its Remove button is greyed out and says what uses it. Removing a colour that page blocks use leaves those blocks without it, so change them first.

A new site names the roles that blocks default to: brand, brand-dark, surface, surface-alt, text, muted, accent and border. Keep those names and change their values. Add your own colours beside them, such as a dark band and the text that sits on it.

If you write your own CSS, each colour is also a variable, var(--color-brand), and a pair of classes, wn-bg-brand for a background and wn-text-brand for text.

Fonts and text

The Typography card has a Body font, a Heading font and, if you press Add a monospace font, a Monospace font that code samples use. For each you give the Family name, a Fallback stack that the browser uses until the font loads, and where it is Loaded from:

  • Installed on the visitor's device loads nothing. Use it for system fonts.
  • Google Fonts loads a family from Google. Tick the weights you use, such as 400 and 700.
  • Files on this site loads font files you provide. For each file give a URL (an https address, or a path on this site), a weight, a style and a format.

Under Text you set the Base size (other sizes in em scale from it), the Line height and the Heading weight.

Text styles are named treatments that a Text block or a menu can wear: default, lead, small, eyebrow, display, title and wordmark. Each has a Size, Weight, Line height, Letter spacing, Case and Font, and sets the colour and underline of links inside it. Change one by name, or add your own. The Default row sets how every link on the site looks, and a blank link cell in another row keeps the Default's.

Spacing, corners and widths

  • Spacing has seven steps, from none to 2xl. Every padding, margin and gap field offers them. A step can be any CSS length. Write the largest steps as clamp() values so that generous desktop spacing shrinks on a phone without editors setting anything per screen.
  • Radius has five steps of corner rounding: none, sm, md, lg and full.
  • Layout sets the three breakpoints, md, lg and xl. These are the screen widths where responsive fields change value and where columns stop stacking. It also sets the three container widths, narrow, default and wide, which are how wide a section's content can grow.

Buttons

The Buttons card has two tables. Styles are named combinations of colours: a background, a text colour, a border and a hover background and text colour. Sizes combine a horizontal and vertical padding, a font size and a radius. Every Button block and every hero call to action picks one style and one size by name.

Form fields

The Form fields card styles every text input, select, textarea, checkbox and radio on the public site: forms, sign-in, checkout and the quantity stepper. Anything left on Default keeps the standard look, which is a 1px border in the border colour, sm corners, a surface background, a height of 2.75rem and a brand focus ring.

The card has a Resting group (Background, Text, Placeholder, Border, Border width, Corners, Padding across, Padding down, Font size and Height) and a Focus and states group (the focus colours, the Focus ring, the Error colour for an invalid field and the Ticks colour for checkboxes and radios). Setting the focus ring to None removes the outline, which needs a focus border or a focus background so a focused field still looks different.

In theme.ts the same choices live under components.field. Colours are token names. radius, paddingX and paddingY are radius and spacing steps. borderWidth, fontSize and height are CSS values. Add a field entry inside components, next to button:

theme.ts
components: {
  // Keep the button styles you already have here.
  field: { border: 'ink', radius: 'full', paddingX: 'lg', focusBorder: 'brand', focusRing: 'none' },
},

Colours for code samples

The Code block (Pages and blocks) colours code with one of eight colour schemes:

SchemeLooks like
GitHub LightLight
GitHub DarkDark
One LightLight
One DarkDark
DraculaDark
NordDark
Solarized LightLight
Solarized DarkDark

The theme holds the site's default. To set it, open Settings → Theme and use the Code card. Choose the scheme, the corner radius and the font size, and the card shows a preview of a short snippet. Press Save theme. Every Code block that follows the site default changes at once, with no need to republish a page. If you leave the card alone, code is drawn in GitHub Dark, with md corners and a font size of 0.875rem.

A single block can override the default. Select the Code block and set its Colours field to one of the eight schemes instead of Site default. The font of the code is the theme's Monospace font, if you have added one.

In theme.ts the same default is components.code. Add a code entry inside components, next to button. Every key is optional. The radius is one of the radius steps and the font size is a CSS value:

theme.ts
components: {
  // Keep the button styles you already have here.
  code: { scheme: 'one-dark', radius: 'md', fontSize: '0.875rem' },
},

The scheme names in theme.ts are github-light, github-dark, one-light, one-dark, dracula, nord, solarized-light and solarized-dark.

Edit the theme in theme.ts

theme.ts is the theme a developer keeps with the site's code. A new site's file is a complete theme. The example below is a small one that starts from the plain starter theme and changes two colours and the fonts:

theme.ts
import { defineTheme, starterTheme } from '@jamiemcconnell/cms/theme';

export default defineTheme({
  ...starterTheme,
  colors: {
    ...starterTheme.colors,
    brand: '#0f766e',
    ink: '#14213d',
  },
  fonts: {
    body: {
      family: 'Figtree',
      fallback: 'system-ui, sans-serif',
      source: { google: { family: 'Figtree', weights: [400, 600] } },
    },
    heading: {
      family: 'Bricolage Grotesque',
      fallback: 'system-ui, sans-serif',
      source: { google: { family: 'Bricolage Grotesque', weights: [700] } },
    },
  },
});

defineTheme checks the theme when the site starts and names every problem it finds, such as a button style that uses a colour that does not exist. Remember that a theme saved in Settings → Theme takes the place of this file until it is reset.

The site's own CSS

The theme cannot say everything. Bordered cards, a pill behind a hero's eyebrow or a hairline under the header are the site's own CSS. Put it in the local block package's styles in blocks/index.ts. It is a function of the theme that returns CSS, and it is added after the theme's CSS on every page, so it can use every variable:

blocks/index.ts
import { defineBlockPackage } from '@jamiemcconnell/cms/blocks';

export default defineBlockPackage({
  name: 'site',
  blocks: [
    // Your own blocks stay here.
  ],
  styles: () => `
    .wn-card-grid .wn-card {
      background: var(--color-surface);
      border: 1px solid var(--color-border);
      border-radius: var(--radius-lg);
    }
  `,
});

The starter site's own look is a stylesheet of this kind over its theme. Keep your blocks, collections and icons in the file as they are, and change only the styles entry.