How Styling Works

There is no CSS editor in the CMS. Every rule that reaches a visitor comes from a file in your site repository, compiled by Tailwind during leed site build. What the CMS contributes is a much smaller thing: names and image URLs. A theme name, a font name, a layout name, a logo. Those names travel into the build as class names on <html>, and the CSS that gives them meaning is yours.

That seam is the single most useful thing to understand about styling a Leed site, because almost every “why doesn’t this change anything?” question turns out to be a name on one side with no rule on the other.

The two surfaces

Every appearance decision on a Leed site lands on exactly one of two surfaces. The CMS surface is a form; the repository surface is CSS. Nothing crosses over.

Appearance concernCMS UISite repositoryNotes
Site title and description✅ Settings → General—Feeds metadata, feeds and social cards
Site logo✅ upload❌ src/static/images/logo-* is read-only
Favicon✅ upload❌ src/static/images/favicon-* is read-only
Documentation logo (light / dark)✅ upload—Injected as --nav-logo-url
Documentation layout✅ dropdown: alpha, bravo, charlie❌ fixed setThe name becomes a Handlebars partial path
Documentation color theme✅ 17 built-ins, or a custom name⚠️ a custom name requires an @utility under tailwind/
Documentation code theme✅ 14 built-ins, or a custom name⚠️ same
Documentation font✅ 5 built-ins, or a custom name⚠️ a custom name needs a --font-* token and an @font-face
Extra highlight.js languages✅ list of language names⚠️ needs src/static/js/hljs-<lang>.jsAdding one is an Enterprise gate; existing languages are grandfathered
Tab groups✅ Settings → General and Settings → Page Types—Supplies each tab’s title and icon
Documentation header CTA button✅ text + href—
All site CSS❌✅ tailwind/One entry point, tailwind/site.config.css
--site-* color tokens❌✅ your own :root
Component overrides❌✅ a layer(components) fileNo !important needed
Custom fonts❌✅ woff2 under src/static/webfonts/, @font-face under tailwind/
Dark-mode palette❌✅ @media (prefers-color-scheme: dark)There is no toggle
Mermaid diagram palette❌✅ src/static/js/mermaid.theme.json
Arbitrary <head> CSS or JS❌✅ src/_includes/header-includes.hbs
Documentation header / footer markup❌✅ src/_includes/docs-header.hbs, docs-footer.hbsThe file existing is what enables it; Starter and up
Settings → General with the Documentation section expanded, showing the Layout, Theme, Font and Code Theme dropdowns alongside the light and dark logo upload slots

Everything the CMS knows about your documentation set’s appearance is in that one panel, and there is no CSS anywhere in it. The names it stores are cataloged, with their defaults, in Themes, Fonts and Code Themes; the words theme, layout, page type and deployment are used as exact terms across this category and are defined once in Core Concepts.

How a theme name becomes a style

The CMS stores documentationConfiguration.colorTheme as a complete class name — color-theme-blue, not blue. At build time the leedConfiguredCSS shortcode concatenates four of those stored values in a fixed order: the font, then docs-layout- prefixed onto the layout name, then the color theme, then the code theme. shell.hbs drops that string onto the root element of every documentation page:

<html lang="en" class="scroll-smooth js-focus-visible leed-documentation font-open-sans docs-layout-alpha color-theme-blue code-theme-github">

Only layoutName is stored unprefixed, because the builder also turns it into a Handlebars partial path (leed/docs/alpha/base). That is why the layout list is fixed at three and a custom layout is not possible at any price — the name has to resolve to a template that exists.

The other three are only ever class names, and a class name means whatever CSS says it means. Leed ships the 17 color-theme-* utilities, the 14 code-theme-* utilities and the six font-* tokens. If you type a name that is not one of them, the class still reaches <html> — it is simply inert.

Where the CSS itself comes from

One file turns styling on: tailwind/site.config.css at the root of your repository. If it exists, the build compiles CSS; if it does not, the build logs Tailwind is not enabled, skipping compilation at debug level and the site ships no stylesheet at all.

That file is an ordinary Tailwind v4 entry point. Leed string-inserts four lines into it at compile time — three @source directives and an @import of its own stylesheet — so your CSS and Leed’s compile together into one file. The output is written once per build to /static/css/tailwind.css with a content-hash query string, and leed/head links it into every page. You never write a <link> tag, and there is no tailwind.config.js anywhere in the product.

The mechanics of that compile — what exactly gets injected, the PostCSS chain, the hash, the watch loop — are in Tailwind Build. Everything under tailwind/ is yours to edit; the paths that are not are listed in Editable and Read-Only Files.

flowchart TB
  subgraph CMS["CMS — stores names"]
    F["Settings → General<br/>Settings → Page Types"] --> DC["documentationConfiguration<br/>font · layoutName · colorTheme · codeTheme"]
    DC --> J1["src/src.11tydata.json<br/>(site-wide)"]
    DC --> J2["src/&lt;pageTypeSlug&gt;/&lt;name&gt;.11tydata.json<br/>(per page type)"]
    J1 --> SC["leedConfiguredCSS shortcode"]
    J2 --> SC
    SC --> CLS["class string on &lt;html&gt;"]
  end
  subgraph REPO["Site repository — holds the CSS"]
    E["tailwind/site.config.css"] --> INJ["Leed injects 3 @source lines<br/>+ @import leed.css"]
    INJ --> PC["Tailwind v4 → PostCSS → postcss-url"]
    PC --> OUT["/static/css/tailwind.css?v=&lt;hash&gt;"]
    OUT --> LNK["&lt;link&gt; in leed/head"]
  end
  CLS --> PAGE["Rendered page"]
  LNK --> PAGE
  PAGE --> NOTE["The class only means something<br/>because the right-hand side defined it"]

Which copy of the configuration actually ships

documentationConfiguration exists in two places, and they do not have the same durability. This costs people an afternoon, so it is worth knowing before you touch either.

The company record — Settings → General — is what the CMS editor reads. The page editor’s tab-group menu, for instance, comes from there. But the company record is never written into your site’s configuration file: CompanySiteBuilderData is a thirteen-field .pick() that does not include documentationConfiguration, and every writer of src/src.11tydata.json re-serializes that file wholesale from the picked shape. A documentationConfiguration block hand-written into src/src.11tydata.json is therefore silently deleted by the next settings save, batch publish or entitlement change.

The page type record is the durable home. On publish it round-trips into src/<pageTypeSlug>/<lastSegment>.11tydata.json, which is exactly where the site build reads it from.

The rule in one line: put the complete documentation configuration on the docs page type. The one value that genuinely wants to exist in both places is tabGroups, because it has two different consumers — the CMS editor reads the company copy to populate its toolbar, and the site build reads the page-type copy to render titles and icons.

What is not configurable anywhere

Four things readers look for and do not find:

  • No dark-mode toggle. Leed sites follow the operating system through prefers-color-scheme. There is no switch, no data-theme attribute and no stored preference.
  • No per-page CSS field. A page has no style column and no stylesheet slot.
  • No <style> block in a page body. Page bodies are Leed Markdown; raw HTML other than <br> is rejected.
  • No custom layout name. layoutName becomes a partial path, so the three shipped layouts are the whole set. (Some marketing copy pairs “custom themes” with “layouts” — custom themes are real, custom layouts are not.)

Editing the CSS: locally or from the CMS

The normal path is the developer one: clone the repository, edit under tailwind/, run leed site build --serve to see the change immediately, then commit and push. Push is a four-step contract — validate, commit, push, deploy — described in Validate, Commit and Push.

You can also edit the same files without leaving the browser, from the Layouts Workspace. That workspace renders an upgrade prompt below Growth, so it is the narrower path; the files themselves are unchanged either way, and a repository clone can always edit them.

If your own rules are compiling but losing to Leed’s, the fix is almost never !important — it is an import annotation. That mechanism is in Cascade Layers and Overriding Leed, and it is worth reading before you write your first override.

Two shipped sites are worth reading end to end rather than paraphrasing: Pithy (pithy-sh/marketing/raw-content) and leed.ai’s own raw-content. Both are real Leed documentation sites built on this same system, and both carry unusually explicit comments in tailwind/site.config.css explaining why each import is layered or unlayered. The files this category cites most often are tailwind/docs/color-theme-<brand>.css, tailwind/partials/alerts.css, src/static/js/mermaid.theme.json and src/docs/docs.11tydata.json.

ESC