Form follows function.
The system is intentionally small. It captures one strong opinion (minimal, classless, semantic, sharp-cornered) and applies it everywhere. Most of the design lives in the browser's defaults plus a tight set of CSS variables. There is no component library to learn, no token sprawl to memorize, just a stylesheet, a font stack, and a color. The principles are older than the code: Reyner Banham's 1955 brutalism manifesto asked buildings for memorability as image, clear exhibition of structure, and valuation of materials as found. The same three apply here.
<header>, <main>, <section>, <table>, <form>, <details>, do that. Reach for a class only when the HTML genuinely can't say it.
#C85A3A) is the only warm color on the page. Everything else is a stop on the neutral ramp. Adding a second accent is a brand violation.
Seven stops of soot, one drop of ember.
The whole palette is a 7-stop neutral ramp from soot (#0A0A0A) to bone (#ECECEC), plus a single accent (ember) and four rarely-used signal colors. Do not introduce a second hue. If you find yourself reaching for one, it's almost always solvable with a stop on the ramp.
| Token | Hex | Role |
|---|---|---|
--soot-0 | #0A0A0A | Page background. The default canvas. |
--soot-1 | #1A1A1A | Elevated surface: code blocks, drop-zones, inputs. |
--soot-2 | #2A2A2A | Hover surface on dark; dividers. |
--soot-3 | #4A4A4A | Heavier dividers; disabled-on-dark. |
--soot-4 | #7A7A7A | Muted text, captions, <figcaption>. |
--soot-5 | #C8C8C8 | Light dividers; secondary on dark. |
--soot-6 | #ECECEC | Primary text; primary structural border. |
--ember | #C85A3A | The single accent. Links, focus rings, <mark>, the heart. |
--ember-2 | #E07050 | Hover-on-ember. Used sparingly. |
Each stop has a defined job and a material analogue. Soot is the concrete in shadow, bone is the same concrete in light, and ember is the rust-iron weep that runs down the side of a 1970s slab. --soot-1 is for elevated surfaces, --soot-4 for muted text, full stop. The ramp is small enough to memorize and strict enough that you never need to invent a custom gray.
Four signals that share ember's family resemblance.
Signal colors are tuned to share luminance and saturation with ember, so when they appear they read like cousins, not like the Bootstrap default palette. They are reached for only when the UI genuinely needs to signal state. Never as decoration, never as a second accent, never two at once.
Usage in context
#SOSBrutalism on social media and the DAM curators will check the building for inclusion.
Same family of hue and value as ember keeps the page visually coherent even when it has to flash a warning. Ember is still the dominant accent by far. If a screen contains a signal color and ember next to each other, ember wins the focal point.
Light mode exists, but dark is the brand.
A complete light mirror is shipped as an opt-in via <html data-theme="light">. Most pages should still ship dark; that is the brand identity. Use the toggle in the floating panel (bottom-right) to see the whole page repaint in real time. It is the same tokens, just remapped.
Dark · default
--color-bg | #0A0A0A |
--color-fg | #ECECEC |
--color-bg-elev | #1A1A1A |
--color-border | #ECECEC |
Light · opt-in
--color-bg | #ECECEC |
--color-fg | #0A0A0A |
--color-bg-elev | #FFFFFF |
--color-border | #0A0A0A |
System fonts, set with intent.
The default stack is the user's native UI font: Apple's San Francisco on Mac and iOS, Segoe UI on Windows, Roboto on Android. It loads instantly, costs zero bytes, and is hard to make ugly. Monospace follows the same idea: SF Mono first, then the best-available installed mono.
--font-sans
-apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif
--font-mono
"SF Mono", "Monaco", "Cascadia Code", "Roboto Mono", "Courier New", monospace
Type scale
Headings shrink fluidly with viewport width. Body sits at 14–15 px and uses a relaxed line height. Nothing scales below 12 px.
Body and runs of text
The body is set with line-height: 1.7 and text-wrap: pretty, which trims off orphaned single-word last lines. Headings get text-wrap: balance so multi-line titles fall in pleasing chunks. Tracking is slightly tight on display sizes and slightly loose on UPPERCASE labels; the eye reads each at its best.
He proposed three criteria for a building to qualify: memorability as image, clear exhibition of structure, and valuation of materials as found. No ornament. No plaster. What the building does, you can see. What it's made of, you can touch.
On Reyner Banham, The New Brutalism, Architectural Review, December 1955
Webfonts are a tax: bytes, layout shift, FOIT. The system stack pays none of it and reads native on every OS. Reach for a designed family only when the project specifically needs one. See the next section.
Two opt-in font families.
If a project benefits from a designed family (usually marketing pages, not utility tools), link one of the presets in fonts/ after kauz.css. The preset overrides the family and enables the font's stylistic sets, ligatures, and tabular numerals. Everything else in the design system is unchanged.
fonts/plex.css
IBM Plex Sans & Plex Mono
Humanist, slightly engineered. The most "Kauz".
<link rel="stylesheet" href="fonts/plex.css">
fonts/dm.css
DM Sans & DM Mono
Geometric, screen-optimized. Crisp at small sizes.
<link rel="stylesheet" href="fonts/dm.css">
Try a preset live with the font switcher in the floating panel. The page will reflow as the family swaps; everything else stays put.
One unit. Multiples only.
Everything that takes up space uses a multiple of --space-unit, which is 0.5rem (8 px). The set of allowed sizes is small on purpose: 8, 16, 24, 32, 48, 64. No half-units. No 12, no 20. If the design wants 14 px between two elements, the answer is 16.
A single unit is what makes the system feel coherent. You can change --space-unit in :root and every margin in the system scales with it. Try setting it to 0.375rem in the config panel and watch the entire page tighten up.
Three weights, no radius, ever.
border-radius: 0 everywhere. Buttons, inputs, cards, code blocks, images, even checkboxes. This is the loudest brand signal the system has and the easiest to violate by accident. If you see a rounded corner in a Kauz product, something has gone wrong.
--border-thin
1 px, soft. Inline things: code, kbd, swatch outlines.
--border-normal
2 px, soft. The default. Sections, fieldsets, forms, cards.
--border-heavy
4 px, bone. The structural accent. Left rule on blockquote, pre, .why blocks.
The heavy rule is reserved. When you see it on the left of a block, the page is saying "this is structural; pay attention". Don't apply it casually or it loses its weight.
No shadows. Lightness is elevation.
The system has no box-shadow. None. Surfaces that need to feel raised step up the neutral ramp instead: page is --soot-0, elevated surface is --soot-1, hover is --soot-2. If a card feels flat, the answer is more contrast or a 2 px border, never a drop shadow.
--soot-0
Page background. The canvas.
--soot-1
One step up: code blocks, drop-zones, the elevated card.
--soot-2
Two steps up: hover-on-dark.
Card uses --soot-1 + --border-normal. Clearly different from the page, no haze around it.
Same card with a soft drop shadow. The page is haunted now. Delete it.
One centered column. Two opt-outs.
The default body is a centered single column at max-width: 960px, with 20 px of padding. Sections separate themselves with a 2 px top border and 40 px of margin. There are no fixed headers, no sticky bars, no sidebars. The page reads top to bottom; that is on purpose.
Default
<body>
<header> ... </header>
<main> ... </main>
<footer> ... </footer>
</body>
Centered, 960 px max. The reading layout.
Full-bleed opt-in
<body data-full-bleed>
...
</body>
Width fills the viewport. For dashboards and tool surfaces.
Honest inputs. Hard borders. Native behavior.
Text fields sit at --color-bg-elev, with a 2 px soft border. No placeholder grays, no floating labels, no inset shadow. The cursor lands and the field is obviously the field.
Both are squares.
The system renders checkboxes and radios as identical 16 px sharp squares with hand-built indicators: a rotated tick for checked, a filled inner square for radio. The semantic difference (multi-select vs exclusive) lives in HTML, not in the shape. Brutalism doesn't bow to the radio-is-round convention.
Indeterminate state is supported too: set el.indeterminate = true in JS and the box renders an ember bar instead of a tick.
Sharp ember thumb. Native progress.
The range slider is a 14×22 px ember rectangle on a 1 px bordered track. The filled portion uses a tiny variable, --_pct, which kauz.js updates on input. A paired <output> element (matched via for="…" or as a direct sibling) is kept in sync automatically.
Drag a slider. The output updates as you go.
Group with fieldset. Caption with legend.
Forms group related inputs in a <fieldset> with a <legend>. There are no card wrappers and no floating helper text; the legend explains the group, the label explains the field, and that is the whole structure.
Three lists for three relationships.
Unordered
- Memorability as image.
- Clear exhibition of structure.
- Valuation of materials as found.
Ordered
- Visit the site.
- Photograph the elevations.
- File the survey.
Definition
- Béton brut
- French for "raw concrete". Le Corbusier's term for unfinished, board-formed cast surfaces.
- Reyner Banham
- British critic. Wrote The New Brutalism in Architectural Review, December 1955.
- Soot
- The color of the page background.
<ul> uses a square bullet: Unicode ▪ via list-style: square. That square is the only decoration the bullet ever gets. No custom icons, no gradient discs.
Caption it. Header it. Read it top to bottom.
| Building | Year | Location | Status |
|---|---|---|---|
| Corbusierhaus | 1958 | Berlin | Listed (1996) |
| Ruhr-Universität | 1965 | Bochum | Listed (2015) |
| St. Agnes | 1967 | Berlin | Gallery since 2015 |
| Mariendom | 1968 | Neviges | In use |
| Bensberg Rathaus | 1969 | Bergisch Gladbach | Listed |
| ICC Berlin | 1979 | Berlin | Listed (2019), closed |
| Mäusebunker | 1981 | Berlin | Listed (2023) |
The caption is UPPERCASE and tracked, the same affordance as a button label. It says "this is a table about X" before your eye gets to the headers, and it doesn't waste a paragraph of body copy to do it.
Heavy left rule means "structural".
Both blockquote and pre get a 4 px bone rule on the left. That rule is the visual handle that says "this content is lifted out of the page flow". The same rule appears on .why blocks; it never appears on anything else.
It is our intention to have the structure exposed entirely, without interior finishes, wherever practicable.
Alison Smithson, 1953
// frame.js: drop an image, get a bordered frame
function applyFrame(img, { thickness = 64, blur = 12 } = {}) {
const canvas = document.createElement('canvas');
canvas.width = img.width + thickness * 2;
canvas.height = img.height + thickness * 2;
const ctx = canvas.getContext('2d');
ctx.filter = `blur(${blur}px)`;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.filter = 'none';
ctx.drawImage(img, thickness, thickness);
return canvas;
}
Inline things use the thin border instead: npm install soot.css, git push origin main, & the occasional 404 NOT FOUND in samp.
Spoilers without JS.
<details> is the system's only collapsible affordance. It is native, accessible, and survives JS being off. The marker is the browser default; the chrome around it is the system's.
How did the Mäusebunker get saved?
Charité applied for a demolition permit in 2020. Felix Torkar and Gunnar Klack started a petition, and Berlin's preservationists rallied. Three years and 10,683 signatures later, the State Monuments Office listed both the Mäusebunker and its sibling Hygieneinstitut as cultural monuments.
- December 2020: demolition permit applied for.
- 2020–2023: campaign, petition, press.
- May 2023: Mäusebunker and Hygieneinstitut listed.
- Result: 10,683 signatures, two saved buildings.
SOS Brutalism, by the numbers (open by default)
Started in 2015 by the Deutsches Architekturmuseum and the Wüstenrot Foundation. As of 2025 the database holds 2,300+ entries, with roughly 175 on the RED LIST as threatened. Tag #SOSBrutalism on social media and the curators will check the building for inclusion.
Grayscale, full-bleed, untouched.
The system uses very little photography. When it does, it is desaturated or near-desaturated, high-contrast, often architectural. The placeholder service of choice is picsum.photos/...?grayscale. Ember is the page's only warm note; never a warm photo.
Mostly, don't.
The brand is anti-decoration, and most icons are decoration. The system prefers text labels, semantic HTML, and the occasional Unicode shape. There is exactly one sanctioned glyph (♡) and it lives in the footer signature.
Use <button>DOWNLOAD</button>. The word is unambiguous and reads everywhere.
Use a download arrow glyph with no label. Now I have to guess.
Use <kbd>Esc</kbd> for shortcut hints; it renders as a small bordered cap.
Esc Clear
Use a rocket emoji to indicate "launch". This is a design system, not a startup pitch deck.
If a screen genuinely needs an iconic affordance (and you have argued yourself out of every alternative), use Lucide at stroke-width="1.5", sized at 1em, stroked with currentColor, inline at the start of a label. Document it as a deviation.
One letter, on the same soot square, forever.
There is no wordmark and no separate glyph mark. Every product gets a single character (Helvetica Bold, ember on soot) on a square. The favicon is the logo and the logo is the favicon. New project under brutalist.systems? It gets a new letter. That's it.
The full set of variables.
Everything below is overridable from :root in your own stylesheet. The system is built around the assumption that you will override a few of these per project: most likely --color-accent never, --space-unit rarely, and --max-content-width if you opt out of the centered layout. The floating panel (bottom-right) edits a handful of these live so you can feel the consequence.
Color
| Token | Default | Used for |
|---|---|---|
--color-bg | #0A0A0A | Page background. |
--color-bg-elev | #1A1A1A | Elevated surface: inputs, code, drop-zone. |
--color-bg-hover | #2A2A2A | Hover state on dark. |
--color-fg | #ECECEC | Primary text. |
--color-fg-muted | #7A7A7A | Secondary text, captions. |
--color-fg-dim | #4A4A4A | Tertiary, very quiet. |
--color-accent | #C85A3A | Links, focus rings, <mark>, the heart. |
--color-accent-2 | #E07050 | Hover-on-ember. |
--color-border | #ECECEC | Heavy / structural border (4 px rule). |
--color-border-soft | rgba(236,236,236,0.10) | Default border on dark. |
--color-mark-bg · --color-mark-fg | ember / soot | <mark> fill and text. |
--critical | #C8332E | Signal · danger. |
--warning | #C8902E | Signal · expiring. |
--success | #5C9C72 | Signal · saved. |
--info | #5C84A5 | Signal · neutral notice. |
Type
| Token | Default | Used for |
|---|---|---|
--font-sans | -apple-system, … | Body, headings, UI. |
--font-mono | "SF Mono", … | code, kbd, pre, output. |
--fs-base | clamp(0.875rem, 1vw + 0.75rem, 0.9375rem) | Body fluid size: 14→15 px. |
--fs-sm · --fs-xs | 0.8125rem · 0.75rem | Captions, eyebrows, mono tags. |
--fs-h1 | clamp(1.75rem, 3.5vw + 0.5rem, 2.75rem) | Page title. |
--fs-h2 | clamp(1.375rem, 2vw + 0.5rem, 1.875rem) | Section heading. |
--fs-h3 · --fs-h4 | 1.25rem · 1.0625rem | Sub-headings. |
--lh-base · --lh-relaxed · --lh-tight | 1.6 · 1.7 · 1.2 | UI / body / headings. |
--tracking-loose | 0.04em | UPPERCASE labels, captions. |
--tracking-tight | -0.01em | Large display headings. |
Spacing & borders
| Token | Default | Used for |
|---|---|---|
--space-unit | 0.5rem | The atom. Change this and the page scales. |
--space-1 … --space-8 | 8 · 16 · 24 · 32 · 48 · 64 px | All gaps, margins, paddings. |
--border-thin | 1px solid var(--color-border-soft) | Inline elements. |
--border-normal | 2px solid var(--color-border-soft) | Default. Sections, forms. |
--border-heavy | 4px solid var(--color-border) | Structural left rule. |
--border-radius | 0 | Always zero. Don't change. |
--max-content-width | 960px | Centered-column cap. |
Example override
:root {
/* a project under brutalist.systems that wants tighter density */
--space-unit: 0.375rem;
--max-content-width: 1200px;
}
/* opt out of the centered layout for a dashboard */
<body data-full-bleed>
The system intentionally exposes few variables. If a project finds itself overriding more than three or four, that's usually a sign the project should be its own brand, not a fork of Kauz.
If you do these, the brand breaks.
Sharp corners, everywhere.
Rounded corners. Never. Not on buttons, not on cards, not on inputs.
Flat background colors.
Gradient backgrounds. No diagonals, no gloss, no "cosmic" anything.
One accent: ember. Links, focus, marks, the heart.
A second accent. Indigo for "primary", ember for "destructive"? No. Pick one.
Text labels on buttons.
Emoji as UI. Not in buttons. Not in nav. Not in headings.
Honest, first-person copy.
I built this so I'd stop reaching for the same five tabs every time someone asked about a Böhm.
Marketing voice. "Empower", "leverage", "revolutionary", exclamation marks.
Empower your team to unlock revolutionary outcomes!