Skip to content

Make WebJs UI and the AI design guidance first class, grounded in Refactoring UI #1116

Description

@vivek7405

Problem

WebJs teaches agents how to apply styles mechanically and teaches them nothing about design judgment.

references/styling.md (208 lines) is an excellent mechanics reference: Tailwind-first, the light-DOM tag-prefix invariant, the light-dark() token pattern, focus rings, host display: block, even grids, position: fixed headers. Every rule in it answers "how do I make this class list work in WebJs". Not one answers "what should this screen look like". There is no guidance on hierarchy, spacing rhythm, type scale, measure, palette construction, elevation, empty states, or the action pyramid.

@webjsdev/ui has the same shape of gap. It ships 32 shadcn-parity primitives with correct ARIA and a clean two-tier architecture, but the design system underneath is thin:

  • --primary is near-black (oklch(0.205 0 0)), so an app that never themes it has no primary color at all. Every button is grey.
  • The 7 base colors (neutral, stone, zinc, mauve, olive, mist, taupe) are all neutrals. There is no accent color story, and no semantic scale beyond a single --destructive. No success, no warning, no info, and no shades of any of them.
  • There is no elevation system. Shadows appear ad hoc per component (shadow-xs on the outline button) with no scale and no z-axis semantics.
  • There is no type scale, no spacing rhythm, no density variants, no empty-state primitive, no stat/metric primitive, no page-header.

The observable result: an agent building a WebJs app produces generic shadcn defaults with flat hierarchy. Everything competes for attention, labels are printed as Label: value, secondary actions look like primary ones, and empty states are blank. AGENTS.md deliberately says "There is no design gate and no placeholder gate: taste is the agent's job, not the checker's". That is the right call, and it is exactly why the guidance has to be strong: nothing downstream will catch a bad-looking app.

Definition of done: total coverage, delivered into the user's app

Two scope rules govern this task. Both are absolute, and neither is satisfied by a partial or "highlights" implementation.

Rule 1: every single principle in the source is covered, none dropped

The target is not a good summary. It is complete coverage of all 50 chapters across the book's 8 parts, plus the companion volumes. An agent building an app with WebJs should get the benefit of every one of them. The checklist below is the coverage contract, and the implementing agent ticks each line only once that principle is actionable somewhere an app-building agent will actually read it. Nothing may be silently dropped as "too subtle" or "not applicable", and anything genuinely inapplicable to WebJs must be recorded with the reason rather than quietly omitted.

Starting from Scratch

  • Start with a feature, not a layout
  • Detail comes later (restated as "hierarchy must not depend on colour", per translation 3. The paper-and-Sharpie and low-fidelity-mockup advice is human-only and does not carry over)
  • Don't design too much (work in cycles, be a pessimist, ship the smallest useful version)
  • Choose a personality (font, color, border radius, language), delivered as the archetype decision table so an agent can derive a palette from the app type in one pass
  • Limit your choices (define systems in advance, design by elimination, systematize everything)

Hierarchy is Everything

  • Not all elements are equal
  • Size isn't everything (weight and color carry hierarchy, two weights, three text colors, never under 400)
  • Don't use grey text on colored backgrounds (hand-pick at the background's hue, not white at reduced opacity)
  • Emphasize by de-emphasizing
  • Labels are a last resort (no label, combined label and value, label as supporting content, when to invert)
  • Separate visual hierarchy from document hierarchy
  • Balance weight and contrast (soften heavy icons, thicken rather than darken a too-subtle border)
  • Semantics are secondary (the primary / secondary / tertiary action pyramid, and destructive actions)

Layout and Spacing

  • Start with too much white space (restated as "bias the initial value upward", per translation 3, plus the deliberate dense-UI carve-out)
  • Establish a spacing and sizing system (non-linear, no two adjacent values within 25%, base 16)
  • You don't have to fill the whole screen (shrink the canvas, think in columns, don't force it)
  • Grids are overrated (fixed sidebar, max-width over column offsets, don't shrink until forced)
  • Relative sizing doesn't scale (no em type scale, large shrinks faster, padding is not proportional to font size)
  • Avoid ambiguous spacing (more space around a group than within it, vertical and horizontal)

Designing Text

  • Establish a type scale (hand-picked over modular, px or rem, never em)
  • Use good fonts (neutral sans, 5+ weights, x-height and legibility, the system stack)
  • Keep your line length in check (45 to 75 characters, and narrow prose inside a wide container)
  • Baseline, not center
  • Line-height is proportional (to line length, inversely to font size)
  • Not every link needs a color (link-dense UIs, and hover-only ancillary links)
  • Align with readability in mind (left for prose, don't center long text, right-align numbers, hyphenate justified)
  • Use letter-spacing effectively (tighten headlines, widen all-caps)

Working with Color

  • Perceptual color over hex (the OKLCH translation of the HSL chapter, see below)
  • You need more colors than you think (greys, primaries, accents, semantic states, all with shades)
  • Define your shades up front (restated as "take Tailwind's ramp", since Tailwind's ramps already are this method's output. The manual construction procedure survives only as a fallback for a mandated brand hex)
  • Don't let lightness kill your saturation (and why OKLCH removes the workaround)
  • Greys don't have to be grey (temperature, and how it maps onto the kit's base colors)
  • Accessible doesn't have to mean ugly (4.5:1 and 3:1, flipping the contrast, colored text on colored backgrounds)
  • Don't rely on color alone

Creating Depth

  • Emulate a light source (light from above, raised and inset profiles, and don't get carried away)
  • Use shadows to convey elevation (a five-step scale, z-axis semantics, shadow as interaction feedback)
  • Shadows can have two parts (ambient plus contact, and the contact shadow fading with elevation)
  • Even flat designs can have depth (lighter is closer, solid zero-blur shadows)
  • Overlap elements to create layers (including the invisible border for overlapping images)

Working with Images

  • Use good photos (and never design against placeholders you plan to swap)
  • Text needs consistent contrast (overlay, lower image contrast, colorize with multiply, glow text-shadow)
  • Everything has an intended size (don't scale icons up or down, don't shrink screenshots, redraw favicons)
  • Beware user-uploaded content (fixed containers with cover, inset shadow over a border for bleed)

Finishing Touches

  • Supercharge the defaults (icon bullets, promoted quotes, styled links, custom checkbox and radio)
  • Add color with accent borders (card top, active nav, alert side, under a headline, layout top)
  • Decorate your backgrounds (color change, gradient within 30 degrees, low-contrast pattern, simple shapes)
  • Don't overlook empty states (illustration, emphasized call to action, and hiding inert supporting chrome)
  • Use fewer borders (shadow, background color, or spacing instead)
  • Think outside the box (dropdowns, tables, radio-as-cards)

Leveling Up

  • Look for decisions you wouldn't have made, and rebuild interfaces you admire (framed as an agent-usable habit: when a screen looks flat, name the specific de-emphasis or overlap or accent that is missing rather than restyling at random)

Companion volumes

  • Color Palettes (the primary / neutral / supporting three-role structure, and the 10-shade ramp method, expressed with Tailwind's MIT values, plus the archetype selection table that the volume's 24 curated combinations only imply)
  • Component Gallery (the variant taxonomy across all 25 families, used as the kit coverage checklist and as an idea catalogue for agents)
  • Font Recommendations (translated into an open-font pairing recommendation, since the source's picks are mostly commercial)
  • The three screencasts (their worked before-and-after refactors, especially the dashboard's move from Label: value rows to unlabelled hierarchy with status badges, right-aligned amounts, and de-emphasized meta)

Rule 2: the guidance must reach the app, not stop at the framework repo

A reference living only in this repo's .agents/skills/ benefits nobody building an app. Verify all four delivery channels actually carry it:

  1. The scaffolded app's skill. packages/cli/lib/create.js around L643-668 copies the canonical repo-root .agents/skills/webjs/ wholesale into every generated app (falling back from the prepack bundle produced by scripts/sync-scaffold-skill.mjs). So a new references/design.md propagates for free by cp, but only if SKILL.md's routing table lists it. An unrouted file in that directory is a file no agent opens. This is the single most important delivery step, because this copy is what Claude Code, Cursor, Copilot, and Gemini read inside the user's own project.
  2. The component source copied into the app. webjsui add copies a component's .ts into the app's components/ui/, headers and all, so the per-helper Design JSDoc blocks travel into the user's repo and are read at the call site.
  3. The theme tokens. webjsui init writes the tokens into the app's stylesheet, so the elevation scale and semantic ramps become the app's own design system rather than framework trivia.
  4. The MCP knowledge layer. webjs mcp exposes docs and the ui tool. Design guidance should be reachable there too, so an agent that queries rather than reads still gets it.

The acceptance bar is therefore behavioural, not just structural: generate a fresh app, and confirm from inside that app that an agent has the design guidance in hand.

Rule 3: the token-first design system is load-bearing and must come out stronger, never diluted

WebJs promises that an app gets a design system from the very first line of code. An agent building on WebJs must never reach for bg-red-500, text-gray-600, or a literal hex. It defines tokens, then consumes them (bg-primary, text-muted-foreground, border-border, bg-destructive). That property already exists and is the thing most at risk from this task.

What exists today (protect it):

  • The scaffold writes a real token palette into app/layout.ts as light-dark(LIGHT, DARK) custom properties. See packages/cli/lib/create.js around L1219-1230, where the comment already states the intent, that token NAMES are stable and only the VALUES are the app's own.
  • public/input.css maps those names into Tailwind with @theme inline, so bg-background resolves to var(--background).
  • references/styling.md L108-146 documents the whole two-halves model, and L144 states the payoff, that theming the tokens re-skins every @webjsdev/ui helper at once.
  • The scaffold gallery is currently 100% clean. A grep across packages/cli/templates/gallery for raw Tailwind palette utilities (bg-red-500, text-slate-600, and every sibling) returns zero hits. The example an agent learns from does not drift today, and it must not start.

The gap that makes this fragile. The token system is documented descriptively, as how the machinery works. It is nowhere stated prescriptively, as a rule you may not break. Grepping the entire skill and root AGENTS.md for any mention of a raw palette utility returns nothing. There is no sentence anywhere that says "do not write bg-red-500". Today that gap is harmless because nothing tempts an agent toward raw palettes. This issue introduces exactly that temptation.

Evidence the gap already leaks, and the proof that missing tokens cause it. packages/ui/packages/registry/components/sonner.ts L150 and L153 hardcode text-emerald-500 and text-amber-500. They do that for a reason: there is no --success and no --warning token, so success and warning had nowhere else to go. Missing semantic ramps cause raw-palette drift. This makes deliverable 3 (semantic ramps) a fix for the root cause, and it makes those two lines the natural regression test.

The specific hazard this issue introduces. Refactoring UI speaks in raw hex and HSL from end to end, and its Color Palettes volume is 24 palettes of literal hex ramps. An agent that reads a naively-written design.md will conclude it should write bg-blue-600 and #2680C2. That would be a direct regression of the framework's headline promise, caused by this very task. So the color section of the new reference must open with the translation rule, before any color advice at all: every Refactoring UI color principle is expressed as an operation on tokens, never on a utility class and never on a hex literal. The book tells you what shades to create, the token system is where they live.

Refactoring UI concept How it must be expressed in a WebJs app
"You need 8 to 10 greys" A neutral token ramp defined once in the root layout, consumed as bg-muted / text-muted-foreground
"Define your shades up front" Literal values appear exactly once, in the layout's :root. Never at a call site
"Pick a primary color with 5 to 10 shades" --primary plus derived tokens via color-mix(), not blue-500 through blue-900
"Ultra-light shade as an alert background" A --<semantic>-surface and --<semantic>-foreground token pair
"Flip the contrast for accessibility" The same surface and foreground token pair, not a hand-picked utility
"Greys don't have to be grey" The kit's base-color temperature choice (stone is warm, zinc is cool), applied to the token values
"Define a fixed set of shadows" --shadow-raised through --shadow-modal elevation tokens, not per-component shadow-lg
"Establish a spacing and type scale" Tailwind's default scales, which already are these scales. Never an arbitrary p-[13px]

The framing that keeps the promise intact. The new guidance must present Refactoring UI as how to build a good design system, and the token layer as where that system lives, so the two reinforce rather than compete. Concretely, the reference should teach the sequence: choose a personality, build the token palette by the book's shade method, then build every screen out of those tokens, and never bypass the system at a call site. That sequencing turns the book into the reason the token system exists, which is stronger than what is documented today.

A monorepo-internal test asserting our own registry and gallery source contain no raw palette utilities is in scope and appropriate. Adding such a rule to webjs check is not, per the landmine below, because that would police user code where a sensible app might legitimately differ. Note the overlap with #1064 (design-system consistency across monorepo apps).

Rule 4: express every principle in the actual Tailwind v4 API, never as raw numbers

The guiding formulation for this whole task: the Tailwind API is the vocabulary, Refactoring UI is the reasoning. The book supplies the judgment (what to emphasize, what to de-emphasize, when a group needs more space, which shadow means which elevation). Tailwind supplies the words the agent actually types. A reference that states "use a 24px gap" has failed, because the agent then has to translate, and translation is where arbitrary values like p-[13px] get invented.

This is not just a formatting preference. The book's systems are already Tailwind's defaults, verifiably. Measured against the installed tailwindcss@4.3.0 theme.css:

  • The type scale is an exact match. --text-xs through --text-7xl compute to 12, 14, 16, 18, 20, 24, 30, 36, 48, 60, 72 px. The book's recommended scale is 12, 14, 16, 18, 20, 24, 30, 36, 48, 60, 72. Identical, in order, with no gaps.
  • The spacing scale maps cleanly. --spacing is 0.25rem with dynamic multiples, so the book's 4 8 12 16 24 32 48 64 96 128 192 256 384 512 640 768 is exactly p-1 p-2 p-3 p-4 p-6 p-8 p-12 p-16 p-24 p-32 p-48 p-64 p-96 p-128 p-160 p-192. Every value is a plain utility.
  • "Line-height is inversely proportional to font size" is already implemented. Tailwind pairs a line-height with every size via --text-*--line-height, running from about 1.33 at text-xs down to exactly 1 at text-5xl and above. An agent that just uses text-* gets the book's rule for free, and only needs leading-* to adjust for line length.
  • "Shadows can have two parts" is already implemented. --shadow-sm through --shadow-xl are each an ambient shadow plus a tighter contact shadow, and --shadow-2xl is a single shadow with no contact part. That is precisely the book's rule that the contact shadow fades as elevation rises. The elevation deliverable therefore shrinks substantially: it is mostly semantic naming over Tailwind's existing values plus the dark-mode inversion, not inventing a shadow scale.
  • The measure rule has a utility. --max-width-prose is 65ch, sitting in the middle of the book's 45 to 75 character range, so "keep your line length in check" is max-w-prose.
  • Inset depth has utilities. --inset-shadow-2xs through --inset-shadow-sm cover the book's well components, inset form controls, and the "prevent background bleed on user-uploaded images with an inner shadow rather than a border" advice.

The reference must therefore be written in class names and @theme namespaces throughout, with the px values mentioned only as background where they explain the reasoning. Verify every utility name against the installed version while writing, since v4 renamed things (gradients are bg-linear-* and bg-radial-* now, with bg-gradient-to-* kept as a legacy alias, and text-shadow-* is new in v4).

Principle What the agent types
Type scale text-xstext-7xl. Never an arbitrary size
Line-height by font size Automatic from text-*. Do not override without a reason
Line-height by line length leading-normal for narrow, leading-relaxed or leading-loose for wide, leading-tight or leading-none for display
Measure of 45 to 75 characters max-w-prose, or a --container-* value for mixed content
Spacing and sizing scale p-* / m-* / gap-* / space-y-* on the --spacing base. Never p-[13px]
More space around a group than within it Inner gap-2 or space-y-2, outer gap-8 or space-y-8. State both
Two weights only font-normal or font-medium for body, font-semibold or font-bold for emphasis. Never font-light for UI text
Three text tiers text-foreground, text-muted-foreground, and a third softer token
Never grey text on colored backgrounds A --<name>-foreground token or color-mix(). Never text-white/60
Tighten headlines, widen all-caps tracking-tight on display, uppercase tracking-wide or tracking-widest on labels
Elevation scale Semantic --shadow-* names in @theme, consumed as shadow-smshadow-2xl
Inset and wells inset-shadow-xs / inset-shadow-sm
Border radius personality One --radius, from which the kit derives --radius-sm--radius-3xl
Fixed sidebar, fluid main shrink-0 w-64 beside flex-1 min-w-0. Never w-1/4
Constrain width, do not shrink early max-w-* plus mx-auto. Never a column offset
Right-align numbers in tables text-right tabular-nums
Baseline-align mixed sizes flex items-baseline
Text over an image bg-black/40 overlay, backdrop-brightness-*, or text-shadow-lg as a glow
User-uploaded images object-cover with aspect-*, plus inset-shadow-sm against background bleed
Colorize an image grayscale plus mix-blend-multiply over a tinted surface
Overlapping layers relative z-* with a negative margin, and ring-4 ring-background as the invisible border
Accent borders border-t-4 border-primary and siblings
Subtle background gradient bg-linear-to-b between two close hues
Use fewer borders Prefer shadow-* or a bg-muted surface over adding border

Design / approach

Ground both surfaces in Refactoring UI (Adam Wathan and Steve Schoger). This is the right source for WebJs specifically because Wathan also wrote Tailwind, so the book's systems (the spacing scale, the type scale, the shade methodology) are the ones Tailwind's defaults already encode. Encoding the book is not bolting a foreign design language onto Tailwind, it is documenting the reasoning Tailwind was built to express.

I read the full local copy at ~/Documents/Projects/refactoring-ui: the 218-page book, the 152-page Color Palettes volume (24 palettes plus a 300-swatch master file across 30 families), the 86-page Component Gallery (a variant taxonomy across 25 component families), the 69-page Font Recommendations, the 200-icon set, and the three screencasts (Designing a Dashboard, Designing a Complex Form, Designing Content). Everything below is distilled from that material, translated into WebJs and Tailwind v4 terms.

Licensing constraint, read this first

Refactoring UI is a paid, proprietary product. The icon license explicitly forbids redistribution and forbids distributing modified versions. Therefore:

  • Do not copy book prose verbatim into the repo. Encode the principles in our own words. Principles are not copyrightable, the expression is.
  • Do not vendor the 200-icon set, the .sketch file, or the sample CSS into @webjsdev/ui or the scaffold.
  • Do not paste the proprietary hex/HSL palette tables. We do not need them: Tailwind v4's own oklch palette is the successor to that work and is MIT. Where a concrete palette is needed, derive it with the documented method or take Tailwind's.
  • The numeric scales (spacing 4 8 12 16 24 32 48 64 96 128 192 256 384 512 640 768, type 12 14 16 18 20 24 30 36 48 60 72) are already Tailwind's published defaults and are safe to state as such.
  • On whether the source's palettes are just Tailwind's: measured, they are close relatives but not the same set. See "Palette selection" below for the numbers. The practical upshot is that Tailwind's MIT palette fully covers our needs, so the licensing question never has to be tested.
  • Cite the book as recommended reading. Do not reproduce it.

Three translations the guidance must make (do not transcribe the book blindly)

  1. OKLCH replaces the HSL advice. The book's "Ditch hex for HSL" chapter, and its two workaround chapters ("Don't let lightness kill your saturation", "Use perceived brightness to your advantage" via hue rotation), exist because HSL's lightness is not perceptual: yellow and blue at the same HSL lightness look wildly different, and saturation collapses near the lightness extremes. OKLCH fixes this at the color-space level. Equal L means equal perceived lightness across hues, and chroma stays meaningful at the extremes. The ui kit and Tailwind v4 are already oklch. So the guidance keeps the goals (perceptual thinking, defined shades, temperature-tinted greys, contrast flipping) and replaces the mechanics (manual saturation bumps, hue rotation for brightness) with "use oklch and vary L while holding C and H". This is the single highest-value edit in the whole task, and getting it wrong (transcribing the HSL hue-rotation trick as-is) would actively make the guidance worse.

  2. Dark mode inverts the depth model. The book is entirely light-UI and predates dark-first design. Its depth chapters say "lighter feels closer, darker feels further" and lean on shadows. In a dark theme a shadow against a dark background is nearly invisible, so elevation is communicated by lighter surfaces, and the shadow scale becomes a secondary cue. The ui kit ships light and dark, so the guidance must state both models and the ui elevation tokens must define both sides. This is genuine new work, not a translation.

  3. Iterative human workflow becomes a one-pass decision rule. A large fraction of the book is advice for a human iterating visually in a design tool: try a value, look at it, adjust, repeat. An agent emits a class list once and never looks at it. Advice of the shape "start with X and then adjust toward Y" is worse than useless to that reader, because the adjustment step never runs, so the agent ships the deliberately-wrong starting point. Every such chapter must be restated as a rule that produces the right answer in one pass, with the reasoning kept as the justification rather than as an instruction. Worked examples of the required rewrite:

    • "Start with too much white space and remove it" becomes the generous value IS the value. Section padding around py-16 to py-24 rather than py-6, card padding p-6 rather than p-3, and a gap one step above first instinct. A tighter value is correct only for a named dense case such as a data-dense dashboard, which is a condition to check once, not a second pass. The book's insight, that incrementally-added space only ever reaches "not actively bad", survives as the why and never as a procedure.
    • "Design in grayscale first, add color later" becomes hierarchy must not depend on color. Check that the screen still reads correctly with color removed, which is also the accessibility rule, rather than literally building a grey version first.
    • "Pick a size by trying the middle option, then the ones on either side" becomes choose from the scale, and when two adjacent steps both seem plausible, take the larger for a primary element and the smaller for a supporting one.
    • "Don't design too much, work in cycles" becomes build the smallest useful version of the feature, and do not render affordances for functionality that does not exist yet.
    • "Develop your intuition, rebuild interfaces you admire" becomes a diagnostic checklist. When a screen looks flat, name the missing thing (nothing is de-emphasized, every action looks primary, groups have equal inner and outer spacing, there is no elevation, the empty state is blank) instead of restyling at random.

    The implementing agent must audit every chapter for this shape, not only the ones listed. A useful test for each line of the finished reference: could an agent act on this correctly in a single pass, with no opportunity to look at the result? If not, rewrite it until it can.

    The banned phrasings. Run a final audit over the finished reference and remove every instance of these, because each one either cannot be executed or silently invites an arbitrary value. This list is the concrete form of the single-pass test:

    Failure mode Example wording to remove What it must become
    Iterative "start with X and remove", "keep adjusting until" A biased starting value that is correct as-is
    Deferred judgment "by eye", "by hand", "trust your judgment", "it's not a science" A named token, class, or ordered default
    Experimentation "try a shadow, or a background, or spacing" An ordered preference with a stated tie-break
    Human-only activity "browse font directories", "count the weights", "look at sites you admire", "sketch on paper" A named default, or dropped with the reason recorded
    Raw numbers where a utility exists "use a 24px gap", "line-height 1.5" gap-6, leading-normal
    Unsourceable assets "use good photos", "hire a photographer" Never emit a placeholder or stock URL. Use real assets or design without imagery
    Vague quality talk "make it feel polished", "add some flair" The specific mechanism (accent border, elevation step, de-emphasis)

    Note that the reasoning behind a banned phrasing is usually worth keeping. Demote it to a justification sentence so the agent understands why the rule holds, and keep the rule itself executable.

What to encode: the full distilled body of guidance

This is the content inventory for the new reference, and it is the expanded form of the coverage checklist above. Every bullet is a rule an agent can act on. Where this section is terser than the checklist, the checklist wins: the checklist is the contract.

Hierarchy (the highest-leverage chapter, put it first)

  • Not all elements are equal. Deliberately de-emphasizing secondary and tertiary content makes a screen look "designed" without changing the palette, font, or layout.
  • Size is not the hierarchy tool. Carry it with weight and color instead, so primary text stays a reasonable size and secondary text stays readable. Three text colors (foreground, muted-foreground, and a third softer tier), two weights (400/500 normal, 600/700 emphasis). Never a weight under 400 for UI text.
  • Never grey text on a colored background. The effect that works on white is reduced contrast, not greyness. White at reduced opacity looks washed out and lets the background bleed through. Hand-pick a color at the background's hue instead. In Tailwind terms that is color-mix(in oklab, var(--primary-foreground) 70%, var(--primary)), not text-white/60.
  • Emphasize by de-emphasizing. When the focal element will not pop, soften what competes with it rather than shouting louder. A sidebar that fights the main content should lose its background, not gain one.
  • Labels are a last resort. Do not render Client: Maria Schmidt / Amount: $3,400 / Status: Pending. Format and context usually identify the data on their own ($19.99 is a price, (555) 765-4321 is a phone number). When a label is genuinely needed, fold it into the value (12 left in stock, not In stock: 12), and when it must stand alone treat it as supporting content, smaller and softer than the data. Invert this only on spec-sheet pages where the user scans for the label ("depth"), not the value ("7.6mm"). This one rule is the single biggest visual difference between the before and after in the Designing a Dashboard screencast.
  • Separate visual hierarchy from document hierarchy. Pick heading elements for semantics and style them freely. Section titles usually act as labels, so they are usually small. Sometimes they should be visually hidden entirely.
  • Balance weight and contrast. Icons cover more surface area than text, so an icon beside a label reads as emphasized. Lower the icon's contrast to rebalance. Inversely, a 1px border that is too subtle should get thicker, not darker.
  • Semantics are secondary: the action pyramid. One primary action per page (solid, high contrast), a couple of secondary actions (outline or low-contrast fill), and tertiary actions styled as links. Destructive does not mean big and red. If deleting is not the page's primary action, give it a secondary or tertiary treatment and save the red primary button for the confirmation step, where deleting is primary. This maps directly onto buttonClass({ variant }) and belongs in that helper's header.

Layout and spacing

  • The generous spacing value is the value, not a starting point. Section padding at py-16 to py-24, card padding at p-6, and a gap one step above first instinct. A tighter value is correct only for a named dense case such as a data-dense dashboard, checked once as a condition. (Restated per translation 3. The book's "start with too much and remove it" is an iterative instruction an agent cannot execute. Its insight, that incrementally-added space only ever reaches "not actively bad", survives as the justification.)
  • Use a non-linear spacing and sizing scale, where no two adjacent values are closer than about 25%. Base 16. 4 8 12 16 24 32 48 64 96 128 192 256 384 512 640 768, which is Tailwind's default scale.
  • Ambiguous spacing is a bug. Whenever grouping is carried by space rather than a border or background, there must be more space around a group than within it. Applies to label/input pairs, headings above their section, list items, and horizontal groups.
  • Do not fill the whole screen. Give an element the width it needs. Constrain with max-width, not a percentage of a grid.
  • Grids are overrated. A sidebar wants a fixed width optimized for its contents with the main area flexing, not 25% that grows uselessly wide and shrinks below usable. Do not shrink an element until the viewport forces it.
  • Relative sizing does not scale. Large elements shrink faster than small ones on narrow screens, so the ratio between headline and body is not constant. Do not define the type scale in em. Padding should not scale proportionally with font size either: a large button wants disproportionately more padding and a small one disproportionately less, which is why the ui kit's SIZES are hand-tuned rather than computed.
  • Design mobile-first at a real narrow canvas (~400px), then expand. Split a narrow component into columns instead of stretching it.

Text

  • Size from text-xs through text-7xl only. Never an arbitrary size, and never em for the scale (nesting compounds it off-scale). Tailwind's --text-* is exactly the book's recommended scale, so there is nothing to compute.
  • Constrain prose with max-w-prose (65ch), which sits inside the book's 45 to 75 character range. Keep prose narrow even when its container is wide.
  • Baseline-align mixed font sizes on one line with flex items-baseline, never center-align them.
  • Line-height falls out of text-* automatically, since Tailwind pairs a line-height with every size. Override with leading-* only for line length: leading-normal at normal measure, leading-relaxed or leading-loose for wide columns.
  • Not every link needs a color. In a link-dense UI use font-medium or a darker token, and give genuinely ancillary links hover:underline with no resting treatment.
  • Left-align prose. Center at most two or three lines, and shorten the copy rather than centering a long block. Right-align numeric table columns with text-right tabular-nums.
  • tracking-tight on display sizes, uppercase tracking-wide or tracking-widest on all-caps labels.
  • Font choice: default to the system stack (font-sans), which is the book's own safe recommendation and needs no network request. When the archetype calls for a distinct face, name a specific open font rather than describing how to browse for one, and state the weights to load. The book's "count the available weights" and "look at what sites you admire use" are human research activities, not instructions an agent can execute.

Color

  • Do not hand-build a shade ramp. Take Tailwind's. Tailwind's ramps are already the output of the book's method, built in oklch with even lightness steps and chroma tapered toward the ends, which is the whole point of the "define your shades up front" and "don't let lightness kill your saturation" chapters. Pick the family, use 50 through 950. The manual construction procedure (base first, then the edges, then fill the gaps) belongs in the reference only as a fallback for a mandated brand hex, and even then the rule is to anchor on the nearest Tailwind ramp and shift hue, not to build eleven shades from nothing. Any wording resembling "adjust it by eye" is unusable to an agent and must not appear.
  • You need far more colors than a five-swatch generator gives you. Greys (the majority of any UI), one primary, plus semantic states (success, warning, danger, info). Each is a full ramp, which is exactly what a Tailwind family gives you.
  • Define shades up front, meaning the ramp is chosen once and referenced by token thereafter. Never derive shades at a call site with lighten()/darken() or an opacity modifier standing in for a real shade.
  • Greys do not have to be grey. Saturating a neutral toward blue reads cool, toward yellow or orange reads warm. This is precisely what the kit's BASE_OVERRIDES already do (stone is warm, zinc is cool), which the guidance should say out loud so agents pick a temperature deliberately.
  • Accessible does not have to mean ugly. 4.5:1 for body text, 3:1 for large. When white-on-color forces a background so dark it steals focus, flip the contrast: dark colored text on a light tinted background of the same hue. That is the correct default for badges, alerts, and status pills, and it is what the Designing a Dashboard screencast lands on.
  • Never rely on color alone. A red or green delta needs an arrow or a word too. For series in a chart, prefer varying contrast over varying hue. Cross-link the existing dataviz skill here rather than duplicating it.

Depth and elevation

  • Light comes from above, so a raised element is lighter on top and casts below, and an inset element is the mirror image. In practice that is inset-shadow-xs for a raised lip and inset-shadow-sm for a well, never a translucent white overlay (which desaturates the surface beneath). Where a lighter edge colour is needed it comes from a token or color-mix(), not a hand-picked value.
  • A five-step elevation scale, mapped to z-axis semantics rather than picked per component: resting control, button, dropdown, popover or card, modal. In practice that is shadow-xs, shadow-sm, shadow-md, shadow-lg, shadow-2xl behind semantic --shadow-* names. Higher elevation attracts more focus, so the choice is a hierarchy decision, not decoration.
  • Shadows have two parts, a soft ambient one plus a tight contact one, and the contact part fades as elevation rises. State this as the reason the scale is ordered, not as values to author: Tailwind's --shadow-sm through --shadow-xl already ship both parts and --shadow-2xl already drops the contact part.
  • Shadows are also interaction feedback. Raise on drag, flatten on press.
  • Flat design still has depth, via lighter-is-closer surfaces and solid zero-blur offset shadows.
  • Overlap elements across a background transition to create layers. Give overlapping images an "invisible border" matching the page background so they never clash.
  • In dark mode, invert all of this: elevation reads as a lighter surface, and shadows do secondary work.

Images

  • Never emit a placeholder or stock image URL. This is the agent-facing form of "use good photos", and it matters more than the original chapter. An agent must not invent an Unsplash, placehold.co, picsum, or similar URL, because those either break, hotlink someone else's bandwidth, or ship a fake asset into a real product. Use the app's own assets, or design the section to work without imagery (a token-coloured surface, an accent border, an icon in a filled shape). The book's own advice, that designing against placeholders you intend to swap never works, points the same way. This rule is not in the source and must be added.
  • Text over an image needs consistent contrast, and the fix is the image, not the text. In order: a bg-black/40 overlay, backdrop-brightness-* on the overlay, colorizing with grayscale plus mix-blend-multiply over a tinted surface, or text-shadow-lg used as a glow rather than an offset shadow.
  • Everything has an intended size. Do not scale a 24px icon to 4x (wrap it in a filled shape and keep the icon at its native size). Do not shrink a full screenshot into a small slot (crop it, capture at a narrower viewport, or draw a simplified version). Redraw a favicon at its target size rather than downscaling a logo. That last one is live in this repo: seo: rank first for "what is webjs" across site, repo, and Pages #1088 records the favicon declared as 32x32 on a 512px asset.
  • User-uploaded images go in a fixed container with object-cover plus an aspect-* ratio. Prevent background bleed with inset-shadow-sm rather than a border, since a border clashes with the image's colours and a shadow disappears.

Finishing touches (the cheapest wins, and the ones agents skip)

  • Supercharge the defaults: icon bullets instead of discs, promoted quote glyphs, brand-colored custom checkbox and radio states, styled links.
  • Accent borders. A colored rule across the top of a card, beside an alert, on the active nav item, under a headline, or across the top of the layout. Zero graphic-design talent required, large perceived payoff.
  • Decorate backgrounds: alternate section background colors, a subtle gradient between hues no more than 30 degrees apart, a low-contrast pattern, or a simple geometric shape.
  • Empty states are a feature, not an afterthought. They are the user's first interaction with any content-driven screen. An empty state gets an illustration or icon, a clear explanation, and an emphasized call to action, and it hides the supporting chrome (tabs, filters, sort controls) that does nothing until content exists.
  • Use fewer borders, stated as an ordered default rather than a suggestion to experiment. Separate with a bg-muted or bg-card surface change first, a shadow-* second, extra gap-* third, and reach for border only when none of those apply, such as a table rule or an input outline.
  • Think outside the box. A dropdown can be multi-column with sections and icons. A table can merge related columns, carry avatars and status badges, and right-align its numbers. A radio group can be selectable cards.

Process (belongs in the skill's "starting a UI task" framing). Every line here is already in one-pass form, per translation 3. None of it may be restored to the book's original iterative wording.

  • Build the feature's own screen before the app shell. Navigation and chrome cannot be decided until real screens exist, so an agent asked for "an app" starts with a working feature rather than a nav bar.
  • Hierarchy must not depend on colour. The screen has to read correctly with colour removed, which is also the accessibility rule. This is what "design in grayscale first" means for a reader who cannot iterate, and it is checkable in one pass.
  • Build the smallest useful version, and do not render affordances for functionality that does not exist. No dead buttons, no empty tabs, no filters that filter nothing.
  • Choose a personality deliberately across all four axes together (font, colour, one border radius, and language), using the archetype table below. Never mix square with rounded in one interface.
  • Choose from the scale rather than nudging values. When two adjacent steps both look plausible, take the larger for a primary element and the smaller for a supporting one, and move on.
  • When a screen looks flat, run the diagnostic instead of restyling at random: is anything de-emphasized, does more than one action read as primary, do groups have more space around than within, is there any elevation, and is the empty state designed.

Palette selection: choosing a palette for the app being built

This is the piece that makes the design system real at line one, and it does not exist anywhere today. The book's "Choose a personality" chapter names the axes and the Color Palettes volume ships 24 curated combinations, but neither gives a decision procedure. An agent scaffolding an app knows what the app IS. It should be able to derive a palette from that, and write the token values in one pass.

Do not ship the source's hexes. Use Tailwind's palette. I compared all 300 Refactoring UI swatches against Tailwind v4's 286 in OKLab space. Median nearest-neighbour distance is 0.032, with 247 of 300 (82%) inside 0.05 which reads as clearly the same colour, and 83 of 300 (28%) inside 0.02 which is visually near-identical. Same lineage and same philosophy, since the same people made both, but not interchangeable: Tailwind's ramps are systematic in oklch with uniform lightness steps, and Refactoring UI has families Tailwind lacks (magenta, its far more saturated purple, the vivid variants, and four grey temperatures against Tailwind's five neutrals). The conclusion is that there is no reason to introduce a second palette. Tailwind's is the successor, is MIT, is already installed, and covers the space. Palette selection therefore means picking token VALUES out of Tailwind's ramps, never inventing hexes and never copying the source's.

Two independent axes, both mapping onto machinery that already exists.

  1. Neutral temperature. This is already built and unexploited: packages/ui/packages/registry/themes/base-colors.js ships 7 bases (neutral, stone, zinc, mauve, olive, mist, taupe) that differ only in hue and chroma while holding lightness, exactly the book's "greys don't have to be grey" chapter expressed as code. Nothing currently tells an agent which one to pick.
  2. Accent hue. Missing entirely, since --primary is near-black. This is what deliverable 3's primary-color story has to supply.

The archetype table. Ship a decision table keyed on what the app is, setting all four personality axes together (the book is explicit that font, color, radius, and language move as a unit, so a table that sets only colour under-delivers). Illustrative shape, to be finalized by the implementing agent:

App archetype Neutral Accent Radius Type Note
Productivity or generic SaaS (the safe default) zinc or neutral blue or indigo medium neutral sans The book's "nobody ever complains about blue"
Finance, banking, fintech slate or zinc (cool) restrained blue or indigo, low chroma small neutral sans Reserve saturation for semantic states, so numbers read as data
Developer tool, infra zinc or neutral, very low chroma one vivid accent used sparingly (cyan, violet, lime) small sans with a mono companion Dense by intent, so take the dense-UI carve-out
Enterprise or internal admin cool grey muted blue small neutral sans Dense carve-out applies
Healthcare, medical cool but soft (mist) teal or blue medium highly legible sans Raise contrast targets, and keep red exclusively for genuine alerts
Content, editorial, docs warm or neutral (stone) minimal, links only none or small serif body Measure and line-height matter more than accent here
Consumer social, creator warm or tinted (taupe, mauve) higher chroma (rose, violet, fuchsia) large friendly sans Personality carried by radius and language too
Kids, education, games warm high chroma, multi-accent large rounded sans The one archetype where multiple accents are right
Luxury, premium commerce warm (stone, taupe) very low chroma, or amber and gold none or small serif display Restraint reads as expensive
Marketplace, e-commerce neutral one confident accent for the call to action medium neutral sans Strong semantic green and red for stock and price movement

The rules that must ship with the table, because they are what agents get wrong:

  • One accent. A second brand colour is almost always a mistake. Multi-accent is correct for the kids and education archetype and essentially nowhere else.
  • Never use a semantic hue as the brand accent. A green primary collides with success, a red primary with destructive, an amber primary with warning. If the brand genuinely is green, shift the semantic ramps away from it and say so in the tokens. This is non-obvious and it silently ruins status legibility.
  • Never pure black or pure white for text or surfaces. The book is explicit and the existing scaffold already honours this.
  • One radius, applied everywhere. The kit derives every radius from a single --radius, so this is one token, and mixing square with rounded in one interface is the failure mode the book calls out.
  • Neutral temperature and accent hue should agree. A warm stone neutral with an ice-blue accent fights itself. Keep them on the same side, or make the mismatch a deliberate, stated choice.
  • When the app type is unclear, take the default row. Do not stall on the decision. That is the whole point of a constrained system.

The output is a token block, not a recommendation. The guidance must end with the agent writing concrete light-dark() token values into app/layout.ts, in the shape the scaffold already emits (packages/cli/lib/create.js around L1219-1230), plus the matching base colour for @webjsdev/ui. A palette suggestion that stops short of the token block has not honoured Rule 3.

Wire it into init. Deliverable 3's primary-color story should let webjsui init accept an accent (and ideally an archetype), so the very first command an app runs can produce a themed system rather than the all-grey default. That closes the loop between this table and the promise that an app has a design system from the first line of code.

The Component Gallery as a coverage checklist

The gallery is a variant taxonomy across 25 families: buttons, inputs, input groups, error-validation placement, badges, breadcrumbs, pagination, horizontal nav, vertical nav, tables, sign-in forms, alerts, pricing, marketing heros, marketing sections, modals, multi-section forms, header nav, preview cards, profile cards, application layouts, footers, activity feeds, checkout pages, testimonial sections.

Cross-referencing that against the kit's current 32 primitives, the notable gaps are empty-state, stat / metric card, page-header, field / input-group (with addon and error placement), description-list, and timeline / activity-feed. Those six are the ones worth shipping. Marketing sections, pricing tables, and checkout pages are app-level compositions, not kit primitives, and should stay out. The gallery's error-validation section is also worth mining directly: it enumerates seven placements for a form error, and the kit currently has no opinion on any of them.

Deliverables

  1. A new skill reference, references/design.md, carrying the guidance above. Routed from SKILL.md's reference table and marked READ FIRST when building any user-facing screen, the same treatment muscle-memory-gotchas.md gets. It must be WebJs-shaped, not generic design advice, which means it explicitly covers: how an empty state and a skeleton stay correct with JS off (progressive enhancement), that a <webjs-suspense> fallback is a design surface and not a spinner dump, that elision means a display-only decorative component costs nothing on the client so there is no perf reason to skip polish, and that the optimistic-UI pending treatment (opacity-50 in today's AGENTS.md example) is a design decision that should be systematized rather than hand-picked per call site.

  2. references/styling.md refocused. It stays the mechanics reference and gains a pointer to design.md at the top, in the shape of "styling.md tells you how to write the classes, design.md tells you which ones to write".

  3. @webjsdev/ui gains the design-system layer it is missing:

    • An elevation scale in themes/index.css (--shadow-raised through --shadow-modal, five steps, with a dark-mode surface-lightness model instead of shadows), consumed by the components that currently pick shadows ad hoc. Per Rule 4 this is semantic naming layered over Tailwind's existing --shadow-* values, which are already two-part with the contact shadow dropping at 2xl, not a new set of numbers.
    • Semantic color ramps beyond --destructive: success, warning, and info, each with a surface and a foreground, in oklch, contrast-checked. Alert and badge get variants for them.
    • A primary-color story. --primary at near-black is a defensible shadcn-parity default, but init should offer a real accent, and the docs must show how to swap it, because today an unthemed app has no brand color at all.
    • The six new primitives above (empty-state, stat, page-header, field/input-group, description-list, timeline), each as a Tier-1 class helper.
    • Every helper's JSDoc header gains a Design block beside its existing A11y block, stating the hierarchy intent (for example, on buttonClass: one default per screen, outline/secondary for the rest, link for tertiary, and destructive only where destroying is the primary action).
  4. Worked design exemplars in the scaffold, because code teaches harder than prose. Today the gallery under packages/cli/templates/gallery/app/features/ is organised by framework feature (async-render, boundaries, suspense, websockets), so every demo teaches mechanics and none teaches design. An agent pattern-matching the scaffold therefore learns nothing about hierarchy, and prose alone will not outweigh the code sitting in its context. Ship two or three complete, well-designed screens as exemplars, following the existing app/examples/todo precedent rather than adding to features/. The dashboard and settings-form archetypes are the highest value, since they are where flat Label: value output is worst. Each exemplar is densely commented with why each design decision was made, in the gallery's existing teaching style, and each must score full marks on the rubric above.

    Related and cheaper: audit the screens the scaffold already generates (the home page, the root layout, the todo example) against the rubric. If the app an agent starts from is hierarchy-flat, the guidance is fighting the code, and the code wins.

  5. Delivery into generated apps, per Rule 2 above. This is a deliverable in its own right, not a docs chore. The routing entry in SKILL.md, a generated-app verification that references/design.md is present and routed, and the MCP knowledge-layer exposure.

  6. Doc-surface sync, per the standing rule. website/app/docs/styling, website/app/docs/ui, the ui site at packages/ui/packages/website, root AGENTS.md, packages/ui/AGENTS.md, and packages/ui/README.md.

Implementation notes (for the implementing agent)

Source material. The full Refactoring UI product is at ~/Documents/Projects/refactoring-ui on this machine (book PDF, Color Palettes PDF plus 24 palette JSONs plus a 300-swatch swatches.json, Component Gallery PDF, Font Recommendations PDF, a 200-icon set, three mp4 screencasts). It is NOT in the repo and must not be committed. Extract text with pdftotext -layout. Note that all the numeric scales in the book are rendered as images, so pdftotext silently drops them; render those pages with pdftoppm -png and read them (spacing scale on p63, type scale on p91, elevation scale on p161). The distilled inventory in the Design section above is complete enough to implement from without re-reading the source.

Where to edit:

  • New file .agents/skills/webjs/references/design.md. Register it in the reference table in .agents/skills/webjs/SKILL.md and in the identical table in root AGENTS.md (both tables list the references and both must stay in sync).
  • .agents/skills/webjs/references/styling.md, add the cross-reference near the top of "What This Covers" (around L3-15) and in the Tailwind-first section.
  • .agents/skills/webjs/references/ui-kit.md, extend the inventory lists (L55-62) with the new primitives, and add a short design-intent section.
  • packages/ui/packages/registry/themes/index.css, the canonical token file. Add elevation and semantic tokens to both the @theme inline mapping block and the :root / .dark value blocks. Every token needs a mapping entry or Tailwind will not emit a utility for it.
  • packages/ui/packages/registry/themes/base-colors.js, the 7 base-color overrides. Semantic ramps go here if they vary per base color, or stay in index.css if they do not (they should not vary, keep them in index.css).
  • packages/ui/packages/registry/components/*.ts, one file per primitive. Six new files for the new primitives. button.ts is the model for the header format (the A11y (required for accessible output) block, Design tokens used, @example), and every new file must match it.
  • packages/ui/packages/registry/registry.json, the manifest. Every new component needs an entry, and registryDependencies must list lib-utils for anything using cn().
  • Scaffold copy of the skill lives under packages/cli/templates/.agents/skills/webjs/. It is a separate copy from .agents/skills/, and the webjs-scaffold-sync skill owns that propagation.

Landmines:

  • The prose-punctuation hook will block the commit. .claude/hooks/block-prose-punctuation.sh rejects em-dashes, space-surrounded hyphens or semicolons used as pause punctuation, and a colon attached to a code-shaped left-hand side. Refactoring UI's prose is full of em-dashes, so any lightly-paraphrased sentence will trip it. Write in the house voice from the start rather than fixing it up afterward. The same hook enforces WebJs capitalization in prose.
  • The phase-1 gate overrides autonomous mode. Root AGENTS.md tells an agent under bypass permissions to never block and to auto-decide. That is exactly the instinct that would blow through this checkpoint. The gate is an explicit owner request and takes precedence, so stop and wait for the evaluation result.
  • Do not add design rules to webjs check. Root AGENTS.md is explicit: webjs check is correctness-only, and "there is no design gate and no placeholder gate: taste is the agent's job, not the checker's". Design guidance is prose in the skill, full stop. The dividing line is "could a sensible app legitimately want this to pass", and for every design rule here the answer is yes.
  • Do not transcribe the HSL chapters. See the OKLCH translation above. Copying the hue-rotation-for-brightness trick into an oklch codebase would be actively wrong advice.
  • Do not restate the book's numbers as numbers. See Rule 4. The book's type scale is byte-for-byte Tailwind's --text-*, its spacing scale is Tailwind's --spacing multiples, and its two-part-shadow rule is already how Tailwind's --shadow-* are built. Writing "use a 24px gap" instead of gap-6 forces the agent to translate, and translation is where p-[13px] comes from.
  • The book's raw hex will regress the framework's headline promise if transcribed. See Rule 3. Refactoring UI names literal colors on nearly every page, and its Color Palettes volume is 24 palettes of hex ramps. A design.md that carries that register teaches agents to write bg-blue-600, which breaks the "design system from the first line of code" property that the scaffold, the token layer, and @webjsdev/ui exist to deliver. Every color statement in the reference is a statement about tokens.
  • Iterative advice is unusable to the reader. See translation 3. The book assumes a human who looks at the result and adjusts. The reader here does not. An un-rewritten "start with too much and remove it" makes agent output worse, not better, because only the first half executes.
  • Do not vendor the icons. The license forbids redistribution and forbids distributing modified versions. If the kit needs icons, that is separate work with an MIT set.
  • --radius derivation. themes/index.css derives --radius-sm through --radius-3xl from a single --radius with multipliers. The book's "border radius is a personality decision, stay consistent" advice maps onto that one variable, which is a nice thing to document.
  • Dark mode is not optional verification. Per references/styling.md, light mode passing proves nothing about dark. The elevation work in particular is meaningless until checked in a real dark-theme browser.
  • Theme tokens are self-healing. webjsui add re-writes missing tokens, so a new token must be added to the canonical source and not only to a test fixture, or add will silently strip it back out.
  • Light-DOM invariants still apply to anything new: tag-prefixed selectors if raw CSS is unavoidable (invariant 7), and never interpolate into a <style> body in a component.

Tests and doc surfaces:

  • packages/ui/test/registry-contents.test.js and registry-deps.test.js assert manifest and file consistency, so new components must be registered there.
  • packages/ui/test/registry-composer.test.js covers the base-color composition path, relevant if semantic ramps end up in base-colors.js.
  • Browser-layer coverage for any new primitive that renders interactive markup, per the AGENTS.md testing rule (unit alone is necessary but not sufficient for browser-facing work).
  • Doc surfaces per the webjs-doc-sync skill: website/app/docs/styling/page.ts, website/app/docs/ui/, packages/ui/packages/website/app/docs/components/, packages/ui/README.md, packages/ui/AGENTS.md, root AGENTS.md.
  • Scaffold surfaces per the webjs-scaffold-sync skill, including generate + boot + webjs check verification of a freshly created app.

How to work this issue: one branch, one PR, with a human gate in the middle.

It is long because the implementer is cold and the coverage contract is explicit. Read it fully once, then work phase by phase, ticking the coverage checklist as you go rather than from memory at the end.

This ships as a single branch and a single PR, not per-phase PRs. Open the PR as a draft immediately after the first push, commit each phase as its own logical commit, and narrate each pushed phase as a PR comment so the work is resumable if the session dies. Call gh pr ready only once every phase is done and the gate below has passed.

The gate, after phase 1. Phase 1 is the guidance plus the evaluation harness and nothing else. When it is pushed, post the phase-1 status as a PR comment and STOP. The repo owner runs the before-and-after evaluation personally and confirms the result before phases 2 onward are committed to the same branch.

Why gate here specifically, rather than just at the end: at this point the branch contains only the guidance, with no token changes, no new primitives, and no exemplar screens. That makes it the cleanest possible experiment, isolating the effect of the guidance itself. Measured only at the end, an improvement could equally be explained by the new tokens or the exemplar screens, and a null result would be unattributable.

This is an authorized exception to autonomous mode. Root AGENTS.md says that under bypass or sandbox permissions an agent must never block on questions and should auto-decide instead. That rule does not apply to this gate, which is a deliberate human checkpoint requested by the repo owner. Stop and wait even when running autonomously.

Phasing. Phase 1 is not "the easy part first", it is the phase that satisfies the coverage contract, so it is the one that must not be trimmed:

  1. references/design.md covering all 50 chapters plus the companion volumes, plus the SKILL.md / AGENTS.md routing, the styling.md cross-reference, and a generated-app check that it lands. If the file grows past a comfortable single read, split it by part (hierarchy, layout, text, color, depth, images, finishing) and route the parts from SKILL.md, rather than cutting content. Also in phase 1, because the gate needs it: the three committed prompt files and the scripts/eval-design.mjs machine-check script with its counterfactual. The harness cannot be deferred to the end, since the gate depends on it.
  2. GATE. Stop here. Post the PR comment, wait for the owner's evaluation result and explicit go-ahead. If the result is negative, the fix is this phase (wrong content, too long, badly routed), not the later phases.
  3. The @webjsdev/ui token layer (elevation, semantic ramps, primary-color story).
  4. The six new primitives.
  5. The per-helper Design JSDoc blocks.
  6. The scaffold design exemplars, and the audit of the screens the scaffold already generates.
  7. MCP exposure, docs-site, website, and remaining sync.
  8. The final characterization run. The gate measured guidance alone, so re-run the machine checks across all three prompts plus one visual pair to characterize what actually ships. Post the numbers on the PR before marking it ready.

How we will know it worked (mandatory, and the real definition of done)

Every other criterion in this issue checks that an artifact exists. None of them check that generated UI got better. It is entirely possible to ship this issue with every box ticked and change nothing about what an end-user agent produces. That would be a failed implementation, so the outcome test below is the one that actually decides.

Measured at two points, and the first one is a hard stop. The gate run happens right after phase 1, when the branch carries the guidance and nothing else, and it is executed by the repo owner rather than the implementing agent. That removes the "someone has to run it honestly" weakness entirely, because the party checking the result is not the party being evaluated. The final characterization run happens before the PR is marked ready, and exists because the tokens, primitives, and exemplars added after the gate change what ships, so the gate's numbers no longer describe it.

The implementing agent's job is therefore to build the harness and get out of the way: commit the prompts and the script, generate nothing self-serving, and hand the owner a procedure that runs in a few commands.

The outcome, stated so it can be falsified. An agent building an app with WebJs, given an ordinary product prompt and no design hints, produces on its first attempt a screen with visible hierarchy, a coherent token palette, semantic elevation, a designed empty state, and zero raw palette utilities.

The protocol, step by step

Three fixed prompts matching the three screencast subjects, since they span the space well: a data dashboard, a multi-section settings form, and a content or marketing page. Commit the prompt text verbatim so the run is reproducible, and put no design guidance in them.

Step 1. Two checkouts. Per the repo's one-task-per-worktree rule, take a main worktree for the "before" side and use the feature worktree for "after".

git worktree add ../webjs-eval-before origin/main

Step 2. Generate an app from each side's CLI. This is what makes the comparison valid: create.js copies the skill from that checkout's canonical .agents/skills/webjs/ at create time (around L648-650), so the "before" app has no design guidance and the "after" app has it, with nothing else differing.

node ../webjs-eval-before/packages/cli/bin/webjs.js create eval-before-dashboard
node ./packages/cli/bin/webjs.js                  create eval-after-dashboard

Step 3. Run the same prompt in each app, in a fresh headless session. A fresh session is essential, since an agent that saw this issue or the implementation conversation is contaminated. It must work only from the app's own AGENTS.md and skill.

cd eval-before-dashboard
claude -p "$(cat ../prompts/dashboard.txt)" --model <same-model-both-sides> --permission-mode acceptEdits

Repeat for each of the three prompts, on both sides. Run each cell at least three times. Model output varies, so a single generation per side is not evidence, and reporting one run invites accidentally comparing a lucky "after" against an unlucky "before". Report every run, not the best one.

Step 4. Split the rubric into machine-checked and visual. This is what turns the evaluation from a claim into a check.

Machine-checked, by a committed script (scripts/eval-design.mjs or a test) that takes a generated app directory and emits a score, so anyone can re-run it and get the same answer:

Rubric line How the script checks it
Zero raw palette utilities grep for (bg|text|border|ring)-(red|blue|slate|gray|…)-[0-9]{2,3}, same pattern already used to verify the gallery is clean
Zero literal hex outside the token block grep #[0-9a-fA-F]{3,8} in app/** and components/**, allowing only the layout's :root
Zero arbitrary values grep for -\[[0-9.]+(px|rem)\]
On-scale spacing and type every p-* / gap-* / text-* resolves to a real theme step
Table numbers numeric columns carry text-right and tabular-nums
Measure constrained prose containers carry max-w-prose or a --container-* cap
Empty state exists the list-rendering path has a zero-length branch that is not an empty fragment
Works with JS off fetch the SSR HTML with no JS and assert the content is present
Dark mode defined the token block covers both sides, and dark renders without a missing-token fallback

Visual, judged from the screenshot pair and therefore requiring both sides' screenshots in the PR:

Rubric line What to look for
One primary action Exactly one action reads as primary. Secondary and tertiary are visually distinct
Three emphasis tiers Present and carried by weight and colour, not font size
Group spacing Every group has more space around it than within it
Elevation Comes from the scale and maps to z-axis meaning, not per-component guesswork
No Label: value Data is not rendered as label-colon-value rows
Chrome hidden when inert Tabs, filters, and sort controls are not shown doing nothing

Step 5. Boot both apps and capture the evidence.

(cd eval-before-dashboard && npm run dev -- --port 3001)
(cd eval-after-dashboard  && npm run dev -- --port 3002)

Screenshot each in light and dark, plus one JS-disabled load, using the repo's configured Playwright or chrome-devtools MCP. Six screenshots per prompt pair.

What makes the result un-fakeable

The weak point of any outcome test is that it can be asserted rather than performed, so the PR must carry artifacts a reviewer can check independently:

  • The prompt files, committed verbatim.
  • The machine-check script, committed and re-runnable by anyone against either app directory. Its output is a number, not a judgment.
  • Both sides' screenshots, not only the flattering ones. You cannot claim an improvement while showing the "before" images.
  • The generated source for at least one screen per side, committed as a fixture, so the machine checks can be re-run later and the claim survives the branch being deleted.
  • Every run's score, since selective reporting is the realistic failure mode here, well ahead of outright fabrication.

If the "after" side does not beat "before", say so and treat it as the finding. A negative result means the guidance is wrong, too long, or badly routed, and that is far more useful than a passed checkbox.

The negative check, equally important. The mechanics guidance must not be crowded out. In the same "after" runs, confirm the agent still gets the light-DOM tag-prefix rule, the global focus ring, position: fixed headers, and the server-import boundary right. If design guidance displaced mechanics guidance, the reference is too long or badly routed, and the fix is splitting and sharpening the trigger rows, not deleting mechanics.

Ship the before-and-after screenshots and the scored rubric in the PR. A PR without them has not demonstrated the goal.

Context budget and routing, because this can fail by being too big

SKILL.md explicitly tells agents to "load the smallest useful reference set" and warns that "loading more than two or three at once usually means the task is not narrowed yet". A single 50-chapter design reference fights that instruction directly. Two failure modes follow, and both produce a worse agent than today:

  • The reference is so large that agents skip it, and nothing changes.
  • The reference is loaded and crowds out styling.md and components.md, so mechanics regress.

So the routing design is load-bearing, not clerical:

  • Keep a short, high-value core that is worth reading on every UI task. Hierarchy and the action pyramid, the spacing-grouping rule, the token and palette rules, and the empty-state rule earn their place. Depth, images, and finishing touches can be deeper files loaded on demand.
  • Give each file a precise trigger row in the SKILL.md table, phrased against what the task involves in the same voice as the existing rows, so routing fires on real UI work and not on everything.
  • Every file starts with a "What This Covers" block, matching the existing reference convention that agents use to confirm relevance before reading on.
  • Measure it. If the core file cannot be read alongside styling.md and components.md within a sane budget, it is too big and must be split further.

Acceptance criteria

Process shape

  • One branch and one PR, opened as a draft right after the first push, with each phase as its own commit and a PR comment per pushed phase
  • Phase 1 ships the guidance and the evaluation harness (three committed prompt files, scripts/eval-design.mjs, its counterfactual), since the gate depends on the harness existing
  • After phase 1, the agent posts the status as a PR comment and STOPS, and does not commit phase 3 onward until the owner reports the gate result and gives an explicit go-ahead
  • The stop happens even under autonomous or bypass permissions, as the authorized exception to the never-block rule
  • A negative gate result is treated as a defect in phase 1's content or routing, and fixed there rather than papered over by later phases
  • gh pr ready is called only after every phase is done and the final characterization run is posted

Outcome (the one that decides)

  • Three fixed prompts (dashboard, multi-section settings form, content or marketing page), committed verbatim with no design hints, runnable on both sides with the same model in fresh headless sessions
  • The harness is documented well enough that the owner can run the whole comparison from a few commands, without reconstructing the method from this issue
  • At least three runs per prompt per side, with every run's score reported rather than the best one
  • scripts/eval-design.mjs (or an equivalent test) is committed, takes a generated app directory, and emits a score for the nine machine-checkable rubric lines
  • A counterfactual proves the eval script actually fires (feed it an app with a bg-red-500 and confirm it fails)
  • The gate run (guidance only, owner-executed) shows the "after" side beating "before" on all rubric lines, machine and visual, regressing none
  • The final characterization run is posted on the PR before it is marked ready, covering the machine checks across all three prompts plus one visual pair
  • Both sides' screenshots are in the PR, light, dark, and one JS-disabled load, for every prompt pair
  • The generated source for at least one screen per side is committed as a fixture, so the machine checks stay re-runnable after the branch is gone
  • A negative result, if any, is reported as a finding rather than worked around
  • The negative check passes: the "after" run still gets the tag-prefix rule, the global focus ring, position: fixed headers, and the server-import boundary right, proving design guidance did not crowd out mechanics
  • Two or three complete design exemplar screens ship in the scaffold under app/examples/, densely commented with the reasoning, each scoring full marks on the rubric
  • The screens the scaffold already generates are audited against the rubric, and fixed where they contradict the guidance

Routing and context budget

  • The design guidance has a short high-value core, with depth, images, and finishing touches split into on-demand files if size requires
  • Each file has a precise trigger row in the SKILL.md table and a "What This Covers" block, matching the existing reference convention
  • The core file can be read alongside styling.md and components.md within a sane context budget, verified rather than assumed

Coverage (Rule 1)

  • Every line of the 50-chapter coverage checklist in "Definition of done" is ticked, with each principle actionable somewhere an app-building agent reads
  • Any principle deliberately not carried over is listed with its reason, rather than silently dropped
  • The four companion-volume lines (palettes, component gallery, fonts, screencasts) are covered too

Delivery (Rule 2)

  • webjs create on a fresh app produces .agents/skills/webjs/references/design.md in that app, and SKILL.md inside the generated app routes to it
  • The per-helper Design JSDoc blocks survive webjsui add into an app's components/ui/
  • webjsui init writes the elevation and semantic tokens into the app's own stylesheet
  • The design guidance is reachable through the webjs mcp knowledge layer
  • Behavioural check: from inside a freshly generated app, the design guidance is discoverable by an agent that reads AGENTS.md and follows the skill routing, with no access to this repo

Palette selection

  • The archetype decision table ships in design.md, setting neutral, accent, radius, and type together per app archetype, with a stated default row for the unclear case
  • The rules that ship with it are present: one accent, never a semantic hue as the brand accent, no pure black or white, one radius, neutral and accent agree in temperature
  • The selection procedure ends in a concrete light-dark() token block for app/layout.ts plus a @webjsdev/ui base colour, not a recommendation
  • All values are drawn from Tailwind's palette. No Refactoring UI hexes and no newly invented ones
  • webjsui init can take an accent (and ideally an archetype), so the first command an app runs produces a themed system rather than the all-grey default

Tailwind API vocabulary (Rule 4)

  • Every principle in the reference is stated as a Tailwind utility or @theme namespace, with px values appearing only as explanatory background
  • The principle-to-utility mapping table ships in the reference
  • Every utility name is verified against the installed Tailwind version (v4 renames, bg-linear-* and text-shadow-*)
  • No arbitrary-value syntax (p-[13px]) is recommended anywhere, and the reference says so explicitly
  • The elevation deliverable is semantic naming over Tailwind's existing two-part --shadow-* values plus the dark inversion, not a reinvented shadow scale

Design-system integrity (Rule 3)

  • The color section of design.md opens with the token translation rule, before any color advice, and every color principle in it is expressed as a token operation rather than a utility class or hex literal
  • The Refactoring-UI-concept to token-expression mapping table is present in the reference
  • The guidance states the prohibition explicitly and positively: build the token system first, consume it everywhere, never bypass it at a call site
  • sonner.ts L150 and L153 no longer hardcode text-emerald-500 and text-amber-500, and use the new --success and --warning tokens
  • A monorepo-internal test asserts the registry and scaffold gallery contain no raw Tailwind palette utilities, with a counterfactual proving it fires
  • The scaffold gallery is still 100% token-based after this work, verified by the same grep that passes today
  • No raw palette rule is added to webjs check (that would police user code, and the taste boundary in AGENTS.md forbids it)

One-pass usability (translation 3)

  • Every line of the finished reference passes the single-pass test: an agent can act on it correctly with no opportunity to look at the rendered result
  • A final audit pass has run against the banned-phrasings table, and no instance of any of the seven failure modes survives
  • No instruction of the shape "start with X then adjust toward Y" survives anywhere in the reference
  • No "by eye", "by hand", "trust your judgment", or "try A, or B, or C" wording survives
  • No human-only activity (browsing font directories, sketching, hiring) is presented as an instruction
  • The reference explicitly forbids emitting placeholder or stock image URLs, and gives the no-imagery alternative
  • The iterative chapters (white space, process of elimination, grayscale-first, work in cycles, develop your intuition, shade construction) are each restated as one-pass rules or diagnostics
  • Where a banned phrasing carried real reasoning, that reasoning survives as a justification rather than being deleted outright

Content

  • .agents/skills/webjs/references/design.md exists and covers all seven areas: hierarchy, layout and spacing, text, color, depth and elevation, images, and finishing touches, plus the process framing
  • It is routed from the reference tables in both .agents/skills/webjs/SKILL.md and root AGENTS.md, marked as read-first for user-facing UI work
  • It is WebJs-shaped, covering at minimum: progressive-enhancement-safe empty and loading states, <webjs-suspense> fallbacks as a design surface, and a systematized optimistic-pending treatment
  • Color guidance is expressed in oklch, and does not carry over the HSL-era saturation and hue-rotation workarounds
  • Depth guidance states both the light-mode (shadow) and dark-mode (surface lightness) elevation models
  • references/styling.md cross-references design.md and stays the mechanics reference
  • @webjsdev/ui ships a five-step elevation scale in themes/index.css, defined for both light and dark, and the components that previously picked shadows ad hoc consume it
  • @webjsdev/ui ships success, warning, and info semantic ramps alongside --destructive, contrast-checked at 4.5:1 for text and 3:1 for large text, with alert and badge variants for each
  • The six new primitives (empty-state, stat, page-header, field/input-group, description-list, timeline) are registered in registry.json and installable via webjsui add
  • Every helper's JSDoc carries a Design block beside its A11y block
  • No Refactoring UI asset (icons, .sketch, palette tables, book prose) is committed to the repo
  • No design rule is added to webjs check
  • A counterfactual proves the registry tests actually fire for the new components
  • Tests cover the new behaviour at every layer it touches (unit for the registry and manifest, browser for interactive primitives)
  • Docs surfaces updated: website/app/docs/styling, website/app/docs/ui, the ui site, packages/ui/README.md, packages/ui/AGENTS.md, root AGENTS.md
  • A freshly generated app boots and passes webjs check
  • Verified in a real browser in both light and dark themes

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions