v1 guide portable html · css · js

[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.

2asset files (CSS, JS)
10reusable primitives
9accent palettes
0build steps

// 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 // NN anchor; 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.

Page layout anatomy Schematic of a scut docs page: sticky rail on the left containing project label, page name with live dot, numbered table of contents, and theme switcher. Main column on the right containing hero with badges, moniker, lede and stats, followed by numbered sections containing primitives, and a single-line footer. // project · area Page Name Overview 01Introduction 02Anatomy 03Quick Start Primitives 04Layout 05Rail and TOC 06Hero 07Sections Theme [ PageMoniker ] 25hook events 2 scopes 9 primitives 0 build steps // 01 First Section // 02 Second Section NOTE project section · category rail · 260px main · fluid (max 1340px) page max-width · 1600px · centred

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

  1. Copy the two asset files into your project's docs folder:

    install.sh SHELL
    # From this repo:
    cp scut/docs/scut-docs.css  yourproject/docs/yourproject-docs.css
    cp scut/docs/scut-docs.js   yourproject/docs/yourproject-docs.js

    Rename the files to <project>-docs.css / <project>-docs.js. The scut- prefix is namespacing the localStorage key the JS uses (see section 16); rename the constant inside the JS too.

  2. Create your first page using the skeleton below. Save it next to the assets.

  3. Open the file directly in a browser — there is no build step. Iterate, save, refresh.

page-skeleton.html HTML
<!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>
noteEvery page in this design system has this same shell. The variation lives inside <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.css CSS
.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.

rail.html HTML
<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>
ClassPurposeNotes
.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.

hero.html HTML
<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:

ClassColourTypical use
.badge.primarycoralVersion or maturity (v1 docs, v0 spec)
.badge.swifttangerineImplementation language or runtime
.badge.testssuccess greenCategory or stage tag
.badge.macossky bluePlatform 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.

switcher.html HTML
<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:

  1. 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.
  2. Pre-paint resolution. The JS runs before first paint (deliberately not deferred) and sets html[data-theme] from localStorage → OS preference → "light". This prevents a flash of unstyled theme.
  3. Cross-tab sync. A storage event 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.html HTML
<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.

fields.html HTML
<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 classRenderingUse for
.fieldmonospace, full opacityIdentifiers — field names, flag names, file paths
.typemonospace, sky blueType names, enum values, kinds
.noteserif, body weightDescriptions 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.

code-frame.html HTML
<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>
noteFilenames split into the base name and an <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.

ClassColourUse for
.tok-keywordvioletLanguage keywords (func, type, return, HTML tag delimiters)
.tok-typesky blueType names, struct names, attribute names
.tok-stringmint greenString literals, attribute values
.tok-numbertangerineNumeric literals, hex values, units
.tok-commentslate (italic)Comments — any flavour
.ini-sectiontangerine (bold)INI / TOML section headers
.ini-keysky blueINI / TOML keys
.ini-valuemint greenINI / TOML values
.term-promptmint (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.

callouts.html HTML
<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>
tipThis is what the default coral variant looks like — use it sparingly for editorial asides that don't fit the typed categories.
noteThe note variant is the workhorse — clarifications, "here's what to remember", invariants worth highlighting. Most pages will use this and only this.
warnThe warn variant marks footguns and gotchas — behaviours that surprise readers, things that can go wrong if you ignore the advice.
okThe success variant marks confirmed-good states — useful in checklist-style content ("this is the right shape") but rare overall.
dangerThe 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.

steps.html HTML
<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.

Colour palette swatches Each row shows one palette family with five representative shades (100, 300, 500, 700, 900) and a usage note. Coral is the primary accent; sunshine highlights selections; sky tints type and note callouts; mint marks success and the live dot; violet colours keywords in code; tangerine colours numbers and INI sections; bubblegum and lagoon are reserved accents; slate provides body type and rules. 100 300 500 700 900 role coral primary · links · accents sunshine selection · decisions sky types in code · note callouts mint strings · success · live dot violet keywords in code tangerine numbers · INI sections bubblegum reserved · editorial accent lagoon reserved · alternate cool tone slate body type · rules · muted UI

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.

semantic-tokens.css CSS
/* 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:

  1. Explicit user choice — the theme switcher sets html[data-theme="light"] or html[data-theme="dark"] and persists the choice in localStorage.
  2. OS preference — when no data-theme attribute is set and JS hasn't run, the CSS @media (prefers-color-scheme: dark) block applies the dark palette automatically.
  3. 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-toggle button to flip the attribute and persist the choice.
  • Listens to the storage event 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.

warnThe script is intentionally not deferred. Loading it as a regular <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 .note cells 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

  1. Copy scut-docs.css and scut-docs.js into your project's docs folder. Rename to <project>-docs.css / .js.
  2. In the JS, change STORAGE_KEY from "scut-docs-theme" to "<project>-docs-theme".
  3. Copy one existing page (e.g. kong-base-setup.html) as a template and overwrite the content.
  4. 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:

ChangeWhereSafe?
Primary accent colourRemap --primary to a different palette in the :root blockyes
Body typefaceChange the body { font-family: ... } stackyes (stay serif)
Monospace typefaceChange the shared mono stack at the top of the fileyes
Hero stat layoutAdjust .hero-stats { grid-template-columns: ... }yes
Section numberingPure HTML — change the numerals in the // NN anchors and TOC numsyes

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 // NN section-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-frame loses 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.