[DocsDesignSystem]
A reusable pattern for technical documentation pages: a flat layout shell, a numbered rail of contents, a hero banner, and a small set of refined primitives — field tables, dark code frames with syntax tokens, callouts, and numbered procedure lists. Built with plain HTML, one CSS file, and one JS file. Drop the assets into any project, follow the markup conventions, and your docs read as part of the same family without a build step.
// 01What This Is
The scut docs design system is an editorial pattern for engineering documentation — closer to a typeset manual than a generated reference. It is intentionally not a documentation generator. There is no toolchain: every page is a hand-authored HTML file that links to a shared stylesheet and a tiny JS file. The pattern's job is to make those pages feel like one product, not like a folder of disparate notes.
Design notes the system commits to:
- Warm paper, dark ink. Body type sits on a cream paper (
#FAF7F0) in a sharp serif (Charter / Iowan Old Style). Code blocks stay dark in both light and dark themes — they are the "inset voice" of the document, like footnotes in a different ink. - Mono for structure, serif for prose. Berkeley Mono / JetBrains Mono for code, badges, TOC numbers, and field names. Serif for everything you read end-to-end. The split is load-bearing — a glance at a paragraph and you already know whether to expect prose or structure.
- Punctuation, not decoration. Colour appears as accent (a coral underline, a sunshine selection highlight, a mint clean-state glyph) — never as a wash. Backgrounds are paper or code; everything else is type and rule.
- Numbered sections, numbered TOC. Every
<h2>carries a// NNanchor; the rail mirrors those numbers. Readers track location by number, not by counting heading levels.
The system is small on purpose. There is no grid framework, no utility classes, no JavaScript components — just a handful of named regions and a small kit of compositional primitives. The whole CSS is ~800 lines; the JS does one job (theme toggle with cross-tab sync).
// 02Page Anatomy
Every page is the same three-region shell: a sticky rail on the left, a hero header, and a stack of numbered sections. The footer is one line.
The shell is identical across every page in the system. Variation lives inside each section's body — different primitives, different prose — but the surrounding scaffold is constant so a reader who has read one page already knows how to navigate the next.
// 03Quick Start
-
Copy the two asset files into your project's docs folder:
# From this repo: cp scut/docs/scut-docs.css yourproject/docs/yourproject-docs.css cp scut/docs/scut-docs.js yourproject/docs/yourproject-docs.jsRename the files to
<project>-docs.css/<project>-docs.js. Thescut-prefix is namespacing the localStorage key the JS uses (see section 16); rename the constant inside the JS too. -
Create your first page using the skeleton below. Save it next to the assets.
-
Open the file directly in a browser — there is no build step. Iterate, save, refresh.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Your Page Title — yourproject</title> <link rel="stylesheet" href="yourproject-docs.css"> <script src="yourproject-docs.js"></script> </head> <body> <div class="layout"> <aside class="rail"> <!-- rail-title, rail-product, .toc, .theme-switcher --> </aside> <main> <header class="hero"> ... </header> <section id="intro"> <h2><span class="section-anchor">// 01</span>Title</h2> <p>Prose, primitives, more prose.</p> </section> <footer> <span class="blink">yourproject</span> <span>section · category</span> </footer> </main> </div> </body> </html>
<section> bodies, never in the surrounding scaffold.// 04Layout
The .layout wrapper is a two-column CSS grid: a 260px sticky rail and a single fluid <main> column. Max page width is 1600px, centered.
.layout { display: grid; grid-template-columns: 260px minmax(0, 1fr); max-width: 1600px; margin: 0 auto; min-height: 100vh; }
<main> handles its own inner padding. The grid is left intentionally simple — no breakpoints. Wide screens get more room around the prose; narrow screens scroll the rail with the page rather than collapsing it.
// 05Rail and TOC
The rail holds three blocks, in order: a section label (// project · area), a product name with a live dot, and a numbered table of contents. The theme switcher sits below the TOC.
<aside class="rail"> <p class="rail-title">// project · area</p> <p class="rail-product"> <span class="live-dot" aria-hidden="true"></span> Page Name </p> <nav class="toc"> <div class="toc-section"> <p class="toc-section-label">Overview</p> <a class="toc-link" href="#intro"> <span class="toc-num">01</span><span>Introduction</span> </a> </div> </nav> <div class="theme-switcher"> ... </div> </aside>
| Class | Purpose | Notes |
|---|---|---|
| .rail-title | Section/category label | Plain-text monospace; use a leading // for editorial voice |
| .rail-product | Page name | Includes .live-dot — a small mint pulse indicating "this page is current" |
| .toc-section | A group of TOC links | Use one section per logical group (Overview, Implementation, Notes, etc.) |
| .toc-section-label | Group heading | Small caps monospace |
| .toc-link | A single TOC entry | Contains a .toc-num (2-digit zero-padded) followed by the link text |
The number a .toc-num shows should match the // NN anchor on the corresponding <h2>. If you renumber sections, renumber both. There is no automated check — the convention is enforced by your eyes.
// 06Hero
The hero is the page's masthead: a row of badges, an outsized moniker title, a multi-sentence lede, and a row of stats. It sets the tone and answers "what is this page about?" in under five seconds.
<header class="hero"> <div class="hero-meta"> <span class="badge primary">v1 docs</span> <span class="badge tests">category</span> <span class="badge swift">language</span> </div> <h1> <span class="moniker-bracket">[</span> <span class="moniker">PageMoniker</span> <span class="moniker-bracket">]</span> </h1> <p class="lede">One- or two-sentence summary. Use real prose, not a tagline.</p> <div class="hero-stats"> <div class="stat"><span class="num">25</span><span class="label">things</span></div> <div class="stat"><span class="num">4</span><span class="label">concepts</span></div> </div> </header>
Badges
Badges are small all-caps tags. Variants tint the text, not the background:
| Class | Colour | Typical use |
|---|---|---|
| .badge.primary | coral | Version or maturity (v1 docs, v0 spec) |
| .badge.swift | tangerine | Implementation language or runtime |
| .badge.tests | success green | Category or stage tag |
| .badge.macos | sky blue | Platform marker |
Class names hint at original use but nothing stops you from using .badge.swift on a Go page — the colour is what matters. Three badges per hero is the typical maximum.
The moniker
The <h1> uses the [Name] bracket-moniker form. Pick a single PascalCase word that names the subject (the package, the command, the system). The brackets are styled separately so they fade into the background.
Lede
Full sentences, ideally one to three. The lede should let a reader who closes the tab immediately still take away the right idea of what the page is about. Avoid taglines or marketing language.
Hero stats
Four single-fact statistics with a big number and a small label. Stats should describe the page's subject in numeric terms — 2 formatters · 8 dispatch steps · 3 testing layers · 3 extensions handled. They are a hook, not a metrics dashboard; if a number isn't memorable or load-bearing, leave it out.
// 07Theme Switcher
The switcher is a single button at the bottom of the rail. It has two glyphs (sun and moon) that the CSS reveals based on the active theme. The JS in scut-docs.js wires the click handler and persists the choice in localStorage.
<div class="theme-switcher"> <span class="theme-switcher__label">Theme</span> <button class="theme-toggle" type="button" role="switch" aria-checked="false" aria-label="Toggle color theme"> <span class="toggle-glyph toggle-glyph--sun" aria-hidden="true">☀︎</span> <span class="toggle-glyph toggle-glyph--moon" aria-hidden="true">☾</span> </button> </div>
Three behaviours are stitched together:
- No-JS fallback. CSS reads
@media (prefers-color-scheme: dark)when no[data-theme]attribute is on<html>, so disabled-JS users still get a sensible theme. - Pre-paint resolution. The JS runs before first paint (deliberately not deferred) and sets
html[data-theme]fromlocalStorage→ OS preference → "light". This prevents a flash of unstyled theme. - Cross-tab sync. A
storageevent listener watches for the toggle being flipped in another tab and updates the current document live.
// 08Sections
Sections are the body of every page. Each one gets an id matching its TOC anchor, an <h2> with a // NN section anchor, and any number of primitives inside.
<section id="my-topic"> <h2><span class="section-anchor">// 04</span>My Topic</h2> <p>An opening paragraph explaining the topic.</p> <h3>A Subhead</h3> <p>More prose, primitives, etc.</p> </section>
Subheads use plain <h3> with no section anchor. Three levels of heading (h1 hero, h2 section, h3 subhead) cover every page in the system — going deeper is a sign the section is too big.
// 09Field Tables
table.fields is the workhorse primitive — used for parameter lists, struct fields, configuration options, command flags, comparison matrices, almost anything tabular. Three- or four-column layouts are typical.
<table class="fields"> <thead> <tr><th>Field</th><th>Type</th><th>Description</th></tr> </thead> <tbody> <tr> <td class="field">session_id</td> <td class="type">string</td> <td class="note">Stable UUID for the session</td> </tr> </tbody> </table>
| Cell class | Rendering | Use for |
|---|---|---|
| .field | monospace, full opacity | Identifiers — field names, flag names, file paths |
| .type | monospace, sky blue | Type names, enum values, kinds |
| .note | serif, body weight | Descriptions and prose |
Mix the cell classes freely — a "Field / Type / Description" table puts .field in column 1, .type in column 2, and .note in column 3, but a "Class / Purpose / Notes" table might use .field in column 1 and .note in both 2 and 3. Pick the class that fits the content, not the column position.
// 10Code Frames
Code samples sit inside a .code-frame wrapper with a titlebar that looks like a macOS window chrome — three "lights" (in palette coral / sunshine / mint), a filename with an extension highlight, and a right-aligned language tag.
<div class="code-frame"> <div class="code-titlebar"> <div class="lights"> <span class="l1"></span> <span class="l2"></span> <span class="l3"></span> </div> <span class="filename">example<span class="ext">.go</span></span> <span class="langtag">GO</span> </div> <pre>// code goes here, with token spans (see next section)</pre> </div>
<span class="ext"> wrapping the extension. The extension is tinted coral so the file type pops at a glance. Use this even for non-source files: install.sh, tree.txt.Common language tags: GO, JSON, HTML, CSS, SHELL, ASCII, TREE, INI, TEXT. Pick the one closest to the content; ASCII is the catch-all for diagrams and listings.
// 11Syntax Tokens
Syntax highlighting is hand-applied with token spans inside the <pre> block. There is no JS highlighter and no build step — every coloured run of text is a <span class="tok-..."> in the HTML source. This sounds onerous; in practice it gives precise control over emphasis and stays readable in plain HTML view.
| Class | Colour | Use for |
|---|---|---|
| .tok-keyword | violet | Language keywords (func, type, return, HTML tag delimiters) |
| .tok-type | sky blue | Type names, struct names, attribute names |
| .tok-string | mint green | String literals, attribute values |
| .tok-number | tangerine | Numeric literals, hex values, units |
| .tok-comment | slate (italic) | Comments — any flavour |
| .ini-section | tangerine (bold) | INI / TOML section headers |
| .ini-key | sky blue | INI / TOML keys |
| .ini-value | mint green | INI / TOML values |
| .term-prompt | mint (non-selectable) | Shell prompt characters ($, %) that should not be copied |
You can render any language with this small set — Go, JSON, HTML, CSS, shell, INI, Python, Rust. The token vocabulary is intentionally small. If you find yourself wanting a 9th colour, you almost certainly want to colour something else differently, not introduce a new token.
// 12Callouts
A callout is a left-edged tinted block for asides — a warning, a clarifying note, an invariant the reader must not miss. The label is a small monospace caps run inside <strong>; the body is regular prose.
<div class="callout"><strong>tip</strong>Default coral accent.</div> <div class="callout note"><strong>note</strong>Sky-blue accent.</div> <div class="callout warn"><strong>warn</strong>Sunshine accent.</div> <div class="callout success"><strong>ok</strong>Success-green accent.</div> <div class="callout danger"><strong>danger</strong>Error-red accent.</div>
note variant is the workhorse — clarifications, "here's what to remember", invariants worth highlighting. Most pages will use this and only this.warn variant marks footguns and gotchas — behaviours that surprise readers, things that can go wrong if you ignore the advice.success variant marks confirmed-good states — useful in checklist-style content ("this is the right shape") but rare overall.danger variant is for "do not do this" — destructive operations, security mistakes, never-rolls-back states. Use it rarely or it loses force.The label inside <strong> is conventionally a single short word (note, warn, tip, danger, ok, invariant, rule) — the visual treatment makes it read as a chip, so longer phrases look wrong. The body is normal prose and can run multiple sentences.
// 13Steps
<ol class="steps"> renders an ordered list as a numbered procedure — large coral numerals on the left, indented body content on the right. Use it for any sequence the reader is expected to follow in order: installation, dispatch flows, migration paths.
<ol class="steps"> <li>Read input from stdin.</li> <li>Decode JSON into the typed struct.</li> <li>Apply the operation; write JSON output.</li> </ol>
Items can be short single sentences or longer multi-paragraph blocks containing prose, code frames, and nested structures. CSS auto-numbers from 1 — there is no manual numbering inside the list.
// 15Design Tokens
The CSS defines nine accent palettes plus a slate palette, each with ten shades from 50 (lightest) to 950 (darkest). They are deliberately broad so a page can lean on a colour family — e.g. mint for "ok / clean" indicators — without having to redefine local variables.
Each palette also carries shades 50, 200, 400, 600, 800, and 950 — not shown above. Reach for them when a more precise tonal step is needed.
Semantic tokens (--bg, --fg, --primary, --rule, etc.) reference the palette tokens above and are remapped between light and dark themes. Reference semantic tokens in your own additions; only reach into the raw palette when nothing semantic fits.
/* Light theme */ --bg: #FAF7F0; /* warm cream paper */ --bg-sunk: #EFE9DA; /* card / sunken surface */ --bg-code: #1A1E23; /* always dark */ --fg: #2D3238; /* charcoal body type */ --fg-mute: #545B62; --rule: #DAD2BD; /* warm hairline */ --primary: var(--coral-600); --type: var(--sky-700); --keyword: var(--violet-700); --string: var(--mint-700); --number: var(--tangerine-700);
// 16Theming
Three theme states are supported, in this priority order:
- Explicit user choice — the theme switcher sets
html[data-theme="light"]orhtml[data-theme="dark"]and persists the choice inlocalStorage. - OS preference — when no
data-themeattribute is set and JS hasn't run, the CSS@media (prefers-color-scheme: dark)block applies the dark palette automatically. - Light default — falls through to the cream-paper light theme.
The JS is small (~50 lines) and runs synchronously in the document head. It:
- Resolves the initial theme from
localStorage→ OS preference → "light" before first paint. - Wires the
.theme-togglebutton to flip the attribute and persist the choice. - Listens to the
storageevent so a toggle in one tab updates every other tab live.
If you fork the JS, change the STORAGE_KEY constant to your project name so different projects on the same origin don't share theme state.
<script src> in <head> blocks parsing for a few milliseconds but eliminates the flash-of-wrong-theme that defer-loaded theme scripts produce. Keep the placement.// 17Voice and Style
The design system has a voice. Match it or the pages drift apart.
- Full sentences in prose. The lede, paragraphs, and callout bodies are written end-to-end. Fragments work in
.notecells and footers, not in body copy. - Lowercase identifier-style monospace. Filenames, flag names, command names appear in
<code>with their natural casing —scut claude config install,--scope=project. No marketing capitalisation. - Numbers in stat blocks should be memorable. Four facts that characterise the page, not four random metrics.
- "Reports whether" for booleans when describing functions or fields — borrowed from Effective Go's doc convention.
- Cross-link with
<a href="other-page.html#section-id">. Pages should reference each other liberally — readers travel between docs more than they scroll within one. - Editorial em-dashes. The serif body type carries em-dashes well — use them for parenthetical asides rather than parentheses where possible.
- No emoji. The system already uses colour as accent; emoji compete with it visually and don't match the editorial tone.
// 18Adopting in Your Project
Minimum-effort adoption
- Copy
scut-docs.cssandscut-docs.jsinto your project's docs folder. Rename to<project>-docs.css/.js. - In the JS, change
STORAGE_KEYfrom"scut-docs-theme"to"<project>-docs-theme". - Copy one existing page (e.g. kong-base-setup.html) as a template and overwrite the content.
- Update the rail title (
// project · area) and the footer project name on every new page.
That's it. You'll have working documentation in a few minutes with the full design system intact.
Light customisation
Three points where projects commonly diverge without breaking the system:
| Change | Where | Safe? |
|---|---|---|
| Primary accent colour | Remap --primary to a different palette in the :root block | yes |
| Body typeface | Change the body { font-family: ... } stack | yes (stay serif) |
| Monospace typeface | Change the shared mono stack at the top of the file | yes |
| Hero stat layout | Adjust .hero-stats { grid-template-columns: ... } | yes |
| Section numbering | Pure HTML — change the numerals in the // NN anchors and TOC nums | yes |
Hosting
The output is a folder of static HTML files. Every project that already has a docs/ folder can host them as-is:
- GitHub Pages — point Pages at the docs folder; the files render directly.
- S3 / Cloudflare / Netlify — upload the folder; no build pipeline required.
- Local browsing — open the HTML file directly. The asset references are relative; everything just works.
- Inside an IDE — most editors render the HTML in a live preview.
What to keep verbatim
To stay compatible with future updates to the system (and to keep visual consistency across projects), these conventions are load-bearing:
- The class names —
.layout,.rail,.hero,.code-frame,.callout,.fields,.steps, plus all the token classes. - The page skeleton (rail → main → hero → sections → footer order).
- The
// NNsection-anchor pattern matching the TOC.toc-num. - The titlebar structure inside
.code-frame(lights → filename + ext → langtag). - The callout label-inside-
<strong>convention.
Free to vary: colours, typeface, sub-section structure, the words you put in badges, the content of stat blocks, the granularity of TOC groupings.
// 19Don'ts
Things that look reasonable but fight the system:
- Don't introduce a build step. The whole appeal is "save the file, refresh the browser." A bundler, MDX, or static-site generator forfeits that.
- Don't add a fourth heading level. If a section needs
h4, it's probably two sections. - Don't render code without a code frame. Bare
<pre>outside.code-frameloses the titlebar context — the language, the filename, the "this is code" framing. - Don't omit the token spans inside code. Plain code reads as a flat grey block; the token highlighting carries enormous information density.
- Don't use callouts as decoration. Three callouts per section means none of them mean anything. One per section, at most.
- Don't reflow the layout for mobile. The system is designed for laptop+ screens. Mobile gets a single-column scroll and that is fine — better than a broken responsive layout that looks half-right on every viewport.
- Don't add a search bar. The numbered TOC is the navigation. If the site grows past what a TOC can cover, that's a sign the docs need to be split, not that the design needs a search bar.
- Don't break the warm-paper aesthetic. Pure white backgrounds and Inter / Roboto type collapse the personality. The cream paper and Charter serif are the system.