@whynot/design
The neutral, mostly-black-and-white visual language for whynot — Tegwick's prototype and market-signal organisation. Wireframe-leaning. Quiet. Built for artefacts that should look deliberately unfinished.
A prototype is a question made tangible. —
whynot-control/INTENT.md
This repository is the implementation surface. The language of the system — voice, casing, motifs, the reasoning behind each rule — lives in this README. The artefact — the CSS, the components, the assets — lives in src/ and assets/.
If you're new here, read these in order:
DesignSystemIntroduction.md— how this repo relates towhynot-control, the Claude atelier, and consuming apps. Pipeline, versioning, propagation.SKILL.md— the Agent Skill manifest. Read this if you (or an agent) will be generating new artefacts in this style.- This README — the full design language: tokens, components, content rules, iconography.
CONTRIBUTING.md— how to propose, review, and ship a change.
Quick start
# in a consuming repo
pnpm add git+ssh://git@gitea.example.com/whynot/whynot-design.git#v0.1.0
// at the app root, once
import "@whynot/design/styles/colors_and_type.css";
// anywhere
import { Button, Tag, Eyebrow, StageDot } from "@whynot/design";
What lives where
| Path | Contents |
|---|---|
tokens/ |
Source-of-truth design tokens as JSON. |
src/styles/colors_and_type.css |
All CSS variables + semantic element styles. The single file every consumer imports. |
src/components/ |
React components (JSX, no build step). |
src/index.js |
Barrel export. |
assets/ |
Logo, mark, future imagery. |
examples/whynot-control/ |
Live click-through UI kit. Also the Playwright visual-regression target. |
The remainder of this README is the full design language — colour reasoning, type stack, content rules, iconography. It is identical to the language defined in the Claude atelier project and should stay in sync. Treat it as authoritative.
CONTENT FUNDAMENTALS
Voice
The voice is quiet, structured, evidence-oriented, and careful about overclaiming. It comes directly out of the AGENT_RULES.md and OPERATING_MODEL.md:
Agent outputs should be concise, evidence-oriented, explicit about uncertainty, and careful to separate idea, hypothesis, signal, and decision.
—
whynot-control/AGENT_RULES.md
The voice should sound like a careful field-notebook — not a startup landing page, not a product manifesto, not marketing copy.
Casing
- Sentence case for headings, buttons, labels, links — not Title Case. ("Smallest useful test", not "Smallest Useful Test".)
- lowercase for the organization name in body:
whynot, neverWhyNotorWHYNOT. The logo wordmark may useWhyWhyNotfor legacy reasons. - UPPERCASE is reserved for short eyebrow labels (
PROTOTYPE,SIGNAL S2,STAGE,IN BETA) — set in mono, with letterspacing. code-casefor repo names, doc names, folder names:whynot-control,INTENT.md,inbox/. Always in monospace.
Person
Third person, with the project as subject. Avoid "we", avoid "you", avoid "I".
- ✅ "A prototype is a question made tangible."
- ✅ "The repository helps the user capture unusual but potentially useful ideas."
- ❌ "We help you discover weird ideas."
- ❌ "You'll love how easy this is."
Direct second-person is reserved for imperatives in a checklist (e.g. "Read INTENT.md.").
Tone
- Curious, not enthusiastic. "This may be worth a closer look" beats "🚀 huge if true!".
- Hedged, not promotional. Use may, could, seems, appears to. Avoid will, guaranteed, the best.
- Distinguish idea / hypothesis / signal / decision. Never collapse them.
- Lack of signal is also information. Silence is a finding, not a failure.
Phrasing patterns to imitate
Lifted from the existing control documents:
- "A prototype is a question made tangible."
- "Signal beats enthusiasm."
- "Signals are evidence, not vibes."
- "Capture is not commitment."
- "Low-cost learning first."
- "A prototype can be interesting and still be parked."
The pattern is: short declarative claim → small qualifier or counter-claim. Two beats, no exclamation.
Phrasing to avoid
- ❌ "Revolutionize…", "Reimagine…", "Unlock…", "Empower…"
- ❌ "🚀", "✨", "🔥", any other hype emoji.
- ❌ "We believe…", "We're on a mission to…"
- ❌ "Beta — sign up now!" (use "Closed beta. Invitation only.")
- ❌ Numbers without context ("10x faster"). Use signal-record format instead.
Examples
| ❌ Avoid | ✅ Prefer |
|---|---|
| "Get started — it's free!" | "Inbox is open. Capture is not commitment." |
| "Our amazing new prototype" | "Prototype WNO-014. Stage: experiment." |
| "Users love it!" | "S2 — repeated interest, three concrete use-cases." |
| "Coming soon — sign up!" | "Closed beta. Five seats. Ends 2026-04-01." |
| "🎉 Launched!" | "Promoted to Helix on 2026-03-12. See DECISIONS.md." |
Emoji & punctuation
- No emoji in body copy, headings, or UI.
?and!are the brand's punctuation — they appear in the logo and may appear, sparingly, in display headlines (try($idea) until success;,why? why not!).→(U+2192) for "promotes to / goes to" links between stages. Not->.—(em dash) for parenthetical, not--.
VISUAL FOUNDATIONS
Overall posture
The system reads like engineering graph paper — precise hairlines, lots of whitespace, monospace labels in margins, content blocks that look like fields in a form rather than cards in a feed. The aesthetic is closer to a Bauhaus wall-chart or a man page than to a SaaS dashboard.
Color
- Mostly black on white, with a few flat greys.
- One accent only: a warm yellow (
--hi: #FFE14A) lifted from the LEGO brick in the logo. It appears as highlighter / annotation / signal-marker — never as a button fill, never as a hero background. - No gradients. Anywhere. Including subtle ones.
- No tinted whites —
--paperis a true#FFFFFF;--paper-2and--paper-3are barely-warm off-whites (#FAFAF7,#F4F4EF) reserved for sheets and recessed code blocks. - Status colors (S0–S4 signal strength) are rendered as desaturated grey ramps, not red/yellow/green. S4 ("commercial signal") is the only one that uses the yellow accent, because that's the threshold where a prototype actually matters commercially.
Type
- Family:
IBM Plex Sansfor everything UI/body.IBM Plex Monofor labels, code, and stage markers.IBM Plex Seriffor the occasional editorial pull-quote. (See font substitution note in Fonts below.) - Weights: 300 (display only), 400 (body), 500 (UI / headings), 600 (occasional emphasis). Never 700+ — too marketing.
- Tracking: tight on display (
-0.035em), neutral on body, wide on uppercase labels (0.08em— this is the one signature move). - Eyebrows everywhere: short uppercase mono labels above titles (
STAGE,SIGNAL,PROTOTYPE). They are the system's main rhythmic element.
Spacing
- 4px base unit, exposed as
--sp-1(4) through--sp-10(128). - Generous: a content block typically has
--sp-7(48px) of internal padding. Lists separated by--sp-5(24px) minimum. - Section breaks are big (
--sp-9, 96px). Reads like a printed report.
Backgrounds
--paper(#FFFFFF) by default. Period.--paper-2for full-width "sheet" sections (e.g. between hero and content).--paper-3for recessed surfaces only — code blocks, inline pre, inset cards.- No images as backgrounds. No hand-drawn illustrations. No repeating patterns. No textures. No noise. No grain. (Exception: a 1px hairline grid may be used on a literal "wireframe" mock; see
preview/grid-paper.html.)
Animation
- Minimal. This is a document system, not a product UI.
- Transitions on hover only:
120ms easeontext-decoration-colorfor links,border-colorfor inputs. - No spring physics, no bounce, no fade-in-on-scroll. Anything that draws attention is not in the spirit of "low-cost learning first".
- Exception: the cursor-blink animation on a
<TerminalLine>is permitted because it's a literal terminal motif.
Hover & press states
- Links — underline color goes from
--border-strongto--fg-1on hover. - Buttons (primary, dark) —
--ink→--ink-2on hover. On press, no transform, just--ink(back to baseline). - Buttons (secondary, outline) — border
--border→--ink. On press, background flashes--bg-3. - Cards — do not have hover states. They are documents, not interactive surfaces. Exception: prototype cards in an index list get a 1px black left border on hover.
- No scale transforms, no shadow lifts, no glow effects.
Borders
- 1px solid
--border(#E5E5E2) is the default. Used everywhere. --border-strong(#C9C9C5) for section dividers and the outline of a primary block.- Hairline
--border-soft(#F0F0EC) for internal rules within a card. - No double borders, no inset borders, no dashed borders except in one specific case: dashed
--border-strongis used to indicate "placeholder / not yet defined" content (see Components / Empty State).
Shadows
--shadow-0: none. This is the default. Most cards and panels have no shadow.--shadow-1:0 1px 0 var(--line)— a 1px bottom-line, used in place of bottom-border on sticky headers.--shadow-2:0 1px 0 var(--line-strong)— slightly stronger version.--shadow-3is reserved for floating elements only (a popover, a focus-trapped modal). Even then it's a soft 4-12px diffuse shadow at 10% opacity — never a "card lift" shadow.
Protection gradients vs capsules
- No protection gradients. Backgrounds are solid; never overlay a gradient to "rescue" text from a busy background, because backgrounds are never busy.
- Capsules (pills with rounded ends) are used only for
--labeland tag elements — never for buttons. Buttons are slightly rounded rectangles (--r-2, 4px).
Layout rules
- Single-column reading width of ~640px for body, ~880px for documents with sidebars.
- Constant page padding of
--sp-7(48px) on desktop,--sp-4(16px) on mobile. - Sticky element: top navigation bar, height 56px, bottom hairline.
- Sidebar (where used): 256px fixed width,
--paper-2background, no border on right (uses whitespace to separate from main). - Grids for structured data only — never as a "card wall". 12-column with 24px gutters.
Transparency & blur
- Almost never used. No frosted glass. No backdrop-filter.
- One permitted use: a
rgba(255,255,255,0.92)on the sticky top nav so that scrolled content is faintly visible behind it. No blur.
Imagery
- Black & white only. All photography (when used) is rendered with
filter: grayscale(1) contrast(0.95)— slightly low-contrast, like a Risograph print. - Aspect ratios: 4:3 (preferred — feels like a document figure), 1:1 (for portraits / icons). Never 16:9 in body.
- No people-stock-photography. Prefer objects, diagrams, or screenshots.
- No AI-generated imagery unless explicitly labelled as such with a
[generated]caption.
Corner radii
--r-1(2px) for inputs and tags.--r-2(4px) for buttons and small cards.--r-3(8px) for large cards and modals.--r-pill(999px) for label capsules only.--r-0(0px / square) is the default for documents, sheets, and any element wider than ~600px. Big things are square.
Cards
A "card" in this system is a bordered rectangle, not a shadowed object floating off the page.
- 1px
--borderoutline. --paperbackground.- 4px or 8px radius depending on size.
- No shadow.
- Internal padding
--sp-5(24px) minimum. - A monospace eyebrow at top-left + a stage label at top-right is the canonical card header.
- On hover (when interactive): the top-left eyebrow tints to
--fg-1, and a 2px black bar appears flush against the left edge. Nothing else moves.
ICONOGRAPHY
The codebase did not ship an icon font, an icon sprite, or any SVG icons — whynot-control is a documents-only repo. The only visual asset is the LEGO-brick logo.
Approach
- Lucide icons via CDN is the chosen icon set. It matches the system's stroke weight (1.5px), neutral geometry, and "wireframe artefact" feel better than Material, Heroicons, or Phosphor. Load with
<script src="https://unpkg.com/lucide@latest"></script>and calllucide.createIcons(). - Stroke weight is always 1.5px. Override Lucide's default 2px via
stroke-width="1.5"on each<svg>or via CSS. - Color:
currentColor. Icons inherit from text. No two-tone, no fills. - Size: 16px (inline), 20px (button), 24px (heading-adjacent), 32px (feature). Never larger — large icons read as decoration, and decoration is not in the spirit.
When to use icons
- In navigation labels, button labels, and inline status — only when the icon adds parsing speed. If the word reads faster than the icon, skip the icon.
- In document margins to mark stage transitions (e.g.
→ Helix). - Not as decorative chrome on cards.
- Not as "feature icons" in a 3-up grid on a marketing page (this system has no marketing pages).
Emoji
- Never. Emoji are not used anywhere — in copy, labels, alt text, or as fallback for missing icons.
Unicode characters used as icons
These are allowed and preferred over raster icons in some contexts:
| Char | Used for |
|---|---|
→ |
"promotes to" / pipeline arrow |
← |
back / previous |
· |
inline bullet separator (in nav, in meta lines) |
— |
em dash, in metadata |
? ! |
the brand's signature punctuation, in display headlines only |
§ |
section marker, in long documents |
The logo
- The primary mark is the LEGO brick with
?and!underneath, in pure black & white.assets/whynot-logo.png(300×300 raster, transparent background). - For very small uses (favicon, footer mark), a simplified mark is recommended: just the
?!pair inIBM Plex Sans 600, with the brick implied by a 2×4 dot grid above. Seepreview/logo.html. - The brick should never be coloured (no LEGO-red, LEGO-blue, etc). It is always black-outlined on white, or white-outlined on black.
- Minimum size: 32px square. Below that, fall back to the
?!wordmark.
A note on font substitution
The control repo did not ship font files. IBM Plex Sans / Mono / Serif were chosen as a fresh pairing because:
- The "Plex" family was designed by IBM as an explicitly neutral, technical-document family — the same use-case as this system.
- All three (sans, mono, serif) share metrics, so they mix cleanly in templates and tables.
- They are openly licensed (SIL OFL) and available on Google Fonts.
Plex is currently loaded from Google Fonts (see top of colors_and_type.css). For offline use, drop the .woff2 files into fonts/ and swap the @import for a local @font-face block.
🟨 Substitution flagged: there was no specified brand font; IBM Plex is a choice made here. If
whynotlater adopts a different brand font, replace--ff-sans/--ff-mono/--ff-serifincolors_and_type.cssand everything downstream will follow.