Skip to content
scsiwyg
sign insign up
get startedhow it worksmcpscsiblogcommunityapiplaygroundswaggersign insign up
Making scsiwyg·The Theme System: Why a Headless Platform Ships 11 Visual Presets16 Apr 2026David Olsson

The Theme System: Why a Headless Platform Ships 11 Visual Presets

#scsiwyg#devlog#themes#design#building-in-public#pro

David OlssonDavid Olsson

The Theme System: Why a Headless Platform Ships 11 Visual Presets

scsiwyg is headless. No editor. No dashboard. You write posts from your IDE and publish via API. So why does it ship 11 visual theme presets with custom color overrides, per-theme typography, and layout variants?

Because headless describes the input, not the output. Readers don't care how a post was written. They care how it looks when they read it.

The tension

The early version of scsiwyg had one look: dark background, monospace headers, minimal spacing. It worked for the developer audience we started with. It didn't work for anyone else.

The options were:

  1. Full CSS customization — let users upload stylesheets. Maximum flexibility, maximum support burden. Every theme becomes a bespoke debugging session.
  2. No theming — one design, take it or leave it. Simple, but limiting. Blogs start looking like a platform, not like individual voices.
  3. Presets with overrides — curated themes that handle the hard design decisions (typography scale, spacing ratios, color harmony), with a small surface area for customization.

We went with option 3.

How it works

A theme in scsiwyg is two things: a color palette (15 variables) and a layout definition (typography, spacing, widths, list style).

The resolution function takes a preset name and optional overrides, then produces a complete set of CSS custom properties prefixed --sc-. Every color, font size, spacing value, and layout parameter flows through these variables.

The 11 presets

7 free presets cover the basics:

PresetCharacter
lightWhite background, clean defaults
darkDark grey, light text
slateCool grey tones
oceanBlue-tinted dark
forestGreen-tinted dark
sandWarm beige
monoPure black and white

4 Pro presets push further:

PresetCharacter
brutalistCourier body, Helvetica headers, 56px bold headings, 3px top borders
notebookCharter serif, cream background, rounded cards, 580px narrow width
midnightDeep indigo, Inter headers, card-based layout with 8px radius
galleryTwo-column grid, 48px headings, airy museum spacing

Each Pro preset doesn't just change colors — it changes the entire layout personality. gallery switches the post list to a two-column grid. brutalist uses 9px letter-spacing on metadata. notebook narrows the content width to 580px and wraps posts in cream-colored cards.

What each theme actually controls

Beyond the obvious color variables, each preset defines:

  • Typography: font family, heading family, body size, heading size, heading weight, line heights
  • Layout: max content width, vertical/horizontal spacing, header gap, article gap
  • Post list style: grid columns (1 or 2), gap, item padding, item background, border radius, border style

This is the part that matters. Getting colors right is easy. Getting the relationship between font size, line height, content width, and spacing right is hard. That's what the presets encode — tested ratios that work together.

Custom overrides

Users can override 6 color keys: bg, text, link, muted, border, code. The system auto-derives secondary colors from these:

  • Override bgbg2 and bg3 are computed from luminance
  • Override texttext2 is derived
  • Override muteddim and subtle follow

This keeps the override surface small while preventing broken color combinations. You can't accidentally set bg and bg2 to the same value because you only set bg — the system handles the rest.

All overrides are validated as hex colors before they reach CSS. No arbitrary strings in style attributes.

Setting a theme

From MCP:

update_site(theme: "midnight", themeOverrides: { bg: "#0B0E1A", link: "#7B93DB" })

From the account page: a theme picker shows each preset with its colors. Pro presets are visible but locked for free users — you can see what you'd get.

The theme is stored as JSON on the site record: { preset: "midnight", overrides: { bg: "#0B0E1A" } }. No separate theme table, no theme files, no build step. Change the theme and it takes effect on the next page load.

What we learned

The gallery preset taught us something. When we added list-columns: '1fr 1fr', the post list suddenly needed cards — a flat list in two columns looks broken. So the gallery preset also sets list-item-bg, list-item-radius, and list-item-pad to create visual containers. One layout decision cascaded into four more. That's why presets exist instead of independent toggles — the decisions are coupled.

The other lesson: narrower isn't always better. notebook at 580px reads beautifully for prose. It's terrible for posts with code blocks or Mermaid diagrams. We considered per-post width overrides but decided against it. The theme sets the personality of the blog. If your content needs wide layouts, pick a different theme.

What's next

We're considering letting themes control the header and footer style — right now those inherit from the shell, not the theme. And we're watching whether custom font uploads are worth the complexity. For now, the system fonts (Inter, Charter, system serif/sans) cover enough ground.

Eleven presets is probably enough presets. The value isn't in having more options — it's in each option being a complete, considered design.

Share
𝕏 Post