Files
whynot-design/DesignSystemIntroduction.md
tegwick 80252baf53
Some checks failed
ci / check (push) Has been cancelled
ci / release (push) Has been cancelled
version 0.2.0 replaces fromer version!
2026-05-25 19:32:22 +02:00

16 KiB
Raw Permalink Blame History

Design System Introduction

How whynot-design fits into the broader whynot workflow — from atelier exploration to production deploys, across React, Django, plain HTML, or any future consumer.

Audience: anyone (human or agent) about to add whynot-design as a dependency, or contribute changes back to it.


1. Mental model — three places, three jobs

whynot-design is one of three surfaces. Each has a different job. Don't confuse them.

Place Job What lives there
Claude atelier project (WhyNot Design System template) Explore, decide, mock HTML cards, prototypes of new components, the README that defines the rules. Source of truth for the language of the system.
whynot-design repo (this one) Distribute A versioned, publishable package: tokens, CSS, web components, the logo bundle. Source of truth for the artefact.
whynot-* consuming repos (apps, prototypes, marketing sites) Use Add @whynot/design as a dependency. Use it from React, Django, Vue, or plain HTML — see MultiFrameworkSupport.md.

This mirrors the existing organisational logic. whynot-control is the control surface (intent, scope, decisions, governance). whynot-design is the implementation surface for the visual language.

The Claude-Design template is the atelier for the next design exploration. It is not what production code consumes. Production consumes this repo.


2. The three-layer architecture

whynot-design is deliberately framework-agnostic. It ships in three stacked layers, and consumers pick how deep they go.

┌─────────────────────────────────────────────────────────────┐
│  Layer 3 — Framework adapters (optional)                    │
│  Django partials · React thin wrappers · Vue, Svelte, …     │
│  All thin. All delegate to Layer 2.                         │
├─────────────────────────────────────────────────────────────┤
│  Layer 2 — Web components (canonical)                       │
│  <wn-button>, <wn-card>, <wn-modal>, <wn-prototype-card>…   │
│  Lit-based. Light DOM. SSR-friendly. Work in any framework. │
├─────────────────────────────────────────────────────────────┤
│  Layer 1 — Tokens + CSS                                     │
│  colors_and_type.css · components.css · tokens/*.json       │
│  Plain CSS variables and utility classes. Works in any HTML.│
└─────────────────────────────────────────────────────────────┘

Why this shape

  • Layer 1 is the bedrock. Tokens are framework-agnostic by definition. colors_and_type.css is just CSS variables — Django, Rails, Phoenix, plain HTML, any framework can <link> to it and inherit the system's typography and palette tomorrow. The component utility classes in components.css (e.g. .wn-btn, .wn-card) compose those tokens into the canonical recipes. Anything in this repo can be consumed with no JS at all.

  • Layer 2 adds behaviour without lock-in. Web Components are a stable platform feature — they're not a framework dependency that ages out. Lit is the implementation detail; the contract is the HTML custom-element API. A <wn-button variant="primary"> works in a React JSX file, a Django template, a Vue SFC, and index.html identically. Layer 2 components render to light DOM, which means Layer 1's CSS styles them — no shadow-DOM style isolation, no FOUC, no SSR friction.

  • Layer 3 is convenience, not commitment. If a consumer wants typed React props or Django {% include %} shorthand, the repo ships thin wrappers in adapters/. They delegate to Layer 2. Removing or refactoring them does not break Layer 2 consumers.

Which layer should you use?

If your consumer is… Use this layer Example
Django (server-rendered HTML + HTMX) Layer 1 + 2 directly, or Layer 3 partials <wn-button variant="primary">{{ label }}</wn-button>
React (any meta-framework) Layer 2 directly <wn-button variant="primary">Promote</wn-button> — React handles custom elements natively
Plain HTML page / a prototype landing page Layer 2 directly <wn-button variant="primary">Sign up</wn-button>
Email, PDF, or no-JS context Layer 1 only <button class="wn-btn wn-btn--primary">Open</button>
You only need typography & colours Layer 1 only Just <link> the CSS.

The discipline: do not subclass, fork, or restyle Layer 2 components in your repo. If a component doesn't fit a use case, add a variant upstream in whynot-design. The thing that makes a design system valuable is the discipline of not forking it locally.

For the full how-to per framework — including SSR, hydration, the noscript story, form participation, and HTMX integration — see MultiFrameworkSupport.md.


3. What this repo contains

whynot-design/
├── README.md                       Full design language.
├── DesignSystemIntroduction.md     This file.
├── MultiFrameworkSupport.md        How to consume from React, Django, Vue, plain HTML.
├── SKILL.md                        Agent Skill manifest, cross-compatible with Claude Code.
├── CONTRIBUTING.md                 How to propose, review, and ship a change.
├── CHANGELOG.md                    Hand-edited, one entry per release.
├── BOOTSTRAP.md                    First-push instructions. Delete after use.
├── package.json                    @whynot/design — Lit is the one runtime dependency.
│
├── tokens/                         Source-of-truth design tokens (JSON).
│
├── src/
│   ├── styles/
│   │   ├── colors_and_type.css     Layer 1 — tokens + semantic element styles.
│   │   └── components.css          Layer 1 — utility classes for all components.
│   ├── elements/                   Layer 2 — Lit web components, light DOM.
│   │   ├── atoms.js                button, tag, eyebrow, stamp, stage-dot, phase-dot, icon
│   │   ├── form.js                 input, textarea, select, search-input, field-row
│   │   ├── layout.js               card, modal, table, toast, empty-state, breadcrumb
│   │   └── chrome.js               top-nav, sidebar, page-header, pipeline, prototype-card
│   └── index.js                    Side-effect import: registers all custom elements.
│
├── adapters/                       Layer 3 — optional wrappers.
│   └── django/templates/whynot/    Django {% include %} partials over the web components.
│
├── assets/                         Logo, mark, future imagery.
├── examples/
│   ├── showcase/                   Plain-HTML demo of every component.
│   └── whynot-control/             React + custom-element click-through UI kit.
└── .gitea/workflows/               Lint + Playwright visual regression on PR.

4. Integrating with a consuming codebase

4.1 Distribution channels at A1

In order of effort:

a) pnpm workspaces (recommended for now) — put whynot-design and your consuming app in the same monorepo (or use file: / link: references). Zero registry, zero auth, instant updates.

b) Install directly from Gitea — no registry needed.

pnpm add git+ssh://git@gitea.example.com/whynot/whynot-design.git#v0.2.0

Pin to a tag, not main. Tag-pinning is the entire versioning discipline at A1.

When you outgrow either (second team needs read access without cloning, semver resolution becomes valuable), publish to Gitea Packages (native npm protocol) or a private Verdaccio.

4.2 What a consumer imports

The smallest viable consumption is:

<link rel="stylesheet" href="/static/whynot/colors_and_type.css">
<link rel="stylesheet" href="/static/whynot/components.css">
<script type="module" src="/static/whynot/index.js"></script>

That's it. Now <wn-button>, <wn-card>, <wn-modal> etc. work everywhere on the page, in any framework, server-rendered or client-rendered.

For a Node-tooled consumer:

// At app root, once.
import "@whynot/design/styles/colors_and_type.css";
import "@whynot/design/styles/components.css";
import "@whynot/design";              // side-effect: registers all custom elements

// In any component file — React, Vue, Svelte, all behave the same.
function NewBetaPage() {
  return (
    <article>
      <wn-eyebrow>whynot · closed beta</wn-eyebrow>
      <h1>Concierge prototype triage</h1>
      <wn-button variant="primary">Request an invite</wn-button>
    </article>
  );
}

Three rules of consumption:

  1. Import the CSS once at the app's root. Both stylesheets.
  2. Use tokens for any colour, type, or spacing decision that components don't cover. If the token doesn't exist, that's a signal to extend the system upstream — not to invent.
  3. Don't restyle components by overriding their CSS. If a component doesn't fit, contribute a variant.

4.3 Bootstrapping a new consuming repo

mkdir whynot-prototype-WNO-022
cd whynot-prototype-WNO-022
pnpm init
pnpm add @whynot/design  # or the git+ssh URL
# Then in your entry point:
echo 'import "@whynot/design/styles/colors_and_type.css"' >> src/main.js
echo 'import "@whynot/design/styles/components.css"' >> src/main.js
echo 'import "@whynot/design"' >> src/main.js

If it takes longer than this, the design system is fighting you.


5. Propagation pipeline — five hops

The end-to-end flow for a single design change:

[Claude atelier]              [whynot-design]            [Consuming repo]           [Deploy]

 explore variants    ──►    PR with token /        ──►   Renovate / pnpm     ──►    staging
 in the template            component change             up opens PR bumping
 user approves              + Playwright diff             @whynot/design                │
                            + CHANGELOG entry                                           ▼
                                  │                              │                     prod
                            tag v0.3.1                       CI runs visual
                            CI publishes /                  regression on the
                            attaches release                consuming app
                            asset                              │
                                  │                          merge if green
                                  └──────────────────────────────┘

Hop-by-hop

  1. Atelier → whynot-design PR. Someone (you, a designer, or an agent) takes a change agreed in the Claude project and opens a PR against whynot-design. The PR description quotes the atelier decision.

  2. whynot-design CI runs:

    • Lint + (optional) typecheck.
    • Visual regression — Playwright screenshots examples/showcase/index.html and examples/whynot-control/index.html, diffs against baselines. This catches both styling regressions and behavioural ones.
    • A CHANGELOG.md entry is required.
  3. Merge → tag → publish. On merge to main:

    • Bump package.json (patch for token tweaks, minor for new components, major for renames/removals).
    • Tag v0.3.1.
    • Push tag. CI uploads release notes from the CHANGELOG.
  4. Consumer auto-PR. Renovate or Dependabot watches @whynot/design and opens a PR in every consuming repo bumping the version.

  5. Consumer CI + deploy. The consuming repo's own CI runs its visual regression. If unchanged, auto-merge. If changed, a human reviews. Merge triggers existing deploys to staging → prod.

The whole loop, warm, takes minutes. Automation works only because every step has a deterministic check — visual regression on both sides, semver, changelogs. Skip those and the pipeline is a slow manual process with extra tools.


6. Versioning discipline

Strict semver, even at A1.

Change Bump
Token value tweak that doesn't visibly break any existing example patch0.2.0 → 0.2.1
New component, new variant, new token minor0.2.0 → 0.3.0
Removing / renaming a component, prop, or token; changing default behaviour major0.3.0 → 1.0.0

Stay in 0.x.x until something built with this system is in production. While in 0.x.x, minor bumps are allowed to break things — that's the convention. This gives you permission to iterate without ceremony.

Promotion past 1.0.0 should appear in whynot-control/DECISIONS.md. Same rule as promotion to Helix or Coulomb: it's a deliberate act, not a release-script side-effect.


7. Where Claude fits

Two distinct roles, both useful:

  • The Claude atelier template — used at hop 1. Designer (or you) opens a new project, mocks variations, decides, hands off a PR description + diff to whoever writes the whynot-design PR. The atelier never publishes anything to production directly.

  • Claude Code with SKILL.md — used at hop 1 and hop 5. The same SKILL file works in both contexts:

    • Pointed at whynot-design, Claude Code can write component PRs.
    • Pointed at a consuming repo, Claude Code can build screens that respect the rules.

That's why SKILL.md ships with this repo. Drop it into .claude/skills/ of any consuming repo and any agent operating in that repo will know the visual language.


8. Pragmatic A1 staging — don't build the whole pipeline yet

Right now, whynot is at A1 Incubating. Build the smallest pipeline that still has the right shape. Promote each piece only when a real signal demands it.

Hop A1 version Promote to full when…
Atelier Claude template, as-is. Never — stays here.
whynot-design repo This seed. Tokens + CSS + Lit web components. Never — this is the canonical shape.
Distribution pnpm workspace, or git+ssh install from tags. An external collaborator needs read access without cloning.
Visual regression Playwright over examples/showcase/ and examples/whynot-control/. The system has >40 components or >3 consuming apps.
Dependency updates Manual pnpm up once a week. More than two consuming repos.
Release notes Hand-edited CHANGELOG.md. More than two contributors.
Per-framework adapters Django partials only, for the 45 most-used components. Another non-React, non-Django consumer appears.

This staging is exactly the "low-cost learning first" posture in whynot-control/OPERATING_MODEL.md. A design system with one consumer and one author does not need Chromatic. A design system with five consumers and three authors absolutely does.


9. First-week checklist

For whoever is bootstrapping this repo right now:

  • Push the seed contents to gitea.example.com/whynot/whynot-design.
  • Tag v0.2.0 immediately so consumers can pin.
  • Add the repo as a remote dependency in one consuming app (the Django one) and verify imports work end-to-end. Follow MultiFrameworkSupport.md §Django.
  • Open one trivial PR against whynot-design (e.g. a CHANGELOG typo) to confirm CI passes end-to-end.
  • Record this bootstrap in whynot-control/DECISIONS.md as DEC-004 — "Established whynot-design as the implementation surface, three-layer architecture, Lit web components as the canonical component layer."
  • Update whynot-control/SCOPE.md to mention whynot-design as a sibling.

That's it. Anything more is over-engineering for the current stage.


A design system can be interesting and still be parked. whynot-design exists to reduce visual uncertainty across prototypes, not to create more obligations.