How to generate complete, interactive HTML prototypes using Alpine.js + Tailwind CSS (via CDN). This is the core skill for the design agent.
BY BuilderIOUPDATED Oct 28, 2026
INTENDED FOR◈OpenClawInstall in a listed agent.
instructions.md
---
name: design-generation-565490dc1e9a
description: >-
Generate or refine Design web prototypes and fixed promotional artwork. Use
when creating screens, variants, ads, social graphics, flyers, posters, or
banners. For HubSpot email requests, read hubspot-email instead.
---
# Design Generation
Generate complete web prototypes through Design actions.
## HubSpot emails
For HubSpot email creation, alignment, token edits, conversion or HTML handoff,
read `hubspot-email` before applying the web workflow below. It owns email-safe
authoring and completion checks, which replace the web stack defaults.
## Technology Stack
Web prototypes use:
- **Tailwind CSS v4** — via `@tailwindcss/browser@4` CDN (NOT the old v3 CDN)
- **Alpine.js 3.x** — via `alpinejs@3.15.11` CDN with `defer` attribute
- **Google Fonts** — for distinctive typography (never Inter/Roboto/Arial)
- **CSS Custom Properties** — for theming and tweaks panel integration
## Why the workflow exists — it is the anti-slop engine
Generic output ("AI slop") is a *workflow* failure, not a lack of talent. When one
prompt has to set the taste, explore the options, and emit final code all at once,
the safest answer is the statistical average of the training data: Inter, an
indigo→violet gradient, a centered hero, three rounded icon cards. Design beats
this by splitting those jobs across the tools — use them in order, don't collapse
them:
1. **Direction** — `show-design-questions` (or a stated thesis) sets taste on purpose.
2. **Exploration** — `present-design-variants` compares genuinely different directions
before committing to one. **This step kills sameness; never skip it for open-ended work.**
3. **Spec** — a linked design system and the `:root` token block capture the chosen look as reusable rules.
4. **Code** — `generate-design` / `edit-design` execute a decision already made instead of guessing.
Jumping straight to code is how you get slop. Let the phases (below) do the work.
## First decide the mode: reproduce or explore
The workflow above assumes an under-specified prompt. When the user has already
specified the design, that same workflow is the failure — exploration reads to
them as "the agent ignored everything I gave it".
Before Phase 1, check for **specification signals**:
- an attached screenshot, mockup, or photo of a UI,
- a linked design system / Brand Kit,
- a written outline naming concrete screens, sections, components, or copy,
- a named existing product/page to match, or pasted markup or tokens.
**Any one of these puts you in reproduce mode.** In reproduce mode:
- Do **not** call `present-design-variants`. Three divergent directions when the
user handed you one is not exploration, it is discarding their work. Produce
the one thing they specified.
- Do **not** call `show-design-questions` about anything the supplied material
already answers. Ask only about a genuine gap, and prefer stating an
assumption over asking.
- Every "Do" in the aesthetic quality bar below is **off** wherever it would
contradict the supplied material. Those rules break ties in the absence of a
spec; they never outvote one.
- Reproduce first, improve second. Match the structure, then raise quality
*within* it, and say what you changed and why.
Mixed input is normal: reproduce what was given, use the quality bar only for
the parts genuinely left open. When a supplied reference and the linked design
system disagree, follow the design system for tokens (color, type, spacing,
radius) and the reference for layout and composition, then say you did.
### A reference screenshot is a specification, not a mood board
When the user attaches an image of a UI, read it as a layout spec and
reproduce it: the same regions in the same positions, the same navigation
pattern, the same information hierarchy and density, the same component
grammar (tabs vs. pills, cards vs. rows, sidebar vs. topbar), the same
approximate proportions. Name what you see before you build, so the user can
correct a misread early. Deviate only where the image is genuinely unreadable
or an explicit instruction overrides it — and say which, rather than
silently substituting your own composition.
An attached image is only guaranteed to be visible on the turn it arrived.
If a screenshot was described earlier in the thread but you cannot see one now,
say so and ask for a re-attach — never quietly design from the text alone.
## Authority and direction record
Before generating, make a compact direction record: the job this surface must
do, its audience, one-sentence visual thesis, the active design-system id or
token source, approved references, and any unresolved choice that could change
the result. This keeps a strong point of view without asking the model to fill
in the user's intent silently.
Resolve visual authority in this order:
1. The current-turn product, audience, content, accessibility, and explicit
brand constraints.
2. The active Agent-Native design system and its registered tokens, components,
type, imagery rules, and custom instructions.
3. Approved Creative Context assets, components, and examples.
4. Impeccable-inspired quality heuristics for hierarchy, subtraction,
composition, contrast, motion, and bounded review.
5. Generic defaults only when the sources above are absent.
The active design system is a contract, not a mood board. Do not swap its
palette, fonts, component grammar, or imagery policy because a generic
anti-pattern list prefers something else. A user can explicitly request a
replacement or detachment; do not infer that request from a vague adjective.
References can guide composition and narrative, but they do not silently
transfer their tokens or brand identity.
When the source is a transcript, meeting note, or pasted brief, treat it as
evidence rather than a finished story. Preserve the user's terminology and
exact claims, separate facts from interpretation and visual references, and
ask one targeted question or mark an assumption when a missing choice would
change the design. Never smooth an evidence gap into confident copy.
## Aesthetic quality bar — beat distributional convergence
The rules in this section are fallback quality heuristics for prompts that
leave the look open. They do not override an active Agent-Native design system,
a supplied reference, or an explicit brand constraint. **A banned item stops
being banned the moment the user's own material specifies it** — if their Brand
Kit's body font is Inter, the design ships Inter; if their screenshot is a
centered hero with three icon cards, the design ships that. Substituting a
"better" choice for a specified one is the single most-reported failure of this
app. When you follow a specified value that appears on a "don't" list below,
just follow it — no apology, no note, no compensating flourish elsewhere.
You sample toward the "on-distribution" center by default; refuse it. **Every
"don't" here carries a "do"** — a banned default plus where to go instead —
because banning Inter alone just makes you reach for Roboto next. Use a banned
item only if the user explicitly asks, or their design system or reference
already uses it.
- **Fonts.** Don't: Inter, Roboto, Arial, Open Sans, system-ui. Do: a distinctive
Google Font pairing matched to the chosen aesthetic (see the table) — editorial
serif, grotesk display + mono, or one variable font pushed across weight extremes.
- **Color.** Don't: the indigo/violet slop palette (`#6366F1`, `#8B5CF6`,
`#A855F7`), a purple gradient on white, or everything in default grays. Do:
anchor on one non-default family — clay/ochre/terracotta, ink/bone/mustard,
charcoal/lime, oxblood/cream, navy/copper, warm paper (`#FBF7F0`) over pure
white — with one decisive accent used sparingly for hierarchy, not decoration.
- **Layout.** Don't: centered hero + one CTA + a row of three icon cards,
rounded-everything, `0.1`-opacity drop shadows, blanket glassmorphism, or the
badge-above-headline cliché. Do: asymmetric 60/40 or 70/30 splits, uneven
visual weight, one clear focal point, and flat confident surfaces.
- **Background.** Don't: a single flat fill. Do: layered gradients, a geometric
pattern, grain, or a contextual texture that matches the theme.
- **Copy & voice.** Don't: lorem ipsum or buzzword filler ("empower", "seamless",
"leverage", "revolutionize", "in today's fast-paced world"). Do: realistic
domain content in a specific voice — copy is design material.
**Second-order convergence is real.** Even your "creative" picks converge (Space
Grotesk everywhere; teal accent + blinking dot + left accent bars). Vary
deliberately across generations so two designs never share a fingerprint —
but only across designs *you* had to invent. A token the user's own system
specifies is theirs, not your fingerprint: consistency with their brand is the
goal, and varying away from it to avoid repeating yourself is a bug.
**Principles to quote back while building:** color creates hierarchy, not
decoration · density over decoration · earn every animation · commit to one point
of view. **Match code to the vision** — maximalist themes want elaborate motion
and effects; minimal themes want restraint and precise spacing. Elegance is
executing one vision fully.
**References beat adjectives, but only with a reason.** "Linear: the quiet
confidence of its spacing" or "Stripe: dense but never crowded" points somewhere
specific; "Linear" alone collapses back to the average, and replying to your own
output with "make it cleaner / more premium" means you're negotiating with vibes.
## Prompt the design in four layers
Decide each layer explicitly before writing HTML. This is what makes variations
genuinely distinct instead of three near-identical layouts:
1. **Context** — audience, domain, and the one job this screen must do.
2. **Structure** — a named layout topology: bento grid, sidebar app shell,
editorial column, split-screen, masonry, dashboard tiles.
3. **Aesthetic** — a named visual movement: editorial serif, neo-brutalist,
glassmorphic, Swiss/International, warm organic, technical/mono, etc. Each
variant gets a *different* aesthetic + font pairing.
4. **Tech stack** — Tailwind v4 + Alpine, mobile-first, light/dark via tokens.
Push each dimension to extremes rather than safe middles: weight extremes
(100–200 vs 800–900), 3×+ type-scale jumps, one dominant color + a single sharp
accent, layered gradient/pattern backgrounds (not flat fills), and one
orchestrated staggered page-load reveal (via `animation-delay`).
Pick a preset by `projectType`:
- **Brand / marketing** (landing pages, decks): expressive, atmospheric,
animated, full-bleed.
- **Product / app** (dashboards, tools): dense, restrained, token-driven, fast.
## Measurable rules (bake these in)
- 8px spacing grid — all padding/margins/gaps are multiples of 4/8.
- Every px length is a whole number: position, width, height, padding, gap,
radius, font size, translate. Absolute `left`/`top` are also multiples of the
screen's `layoutGrid.size` when `design-selection` reports one. A fractional
or off-grid value reads as a mistake to a designer and does not match what
dragging the same element produces. Set a screen's grid with
`set-layout-grid`.
- Flat layer tree. Every element is a layer the user selects, drags, and
resizes, so each one must earn its place: one flex or grid container per
visual group, content directly inside it, spacing from `gap` and `padding`.
A wrapper with a single child and no layout, paint, or spacing of its own is
a defect. Sections are usually three or four levels deep.
- Body text ≥ 16px, labels ≥ 12px.
- Big type-scale jumps for hierarchy (don't rely on tiny size deltas).
- WCAG contrast: 4.5:1 normal text, 3:1 large text. Verify accent-on-background.
- Mobile-first breakpoints; never ship a layout that breaks under 640px.
- No arbitrary one-off Tailwind values where a scale step exists.
- **Token grounding is non-negotiable:** every color/font/radius references a
`:root` CSS variable. Never hardcode `text-white` / `bg-black` / hex literals
in the markup — that's what keeps brand + multi-screen consistency automatic.
### Type-scale recipe
Use this as a starting scale, then adjust to the chosen Aesthetic:
- Display: 56-96px · H1: 40-64px · H2: 28-36px · Body: 16-18px · Caption: 12-13px.
- Each adjacent step should be at least 1.25× the one below it — smaller jumps
read as "almost the same size" rather than a deliberate hierarchy.
- A hero/display line should be at least 3× the body size.
- Line-heights: display/H1 tight at 1.05-1.15, H2/H3 at 1.2-1.3, body relaxed
at 1.5-1.7.
- Measure (line length) for body copy: 60-75 characters; constrain with
`max-width` in `ch` units, not a raw pixel guess.
### Section rhythm
Pick one section padding value and repeat it for every top-level section on
the page/screen: 96-128px on desktop, 48-64px on mobile. Don't let each
section invent its own padding — that's what makes a page feel unplanned.
Use spacing to encode grouping: gaps *inside* a card or cluster should be
visibly smaller than the gap *between* cards/sections — if inside-group and
between-group spacing match, the eye can't tell where one group ends and the
next begins.
### Verifiable contrast pairs
Don't just assert "WCAG AA" — check the actual token pairs the design ships.
The most common real failure is muted text (`--color-text-muted`) directly on
a card/surface background rather than the page background; verify that pair
specifically, not just text-on-page. If an accent color doubles as text (a
link, an active nav item, a price), it usually fails 4.5:1 against typical
surfaces — add a separate `--color-accent-text` variant tuned for text-on-
background contrast rather than reusing the decorative accent for copy.
### Richer tokens
Go beyond the minimal `:root` block in the HTML Structure Requirements below
when the design needs it — add `--space-section` (see Section rhythm),
`--color-border`, `--color-accent-text` (see contrast pairs above),
`--shadow-card`, and a success/warning/danger trio
(`--color-success` / `--color-warning` / `--color-danger`) once the design has
status states, alerts, or form validation to express. Keep font tokens as
placeholders you fill per design (see HTML Structure Requirements) rather than
hardcoding a concrete family in a shared template.
## Building on existing code, screens, or a design system
When a design system, tokens, current screens, or a connected codebase already
exist, the slop risk flips: the failure is ignoring the brand and reverting to
defaults. The banned-defaults list above still applies, plus:
- **Inspect before inventing.** Read the linked design system, the current
`:root` tokens, and existing screens (or the connected localhost/repo) first.
Derive the type scale, palette, radius, density, and component language from
what is actually there — don't restate a generic direction.
- **Treat every reversion as a missing spec entry.** If output drifts to a
default (Inter, pill buttons, a stock radius) despite the brand, don't just
re-prompt — pin the explicit value into `:root` so it can't drift again.
- **Consistency is not sameness.** Tokens alone make every screen "the same in
your colors". Keep structure and layout genuinely varied per screen while the
palette, type, and components stay on-brand.
## Generation Application State
- `design-generation-session:<designId>` — multi-screen generation planning
state from `generate-screens` (canvas region assignments, per-frame
instructions consumed by `generate-design`).
- `show-design-questions` opens pre-generation questions in the main canvas
(`show-questions` state).
- `guided-questions` may hold a one-click chat choice for the current variant
set.
## Generation Workflow — the canonical 5-phase flow
This flow mirrors Claude Design's UX: clarify only what's unclear → show variants → user picks → refine. Don't collapse phases into one shot for new, open-ended designs.
### Creative-context gate
Before Phase 1, read the `creative-context` skill and retrieve components,
interaction examples, visual style, and factual evidence as separate roles.
Respect `contextMode: "off"` and pinned packs. Apply its reuse ladder exactly:
use an approved native component/template/asset unchanged, compose approved
pieces, lightly adapt a real example, generate from narrowly retrieved
references, then generate net-new only when the relevant corpus is empty.
Retrieval is a separate operation from `generate-screens`, `generate-design`,
or `present-design-variants`.
Keep the selected immutable `contextPackId` and reuse labels on the generation
session and every resulting screen/variant. Rendered HTML and screenshots are
not provenance. App-local design systems and components remain the fallback
when shared retrieval finds no relevant evidence.
### Phase 1 — Create the project + ask before generating
```bash
pnpm action create-design --title "Project Name" --projectType prototype
pnpm action navigate --view editor --designId <returned-id>
```
The `navigate` step is only for first-party/local app agents that can write
application state. External MCP hosts should surface the `create-design`
returned "Open design" link, then use `present-design-variants` to open the
visual picker.
Then, for a brief or ambiguous first prompt to a **new** design, call
`show-design-questions` BEFORE generating. The editor renders a full-canvas
overlay; answers come back as a chat message. Size the question count to how
much is actually unresolved — most prompts warrant 2-4 questions, not the full
1-8 range — and never ask about something the prompt already specified (a
stated color, audience, or layout is settled; don't re-ask it as a choice).
Skip the questions entirely when:
- the prompt is already specific enough to generate from (names the audience,
purpose, and a visual direction, e.g. "a dark, data-dense analytics
dashboard for ops engineers" or "re-skin this with my brand colors");
- it's a tweak/edit/refinement to an existing design rather than a new one —
go straight to `edit-design` (see Phase 3 and "Making edits" below);
- the user already answered a question set for this design and is now
iterating — don't re-ask settled ground on follow-up prompts for the same
design; or
- the user says "decide for me," "surprise me," "just build it," or similar.
The turn that opens `show-design-questions` already checked Creative Context
and any published Brand DNA before this skill loads, and the directives it
built name exactly which topics (form factor, aesthetic direction,
features/content, interactions/polish, exploring variations) are already
answered. Never re-ask a topic those directives list as covered, and never
skip a topic they don't — a single unrelated context member (a doc, a spec)
must not suppress a question it has no bearing on.
Asking on every prompt is as much a failure mode as never asking: a detailed
prompt that already answers the obvious questions should generate
immediately, and a design already in flight should not be interrupted with a
second questionnaire.
```bash
pnpm action show-design-questions \
--designId "<id>" \
--title "Quick questions about your todo app" \
--questions '[{"id":"form_factor","type":"text-options","question":"What form factor?","options":[{"label":"Desktop web app","value":"desktop"},{"label":"Mobile app","value":"mobile"},{"label":"Both / responsive","value":"responsive"},{"label":"Decide for me","value":"decide"}],"allowOther":true}]'
```
Favor choice-first questions (2-5 concrete options, `allowOther: true`, and a
"Decide for me" option when any answer is acceptable) over open-ended
`freeform` text, and avoid `multiSelect` unless the question genuinely allows
combining multiple answers — stacking multi-select questions multiplies
follow-up ambiguity instead of resolving it.
**Carry the form-factor answer through to generation — do not just ask and discard it.** Map the answer to the design's device SET, not to separate per-device screen files. Device widths of the SAME page are breakpoint frames of one document (see the `responsive-breakpoints` skill), never a `mobile.html` + `desktop.html` pair. Pass the answer through `generate-design`'s `devices` param — `("mobile"|"tablet"|"desktop")[]`, default `["desktop","mobile"]`:
- An exact pixel size in the original user request defines one fixed-size canvas. Use those exact dimensions and pass `devices: []` unless the user explicitly asks for device variants; then preserve exactly those requested devices. For `present-design-variants`, use `responsive: false` for a single fixed canvas. Keep each action prompt to one distinct exact canvas size; make separate calls scoped to each screen when a request names different sizes.
- Ads, banners, social posts/stories, flyers, posters, logos, and other static artwork are fixed canvases, not responsive apps. Do not add mobile or tablet breakpoints unless the user explicitly asks for device variants; if they do, preserve exactly those choices and add no others. If a `present-design-variants` chat caption is generic, pass the original request separately as `brief` so its size and output type stay attached to the variants.
- For a follow-up on an existing fixed artboard, read its exact frame geometry first. Content-only edits keep that size. When regenerating it for a resize or named-format follow-up (for example, “make it taller” or “now a story version”), target the same file with `generate-design`, pass `devices: []`, and include one `canvasFrames` entry with exact numeric `width` and `height` plus its current placement. Preserve the unchanged dimension and resolve the new one from the conversation or the named format's canonical preset (Instagram Story is 1080×1920). Never let device defaults add breakpoints.
- If the prompt/answer names specific devices, generate EXACTLY those, deduped ("mobile" only → one mobile frame; "mobile, tablet, desktop" → all three).
- If no exact pixel size or form factor is specified — or the answer is "Both / responsive" or "Decide for me" — default to `["desktop","mobile"]`: a Desktop base + a Mobile frame only. Never auto-add a tablet, a redundant desktop, or a stray duplicate frame.
The WIDEST requested device is the base/primary frame; narrower devices become breakpoint frames (never at the primary width). Device frame sizes: mobile 390×844, tablet 768×1024, desktop 1440×900. `present-design-variants` takes explicit `width`/`height` per variant to size its exploration screens (see Phase 2). Pass the original request in `brief` when the chat caption is generic; dimensions and output intent come from that brief, never variant labels or feature text.
### Phase 2 — Generate side-by-side variations (2-5, three by default)
Skip this phase entirely in reproduce mode (see "First decide the mode" above) —
a screenshot, a written outline, or a linked design system means the direction
is already chosen, and offering three alternatives to it is the behavior users
report as "the agent ignored my design".
For new designs whose direction is genuinely open, default to **three**
variations (`present-design-variants` accepts 2-5; three is the sweet spot).
Call `present-design-variants` for both first-party and external MCP-host
flows. It saves each candidate as a normal
overview-board screen, then renders an inline chat choice with one button per
screen name.
```json
{
"designId": "<the design id>",
"prompt": "Pick a direction",
"brief": "<the original request, including its exact canvas dimensions>",
"variants": [
{ "id": "a", "label": "Editorial Serif", "width": 1440, "height": 900, "content": "<!DOCTYPE html>...full self-contained HTML..." },
{ "id": "b", "label": "Bold Brutalist", "width": 1440, "height": 900, "content": "<!DOCTYPE html>..." },
{ "id": "c", "label": "Soft & Spacious", "width": 1440, "height": 900, "content": "<!DOCTYPE html>..." }
]
}
```
Each `content` is a complete, self-contained document (Alpine.js + Tailwind via CDN, full `<head>`, CSS variables in `:root`). Variations should be **structurally and compositionally distinct** — different layout grammars, hierarchy, density, and focal points — never just color swaps. When a design system is linked, keep its tokens, typography, components, and imagery rules fixed across variants; vary those only when the user explicitly asks to explore a replacement system. Label the directions with concrete names ("Editorial split", not "Variant A").
Pass `width`/`height` on every variant to match the form-factor answer (mobile ≈ 390×844, tablet ≈ 768×1024, desktop ≈ 1440×900) — the example above is desktop-sized. When the original brief specifies exact pixel dimensions, use that exact size on every variant; Design will preserve it even if a variant supplied another size. Static artwork has no responsive frames even if `responsive: true` is passed. Omit `content` only for open-ended app exploration: a fixed canvas, attached reference image, layout spec, or linked design system rejects direction-only variants. When `content` is omitted, `present-design-variants` infers a size from the brief/label/description text and the width/height you pass still wins when given.
Wait for the user's pick before refining. Once they choose, keep the selected
screen, delete the unchosen variant screens with `delete-file`, and continue
from the kept screen by calling `get-design-snapshot` with the selected
screen's `fileId`, then calling `edit-design` on that same `fileId`. Use
`mode: "replace-file"` when expanding the representative placeholder into the
full chosen direction. Do not call `generate-design` after a variant pick. If
inline chat choice buttons are unavailable in the host, ask the user to tell you
the preferred screen name. Do not ask them to paste HTML or a generated handoff
summary; the variants are already real screens on the board.
### Phase 3 — Save with `generate-design` (when not using variants)
Skip variants and call `generate-design` directly for: a brand-new first
renderable file, multi-screen additions to an existing design, or one-shot
prompts where the direction is unambiguous. For refinements to an already-picked
design or selected screen, use `get-design-snapshot` followed by `edit-design`
instead.
```bash
pnpm action generate-design \
--designId "<id>" \
--prompt "Description of the design" \
--files '[{"filename":"index.html","content":"<full HTML>","fileType":"html"}]' \
--tweaks '[{"id":"accent","label":"Accent","type":"color-swatch","options":[...],"defaultValue":"#0EA5E9","cssVar":"--color-accent"}]' \
--devices '["desktop","mobile"]' \
--canvasFrames '[{"filename":"index.html","x":0,"y":0,"width":1440,"height":900}]'
```
Pass the `devices` param (`("mobile"|"tablet"|"desktop")[]`, default `["desktop","mobile"]`) so a new design renders the right device frames: the widest device becomes the primary `canvasFrames` placement and each narrower device is added as a breakpoint frame of the SAME document — not an extra file. Default to Desktop + Mobile when the form factor is unspecified; never auto-add a tablet or a redundant desktop. When you also pass `canvasFrames` explicitly, size the primary to the widest device (mobile ≈ 390×844, tablet ≈ 768×1024, desktop ≈ 1440×900) — a screen saved without a placement falls back to a generic default that won't match the intended device. For genuinely distinct screens generated together (Home / Dashboard / Checkout — NOT per-device copies of one page), call `generate-screens` first; it returns the matching `canvasFrame` to forward to each `generate-design` call.
#### Non-web sizes — ad units, print one-pagers, social sizes
`canvasFrames` accepts exact `width`/`height` in px, so "create a 300x250
ad" style requests work the same way — use the requested dimensions verbatim
and pass `devices: []`. Keep each action prompt to one distinct exact
size; use separate calls for screens with different dimensions. This also
applies to exact-size email and social assets. The editor's own Frame tool preset
list (`shared/frame-size-presets.ts`) documents the
canonical sizes to reuse instead of guessing:
- **Ad units (IAB standard)**: Medium Rectangle 300×250, Leaderboard 728×90,
Wide Skyscraper 160×600, Mobile Leaderboard 320×50, Billboard 970×250.
- **Print (96dpi CSS px, matching this app's PNG/PDF export unit)**: US
Letter 816×1056, A4 794×1123, A5 559×794, Tabloid 1056×1632. Design print
one-pagers with real multi-column layout, tables, and a footer — a
fixed-size print artifact has no responsive fallback, so lay out the exact
canvas once. The editor's Download PDF export renders these at a
print-quality floor (2x raster, ~192dpi) regardless of the export panel's
default scale setting.
- **Social and platform assets**: LinkedIn Single Image Ad 1200×627 (distinct
from LinkedIn Cover 1584×396), Meta Feed Square Ad 1080×1080, Meta Feed
Landscape Ad 1200×628, Instagram Portrait Post 1080×1350, Instagram Story
1080×1920, Open Graph Image 1200×630, YouTube Thumbnail 1280×720, X Promo
Graphic 1200×675, Email Header 600×200, X Post 1200×675, and Facebook Cover
820×312.
Frame geometry is always numbers — `"width": 800`, never `"800"` or `"800px"`. String dimensions are rejected.
At small ad-unit sizes (320×50, 160×600), text commonly runs 9-11px — smaller
than this skill's general 16px body-text floor — because there is no room to
reflow. Higher-resolution exports make edges sharper, but do not increase text's
relative size when the asset is displayed at its intended dimensions.
### Promotional artwork typography at delivery size
For ads, social graphics, flyers, posters, and banners, size and space type for
the fixed canvas and where the asset will appear. The web prototype type scale
and page section rhythm above are not defaults for artwork. If the user supplies
a `design.md` or Brand Kit, use its typography, spacing, color, and voice as the
source of truth and ground the output in those tokens.
Use the canvas's shorter edge as a starting measure: on feed graphics and
poster-like formats, a headline around 6–10% of that edge and supporting text
around 2.5–4% often gives a clear first hierarchy (about 65–108px and 27–43px
on a 1080px square; 38–63px and 16–25px on a 1200×627 canvas). Treat these as
starting ranges, then adjust for the brand's typeface, copy length, composition,
and intended display scale; raise type or recompose if the display-size check
fails. Choose weights by role from the brand-approved type tokens: give the
headline the strongest available emphasis, keep date/details quieter, and give
the CTA enough emphasis to scan. If the brand offers one weight, use size,
position, or contrast for hierarchy instead of inventing another. Estimate
displayed type size as `authored px × displayed width / canvas width`: 36px type
on a 1080px-wide asset appears about 12px tall in a 360px feed slot. A 2× export
adds pixel density, not apparent size. On short banners and small ad slots, use
a compact message and size type against the available height instead of
carrying over those ratios. Set line length, line height, safe margins, and
spacing from the actual canvas so the headline leads and supporting copy stays
readable. When text does not fit, shorten or recompose it before shrinking
every line.
### Phase 4 — Always ship tweaks with the design
`generate-design` accepts a `--tweaks` array — pass 3-6 of the most impactful knobs bound to CSS custom properties the design's `:root` block actually defines. Surface controls users will actually want to adjust (accent color, density, radius, dark-mode toggle, font choice). Don't ship a generic preset; let the design's structure pick the knobs. When a user asks to add a tweak control to an existing design, preserve the existing useful tweaks and add/update only the requested definitions — read the current file with `get-design-snapshot` first if source edits are needed, and persist the complete updated tweak list through `generate-design`.
### Phase 5 — Audit, screenshot, fix, and eyeball before calling it ready
Run `run-design-audit` against each screen (`designId` + `fileId`/`filename`).
It returns `A11yFinding[]` covering missing alt/labels, tap-target size,
focus-visibility, reduced-motion coverage, a contrast hint, and — for
multi-screen designs — token drift against `index.html`'s `:root` block. For
every `error`-severity finding with `fixAvailable: true`, call
`apply-a11y-fix`; for findings that aren't auto-fixable (missing alt text,
structural issues, token drift), fix them directly with `edit-design`. **A
design with audit errors is not ready** — don't report a design as done while
`run-design-audit` still returns unresolved errors.
A `design-system-drift` finding means the screen uses none of the linked
system's fonts, colors, or token names. That is a generation bug, not a style
disagreement: the user linked that system precisely so the output would use it.
Call `get-design-system` for the linked id, apply the real values through
`edit-design`, and re-audit — never resolve it by explaining the deviation or
by unlinking the system. It is the one check that can catch a design that
looks fine and is still not the user's.
After the audit is clean, call `take-design-screenshot` on each changed screen
(default: 1280px desktop + 375px mobile). For an exact-size screen, capture
only its specified viewport by passing `widths: [width]` and
`heights: [height]`. Fix everything its `diagnostics`
report flags — real computed contrast ratios, horizontal/container overflow,
broken images, zero-size or off-screen text, console errors — before reporting
the design as ready; this is the same "visually inspect the result" pass, done
against the real rendered DOM/CSS instead of by eye. If Chromium isn't
available in the current environment, it returns `{ ok: false, reason }`
instead — fall back to a careful read of the HTML plus the audit findings. The
returned screenshot `url` is for human review (embed it as `` in
your reply); also still scan the rendered output yourself for anything the
diagnostics don't catch — broken hierarchy, empty/loading/error states for app
UI, and whether the copy/content still sounds real. To compare two design
snapshots/branches for a file-level visual diff (added/removed/modified) —
e.g. after a large refactor or before/after a review pass — call
`get-design-review` instead of eyeballing both versions.
For fixed promotional artwork, inspect the exact-size screenshot at 1:1 and at
the intended display scale (for example, as a feed thumbnail). Confirm the main
message reads first and the supporting copy remains legible at that scale; if
it fails, revise the type, spacing, or copy and capture it again. Export scale
alone does not fix type that is too small for the intended display.
After generation or a broad update, leave the user in the screen overview
(`navigate --view editor --editorView overview`) when the work involves
multiple screens or artboard placement — it's the primary editing surface for
selecting, moving, resizing, and entering focused single-screen editing via a
frame's Interact button. Reserve single-screen mode as the default landing
view only when the user asked to focus one specific screen.
## HTML Structure Requirements
### The save gate rejects malformed markup
Every path that persists model-authored HTML — `generate-design`, `create-file`,
`present-design-variants`, `edit-design`, `update-file`, canvas edits — runs an
integrity check, and a **new** file is held to it strictly. Import paths
(Figma, localhost, templates, duplication) deliberately do not fail closed:
that markup comes from a real app or an existing design, and rejecting it would
block bringing work in rather than prevent a bad generation.
Rejections name the file, line, column, and offending source line — fix exactly
what it points at instead of re-sending the payload differently. Blocked: an
attribute quote that is never closed; an unclosed element or a closing tag with
no opener; content ending mid-tag (a payload cut off in transit); a
`<style>`/`<script>` missing its opener or closer; duplicated editor-managed
style blocks; more than one `<html>`/`<body>`.
This exists because the HTML parser never throws — it recovers silently and
renders a page, just not the one you wrote. An unterminated quote in `<head>`
swallows every following tag into that attribute, so the Tailwind runtime stops
applying and the screen renders unstyled with no console error to show it.
Saves may also return non-blocking `warnings` (e.g. utility classes with no
reachable Tailwind runtime). Fix those before reporting the design as ready.
### Mandatory Elements
Every `index.html` must include:
```html
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><!-- Design title --></title>
<!-- Tailwind v4 browser runtime -->
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<!-- Alpine.js — MUST be deferred -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.15.11/dist/cdn.min.js"></script>
<!-- Google Fonts — pick distinctive fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=...&display=swap" rel="stylesheet">
<style>
/* Required: hide elements until Alpine initializes */
[x-cloak] { display: none !important; }
/* Required: CSS custom properties for theming */
:root {
--color-primary: #0F172A;
--color-accent: #0EA5E9;
--color-surface: #1E293B;
--color-text: #F8FAFC;
--color-text-muted: #94A3B8;
--font-heading: '<HEADING_FONT>', sans-serif; /* pick per Font Recommendations below — do not default to Space Grotesk */
--font-body: '<BODY_FONT>', sans-serif; /* pick a pairing, not a repeat of every other generation */
--radius: 12px;
}
/* Base styles */
body {
font-family: var(--font-body);
min-height: 100vh;
min-height: 100dvh; /* fill the frame — see Full-height frames below */
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading);
text-wrap: balance;
}
p { text-wrap: pretty; }
/* Respect users who ask for less motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
</style>
</head>
<body class="bg-[var(--color-primary)] text-[var(--color-text)]">
<!-- Root wrapper fills the device viewport: prefer min-h-screen over inline height:100vh -->
<div x-data="{ /* component state */ }" class="min-h-screen">
<!-- Content -->
</div>
</body>
</html>
```
**Full-height frames.** Overview frames now default to at least the device
viewport height and grow to fit their own content at their own width
(Framer-style), so a short page must still fill its frame instead of leaving a
stubby box. The mandatory `body` rule above sets `min-height: 100dvh` (with a
`100vh` fallback) for exactly this. Put full-height intent on the top-level
wrapper with Tailwind's `min-h-screen` rather than raw inline `height: 100vh` —
the editor keeps device-anchored full-height utilities pinned to the device
viewport, so a full-height hero fills the frame without triggering runaway
growth.
### Alpine.js Patterns
**State management** — use `x-data` on the highest-level container:
```html
<div x-data="{
currentPage: 'home',
mobileNav: false,
theme: 'dark',
items: [
{ id: 1, title: 'Item 1', active: false },
{ id: 2, title: 'Item 2', active: true }
]
}">
```
**Conditional rendering** — use `x-show` with transitions (not `x-if` for simple toggles):
```html
<div x-show="mobileNav" x-cloak x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0">
<!-- Mobile nav content -->
</div>
```
**Lists** — always use `<template x-for>`:
```html
<template x-for="item in items" :key="item.id">
<div class="p-4 rounded-lg bg-[var(--color-surface)]">
<h3 x-text="item.title" class="font-semibold"></h3>
</div>
</template>
```
**Two-way binding** — use `x-model` for form elements:
```html
<input x-model="searchQuery" type="text" placeholder="Search..."
class="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-[var(--radius)] px-4 py-2 text-sm text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] outline-none focus:border-[var(--color-accent)]">
```
**Click outside** — use `@click.outside`:
```html
<div x-show="dropdownOpen" @click.outside="dropdownOpen = false" x-cloak>
<!-- Dropdown content -->
</div>
```
## Responsive Design Patterns
### Breakpoint Strategy
| Breakpoint | Width | Target |
| --- | --- | --- |
| (default) | < 640px | Mobile phones |
| `sm:` | >= 640px | Large phones / small tablets |
| `md:` | >= 768px | Tablets |
| `lg:` | >= 1024px | Laptops |
| `xl:` | >= 1280px | Desktops |
| `2xl:` | >= 1536px | Wide screens |
### Mobile-First Layout Patterns
```html
<!-- Stacked on mobile, side-by-side on desktop -->
<div class="flex flex-col lg:flex-row gap-6">
<aside class="w-full lg:w-64 shrink-0"><!-- Sidebar --></aside>
<main class="flex-1"><!-- Main content --></main>
</div>
<!-- 1 col -> 2 col -> 3 col grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Cards -->
</div>
<!-- Hide on mobile, show on desktop -->
<div class="hidden md:block">Desktop-only content</div>
<div class="md:hidden">Mobile-only content</div>
```
### Mobile Navigation Pattern
```html
<nav class="fixed top-0 inset-x-0 z-50 border-b border-white/5 backdrop-blur-xl bg-[var(--color-primary)]/80">
<div class="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
<a href="#" class="font-bold">Brand</a>
<!-- Desktop nav -->
<div class="hidden md:flex items-center gap-8">
<a href="#" class="text-sm text-[var(--color-text-muted)]">Link</a>
<a href="#" class="text-sm px-4 py-2 rounded-[var(--radius)] bg-[var(--color-accent)]">CTA</a>
</div>
<!-- Mobile hamburger -->
<button @click="mobileNav = !mobileNav" class="md:hidden p-2 cursor-pointer" aria-label="Menu">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
:d="mobileNav ? 'M6 18L18 6M6 6l12 12' : 'M4 6h16M4 12h16M4 18h16'"/>
</svg>
</button>
</div>
<!-- Mobile menu -->
<div x-show="mobileNav" x-cloak x-transition
class="md:hidden border-t border-white/5 bg-[var(--color-primary)] px-6 py-4 space-y-3">
<a href="#" class="block text-sm text-[var(--color-text-muted)]">Link</a>
</div>
</nav>
```
## Common Component Patterns
### Stat Cards
```html
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div class="p-4 rounded-[var(--radius)] bg-[var(--color-surface)] border border-white/5">
<p class="text-xs text-[var(--color-text-muted)] mb-1">Label</p>
<p class="text-2xl font-bold tracking-tight" style="font-family: var(--font-heading)">$12,345</p>
<p class="text-xs text-emerald-400 mt-1">+12.5%</p>
</div>
</div>
```
### Tab Navigation
```html
<div x-data="{ tab: 'overview' }">
<div class="flex gap-1 border-b border-white/5 mb-6">
<template x-for="t in ['overview', 'analytics', 'settings']" :key="t">
<button @click="tab = t"
:class="tab === t ? 'text-[var(--color-text)] border-[var(--color-accent)]' : 'text-[var(--color-text-muted)] border-transparent hover:text-[var(--color-text)]'"
class="px-4 py-2 text-sm font-medium border-b-2 -mb-px capitalize cursor-pointer"
x-text="t">
</button>
</template>
</div>
<div x-show="tab === 'overview'"><!-- Overview content --></div>
<div x-show="tab === 'analytics'" x-cloak><!-- Analytics content --></div>
<div x-show="tab === 'settings'" x-cloak><!-- Settings content --></div>
</div>
```
### Modal/Dialog
```html
<div x-data="{ modalOpen: false }">
<button @click="modalOpen = true" class="cursor-pointer">Open</button>
<!-- Backdrop + modal -->
<div x-show="modalOpen" x-cloak class="fixed inset-0 z-50 flex items-center justify-center p-4"
@keydown.escape.window="modalOpen = false">
<div x-show="modalOpen" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-black/60" @click="modalOpen = false"></div>
<div x-show="modalOpen" x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
class="relative bg-[var(--color-surface)] rounded-[var(--radius)] border border-white/10 p-6 w-full max-w-md shadow-2xl">
<h3 class="font-semibold mb-4" style="font-family: var(--font-heading)">Modal Title</h3>
<p class="text-sm text-[var(--color-text-muted)] mb-6">Modal content goes here.</p>
<div class="flex justify-end gap-3">
<button @click="modalOpen = false" class="px-4 py-2 text-sm rounded-[var(--radius)] border border-white/10 cursor-pointer">Cancel</button>
<button @click="modalOpen = false" class="px-4 py-2 text-sm rounded-[var(--radius)] bg-[var(--color-accent)] font-medium cursor-pointer">Confirm</button>
</div>
</div>
</div>
</div>
```
### Toast/Notification
```html
<div x-data="{ show: false, message: '' }"
@notify.window="message = $event.detail; show = true; setTimeout(() => show = false, 3000)">
<div x-show="show" x-cloak
x-transition:enter="transition ease-out duration-300 transform"
x-transition:enter-start="opacity-0 translate-y-4"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="fixed bottom-6 right-6 z-50 px-4 py-3 rounded-[var(--radius)] bg-[var(--color-surface)] border border-white/10 shadow-xl text-sm">
<span x-text="message"></span>
</div>
</div>
```
## Font Recommendations
Good distinctive font pairings (heading / body). **Rotate through these** — don't
reach for Space Grotesk (or any single pairing) every time; matching the pairing
to the chosen Aesthetic layer is what keeps designs from sharing a fingerprint:
| Heading | Body | Mood |
| --- | --- | --- |
| Space Grotesk | DM Sans | Modern tech |
| Playfair Display | Source Sans 3 | Editorial luxury |
| Sora | Outfit | Clean geometric |
| Bricolage Grotesque | Schibsted Grotesk | Contemporary startup |
| Fraunces | Work Sans | Warm editorial |
| JetBrains Mono | IBM Plex Sans | Developer tool |
| Unbounded | Sora | Bold statement |
| Archivo | Nunito Sans | Friendly SaaS |
| Instrument Serif | Schibsted Grotesk | Editorial minimal |
All of the above are served by Google Fonts (`fonts.googleapis.com/css2`). The
mandatory `<head>` only loads the Google Fonts CDN (see HTML Structure
Requirements) — Fontshare-only families (Cabinet Grotesk, Satoshi, Clash
Display, General Sans) are not on Google Fonts and will silently fail to load,
so the browser falls back to a system sans and quietly reintroduces the exact
slop this skill bans. Only use a Fontshare family if you also add its
Fontshare `<link>`/`@import` and confirm it renders.
## Multi-screen prototypes & navigation
A prototype with more than one screen is **multiple files** in the same design
(e.g. `index.html`, `dashboard.html`, `checkout.html`). The editor shows them
all in the artboard/overview and as screen tabs. This is only for genuinely
distinct screens — the mobile and desktop views of the *same* page are
breakpoint frames of ONE document (the `devices` param + the
`responsive-breakpoints` model), never a second file.
The preview renders each file in a sandboxed `srcdoc` iframe. A real
`<a href="/pricing">` or `<a href="page.html">` resolves against the *app* URL
and navigates the iframe to the Design app itself ("Design not found"), nuking
the prototype. **Never link screens with real URLs.** Use one of:
1. **In-screen flows (preferred):** keep the flow in one file with Alpine state
and `x-show`. Links become buttons:
```html
<div x-data="{ screen: 'home' }">
<button @click="screen = 'pricing'" class="cursor-pointer">See pricing</button>
<section x-show="screen === 'home'">…</section>
<section x-show="screen === 'pricing'" x-cloak>…</section>
</div>
```
2. **Cross-file screen links:** to jump to another file in the design, use
`data-screen` (or a bare filename href). The editor intercepts the click and
switches to that screen instead of navigating:
```html
<a data-screen="checkout.html" class="cursor-pointer">Checkout</a>
```
The target should match the other file's name (`checkout.html` →
`checkout.html`; the `.html` is optional in the match).
3. **In-page scroll:** `href="#features"` is fine and scrolls within the screen.
External links (`https://…`) are allowed — the editor opens them in a new tab.
Never use `target="_top"` or relative paths expecting a real page load.
## Locked subtrees
Treat `data-agent-native-locked="true"` as authoritative: locked elements and
their descendants stay byte-for-byte unchanged, and the server enforces this.
Ask the user to unlock the layer in the Layers panel if they want it changed.
## Making edits — minimal, scoped "smart" diffs
When refining an existing design, change the **smallest** amount possible. Full
regeneration is slow, expensive, and regresses unrelated parts.
1. **Read before you edit.** Pull the current file with `get-design-snapshot`
(or `get-design`) so you edit the live content, not a stale memory of it.
If the design has persisted review comments, fetch the open queue with
`get-review-feedback` and use `.agents/skills/design-review-feedback` to
apply and verify one anchored thread at a time.
2. **Prefer `edit-design` for small changes.** It applies one or more
search/replace blocks to a file's HTML — surgical, cheap, and it preserves
everything you didn't touch (Alpine state, scroll, other screens):
```bash
pnpm action edit-design --designId "<id>" --filename index.html \
--edits '[{"search":"<h1 class=\"text-4xl\">Hello</h1>","replace":"<h1 class=\"text-5xl\">Hello there</h1>"}]'
```
Each `search` must match the file **exactly and uniquely** — include enough
surrounding context to be unambiguous. When a change needs a new container,
first try styling the existing parent; add a wrapper only when no existing
element can hold the layout, as a search/replace whose `replace` adds it
around the original.
3. **Reserve `generate-design` for** net-new files. For large structural
rewrites of an existing selected file, call `edit-design` with
`mode: "replace-file"` and the exact `fileId` from `get-design-snapshot`.
Never resend files you aren't changing.
4. **Treat `:root` as the global spec.** For theme-wide restyles, edit the
tokens in `:root` rather than touching every element.
5. **Don't add unrequested features** during a refinement pass.
For selecting/editing DOM elements as code layers (layer projection, the
deterministic `apply-visual-edit` slice, and the semantic React/TSX handoff),
read `references/code-layers.md`. For the VS Code-style source workbench
(explorer, quick open, `list-source-files`/`apply-source-edit`), read
`references/code-workspace.md`.
## Tailwind v4 + motion gotchas
- **Gradients:** Tailwind v4 renamed the utilities. Use `bg-linear-to-r`,
`bg-radial`, `bg-conic` — the v3 `bg-gradient-to-*` classes silently do
nothing on the v4 browser CDN.
- **Respect reduced motion.** Wrap non-essential animation so it's disabled for
users who ask for it (already included in the mandatory `<style>` below).
- Use Tailwind scale steps, not arbitrary `[…px]` values, unless truly needed.
## What NOT to Do
- For aesthetics (fonts, palette, layout, backgrounds, copy), see the Aesthetic
quality bar above — every banned default there is out unless the user asks.
- Never link prototype screens with real/relative URLs — use Alpine state,
`data-screen`, or `#` anchors (see Multi-screen prototypes & navigation).
- Never hardcode colors — always reference CSS custom properties (no raw
`text-white` / `bg-black` / hex literals in markup).
- Never define or rely on a CSS custom property starting with `--agent-native-`
in generated design content. That prefix (plus `--design-editor-`) is
reserved for editor-internal state (selection chrome, editor-chrome scale
compensation, clipboard/surface tokens) — the editor strips any property
under those prefixes before persisting a cross-screen style capture
(`isEditorInternalCssVar` in `app/pages/design-editor/portable-style.ts`),
so a design that
defines its own theming under that namespace would silently lose those
values on a cross-screen move. Use `--color-*`, `--font-*`, or another
design-owned prefix for tokens/tweaks instead.
- Never use the v3 `bg-gradient-to-*` classes — use v4 `bg-linear-to-*`.
- Never use `<script>` blocks with raw DOM manipulation — use Alpine.js directives
- Never inline `onclick="..."` handlers — use `@click`
- Never use `!important` except in `[x-cloak]`
- Never forget `cursor-pointer` on interactive elements
- Never use `<img>` with placeholder/stock URLs — generate real imagery (see
Imagery below) or use tokened colored divs/gradients only for pure UI
chrome (icons, avatars-as-initials, decorative fills), never as a stand-in
for a hero, product shot, or portrait that should be a generated image
- Never set font-size below 16px for body text or 12px for labels
- Never nest wrapper divs that add no layout, paint, or spacing, and never
write fractional px values such as `left: 256.96px`
## Multi-screen consistency contract
When a design has more than one screen, the shared system must be
byte-identical across every screen file, not just similar: the `:root` token
block, the Google Fonts `<link>`, the nav, and the footer should match exactly
between `index.html` and every other screen. Before saving a new or edited
screen, diff its `:root` block against `index.html`'s (or the design system's
tokens) and reconcile any drift instead of letting each screen accumulate its
own slightly-different palette. Consistency is not sameness — keep structure
and layout varied per screen (see "Building on existing code" above) while the
token layer, typography, nav, and footer stay identical.
## Breakpoints & screen states
- **Breakpoints**: `add-breakpoint` adds a device-width frame (Framer
defaults Phone 390 / Tablet 810 / Desktop 1200, or a custom width) to the
design's breakpoint set stored in `designs.data`; duplicate widths are
ignored. `remove-breakpoint` removes one by id. `set-active-breakpoint`
sets which frame is the current edit scope. Breakpoint frames are ONE
document rendered at different widths with a Framer-style cascade: the
primary (widest) frame is the base, and edits at a narrower active
breakpoint persist as width-scoped overrides (`max-[<bound>px]:` classes
or managed `@media` rules) that cascade down. Always check the active
breakpoint before a responsive-only edit, and pass `activeFrameWidthPx`
to `apply-visual-edit` so the write lands at the right scope. Read the
`responsive-breakpoints` skill for the full model.
- **Design states**: `create-design-state` creates a named alternate
DOM/Alpine snapshot (`kind: "state"` — Loading, Empty, Error), a static data
fixture (`kind: "fixture"`), or a placeholder for a live capture
(`kind: "capture"`). `apply-design-state` updates an existing state row
(rename, change breakpoint, update fixture/capture data, set the preview
reference). `capture-design-state` records a running app's current route,
props, and API data into a `capture` row — it requires the design's source
to advertise the `captureState` bridge capability (localhost/fusion); for
inline designs without a live bridge, use `create-design-state` instead.
`list-design-states` lists all states/fixtures/captures for a design, and
`delete-design-state` removes one (irreversible; the design itself is
unaffected).
## Component reuse
Before hand-rolling another near-duplicate card/button/nav item, check
whether the pattern already exists as a recognised component. Once a visual
pattern repeats 3+ times in a design, promote it: call `create-component` on
the selected root element to stamp deterministic
`data-agent-native-component="<Name>"` and `data-agent-native-prop-*`
annotations, so it becomes a recognised component instance for the canvas
outline and the Component inspector section. Use `index-components` to scan a
design's HTML for existing `data-agent-native-component` annotations and
persist the discovered component list before inventing something that may
already exist. `get-component-details` returns a selected instance's name,
props, variants, and source info. `preview-component-prop-edit` previews a
prop/class change on the canvas without saving; `apply-component-prop-edit`
persists it. `open-component-source` navigates to the component's source
location (the design file for inline/Alpine designs, or the resolved external
file for localhost/fusion sources).
## Suggested auto layout
For an absolute/freeform container, first measure its direct children and
present the proposed direction, visual order, gap, four-side padding,
alignment, and sizing — do not mutate source until the user applies the
preview. Inline HTML/Alpine applies the reviewed proposal through one
`apply-visual-edit`-backed content transaction so undo restores the exact
prior structure. Local React uses the semantic source handoff (see
`references/code-layers.md`, never generic AST rewriting), preserves nested
absolute descendants and responsive logic, and applies the approved proposal
as one reversible source edit.
## Editor extensions
Design editor extensions render in the right inspector slot
`design.editor.inspector` (`create-extension` →
`add-extension-slot-target` → `install-extension`). Read
`references/editor-extensions.md` for the context shape and the AI-driven
style/artboard change flow.
## Realistic app-state content
For app/product UI (not marketing pages), populate lists and tables with
plausible mid-life data — not a pristine "just signed up" empty account and
not obviously fake placeholder rows (avoid "Lorem Ipsum User", "Item 1", "Item
2"). Include at least one realistically long name/title/label so truncation
and wrapping behavior is visible. Always design the empty state and a loading
skeleton for the screen's primary data surface — don't only show the
happy-path populated state.
## Motion craft
Push motion the same way you push type and color: on purpose, not as a
uniform default. Duration bands: 150-250ms for micro-interactions (hover,
toggle, button press), 300-500ms for panel/sheet/modal transitions, and
500-800ms reserved for exactly one orchestrated page-load reveal (stagger
individual elements 60-100ms apart, capped at about 6 staggered elements —
more than that reads as sluggish, not polished). Ease functions: `ease-out`
for elements entering, `ease-in` for elements leaving. Animate only
`transform` and `opacity` for performance; avoid animating `width`/`height`/
`top`/`left`. Every non-essential animation must respect
`prefers-reduced-motion` (see the mandatory `<style>` block below). For
motion edits, keep a versioned keyframe timeline as the editable source
model, then compile it back into the design code. Inspect the selected screen
with `get-motion-timeline` before writing. Inline HTML/Alpine uses its `fileId`
as `sourceRef`; a localhost React/TSX screen uses the same selected screen as
`sourceRef` plus the live module path, connection id, and version hash in
`localSource`. Write through `apply-motion-edit` so edits stay parseable and
round-trip between timeline and CSS/JSX. The inline output is standard CSS
`@keyframes`; the React adapter updates verified JSX/source spans and emits the
same managed CSS contract.
Inline HTML `<style>` elements and React JSX `<style>` elements can adopt static
CSS animations from top-level rules that use one uniquely resolved simple
selector, one `animation-name`, and a matching top-level `@keyframes` block.
React requires a static id selector with one matching static JSX host and one
document-unique id. JSX `<style>` CSS is global, so React import does not adopt
class, type, or attribute selectors. React also requires the complete style
content to be one lossless JSX text or string/template literal with no
substitutions. Dynamic style elements and style props that cannot be proven
free of motion are unsupported. Importable animations use a positive fixed
`ms`/`s` duration, a non-negative fixed delay, a Motion-supported easing,
`animation-fill-mode: both`, one iteration, normal direction, running state,
replace composition, and static keyframe stops from 0% through 100% with both
endpoints and one or more supported properties: `opacity`, `transform`,
`translate`, `scale`, `rotate`, `color`, `background-color`, `border-radius`,
`border-color`, `border-width`, or `box-shadow`. Each property needs static 0%
and 100% endpoints, and duplicate declarations at one stop are unsupported;
Motion maps properties to parallel tracks with shared target and animation
timing. Each target can have one imported animation. Shorthand, multiple
animation names, conditional animation
definitions, ambiguous targets, CSS variables in keyframe values, and external
stylesheets are unsupported; the source reader fails closed so Motion does not
silently discard or flatten them. The exact global
`prefers-reduced-motion: reduce` override generated by this skill is preserved
and does not change the imported timeline.
On the first successful `apply-motion-edit` after import, Motion removes only
the adopted animation declarations from the inline CSS and writes them into
its managed stylesheet. Global `@keyframes` definitions and unrelated
declarations remain in place because other rules may use them.
`remove-motion-timeline` removes the managed stylesheet, adopted animation
declarations, and Text runtime; it leaves authored keyframe definitions in
place. For localhost source, pass the current `sourceVersionHash` returned by
`get-motion-timeline` as `timelineVersionHash`; stale or missing hashes preserve
the source and timeline so you can reload before retrying. This CSS importer
does not read or edit JavaScript runtime animations, GSAP timelines, Web
Animations API calls, or Alpine `x-transition`/`x-effect` behavior. Generic
Alpine `x-bind` on a Motion target is unsupported because it can change
attributes dynamically. In localhost React/TSX, write through the verified
module path, connection id, and version hash in `localSource`. Include the
current `moduleVersionHash` and captured `timelineVersionHash`; autosave
compares both before writing so a code edit cannot replace the source
underneath a dirty timeline. React inline `style` props such as
`style={{ animationName: "fade" }}` are detected and rejected because they
cannot be imported losslessly. This is separate from static JSX `<style>`
elements, which can be imported under the constraints above. Keep runtime-only
animation code source-owned and do not claim that Motion round-trips it.
Text animations use `textDocument` version 2 for `animationDirection`, `order`,
`overlapPercent`, and `ignoreSpaces`; preserve their unit, stagger interval,
property, duration, and keyframes when editing. Version 1 text documents remain
readable and are normalized on write. Do not keep the only copy of animation
state in a GSAP runtime timeline or a one-way export.
## Imagery
Generate real images for anything a real product would photograph or
illustrate: hero backgrounds, product shots, portraits/avatars, testimonial
photos, marketing/editorial imagery. Don't generate images for utility UI —
icons, data tables, form chrome, and dashboard widgets should stay as
tokened SVG/CSS, not photos.
- **Use the Assets generation tool** (`generate-asset`, or `insert-asset` once
an asset is chosen) instead of `<img>` placeholder URLs or colored-div
stand-ins — for raster generation, restyling, or editing existing
screenshots/photos. Always pass `callerAppId: "design"`. If no Assets MCP
tool is available, use the first-party Assets app via `call-agent` with
agent `"assets"` when available. If the user attached an image, use its
hosted chat-attachment URL or call `upload-image` to create one before
delegating. If no image/upload provider is configured, say that specific
setup is needed and continue any non-image Design work separately.
- **Write image prompts as art direction, not a one-line label.** Specify
subject, composition, lens/framing, lighting, and palette, and tie the
palette/mood back to the design's own `:root` tokens so the image reads as
part of the same system rather than a stock photo dropped in. If a design
system is linked, fold its `imageStyle.styleDescription` into the prompt
(see `design-systems` skill) so generated imagery matches the brand's
established photographic/illustration style.
- **Default to `tier: "fast"`** (the cheap Gemini Flash "nanobanana"-class
model) for exploration and every non-final variant. Only request
`tier: "best"` for the final, user-approved hero image — not for every pass.
- **Match `aspectRatio` to the layout slot**: `21:9` for a full-bleed hero,
`4:3` for a card/feature image, `1:1` for an avatar or square thumbnail.
Mismatched aspect ratios force ugly crops in the browser.
- **Always write real `alt` text** describing the image's content — never
leave `alt=""` on a meaningful (non-decorative) image.
- **Placement is a two-step pass**: when the Assets picker returns a selected
asset, preserve its `assetId`, `runId`, and URLs verbatim; call `insert-asset`
to place the chosen image, then do one `edit-design` pass (using
`get-design-snapshot` first) to adjust surrounding layout/spacing if the
inserted figure doesn't sit flush with the rest of the design.
---
name: design-generation-565490dc1e9a
description: >-
Generate or refine Design web prototypes and fixed promotional artwork. Use
when creating screens, variants, ads, social graphics, flyers, posters, or
banners. For HubSpot email requests, read hubspot-email instead.
---
# Design Generation
Generate complete web prototypes through Design actions.
## HubSpot emails
For HubSpot email creation, alignment, token edits, conversion or HTML handoff,
read `hubspot-email` before applying the web workflow below. It owns email-safe
authoring and completion checks, which replace the web stack defaults.
## Technology Stack
Web prototypes use:
- **Tailwind CSS v4** — via `@tailwindcss/browser@4` CDN (NOT the old v3 CDN)
- **Alpine.js 3.x** — via `alpinejs@3.15.11` CDN with `defer` attribute
- **Google Fonts** — for distinctive typography (never Inter/Roboto/Arial)
- **CSS Custom Properties** — for theming and tweaks panel integration
## Why the workflow exists — it is the anti-slop engine
Generic output ("AI slop") is a *workflow* failure, not a lack of talent. When one
prompt has to set the taste, explore the options, and emit final code all at once,
the safest answer is the statistical average of the training data: Inter, an
indigo→violet gradient, a centered hero, three rounded icon cards. Design beats
this by splitting those jobs across the tools — use them in order, don't collapse
them:
1. **Direction** — `show-design-questions` (or a stated thesis) sets taste on purpose.
2. **Exploration** — `present-design-variants` compares genuinely different directions
before committing to one. **This step kills sameness; never skip it for open-ended work.**
3. **Spec** — a linked design system and the `:root` token block capture the chosen look as reusable rules.
4. **Code** — `generate-design` / `edit-design` execute a decision already made instead of guessing.
Jumping straight to code is how you get slop. Let the phases (below) do the work.
## First decide the mode: reproduce or explore
The workflow above assumes an under-specified prompt. When the user has already
specified the design, that same workflow is the failure — exploration reads to
them as "the agent ignored everything I gave it".
Before Phase 1, check for **specification signals**:
- an attached screenshot, mockup, or photo of a UI,
- a linked design system / Brand Kit,
- a written outline naming concrete screens, sections, components, or copy,
- a named existing product/page to match, or pasted markup or tokens.
**Any one of these puts you in reproduce mode.** In reproduce mode:
- Do **not** call `present-design-variants`. Three divergent directions when the
user handed you one is not exploration, it is discarding their work. Produce
the one thing they specified.
- Do **not** call `show-design-questions` about anything the supplied material
already answers. Ask only about a genuine gap, and prefer stating an
assumption over asking.
- Every "Do" in the aesthetic quality bar below is **off** wherever it would
contradict the supplied material. Those rules break ties in the absence of a
spec; they never outvote one.
- Reproduce first, improve second. Match the structure, then raise quality
*within* it, and say what you changed and why.
Mixed input is normal: reproduce what was given, use the quality bar only for
the parts genuinely left open. When a supplied reference and the linked design
system disagree, follow the design system for tokens (color, type, spacing,
radius) and the reference for layout and composition, then say you did.
### A reference screenshot is a specification, not a mood board
When the user attaches an image of a UI, read it as a layout spec and
reproduce it: the same regions in the same positions, the same navigation
pattern, the same information hierarchy and density, the same component
grammar (tabs vs. pills, cards vs. rows, sidebar vs. topbar), the same
approximate proportions. Name what you see before you build, so the user can
correct a misread early. Deviate only where the image is genuinely unreadable
or an explicit instruction overrides it — and say which, rather than
silently substituting your own composition.
An attached image is only guaranteed to be visible on the turn it arrived.
If a screenshot was described earlier in the thread but you cannot see one now,
say so and ask for a re-attach — never quietly design from the text alone.
## Authority and direction record
Before generating, make a compact direction record: the job this surface must
do, its audience, one-sentence visual thesis, the active design-system id or
token source, approved references, and any unresolved choice that could change
the result. This keeps a strong point of view without asking the model to fill
in the user's intent silently.
Resolve visual authority in this order:
1. The current-turn product, audience, content, accessibility, and explicit
brand constraints.
2. The active Agent-Native design system and its registered tokens, components,
type, imagery rules, and custom instructions.
3. Approved Creative Context assets, components, and examples.
4. Impeccable-inspired quality heuristics for hierarchy, subtraction,
composition, contrast, motion, and bounded review.
5. Generic defaults only when the sources above are absent.
The active design system is a contract, not a mood board. Do not swap its
palette, fonts, component grammar, or imagery policy because a generic
anti-pattern list prefers something else. A user can explicitly request a
replacement or detachment; do not infer that request from a vague adjective.
References can guide composition and narrative, but they do not silently
transfer their tokens or brand identity.
When the source is a transcript, meeting note, or pasted brief, treat it as
evidence rather than a finished story. Preserve the user's terminology and
exact claims, separate facts from interpretation and visual references, and
ask one targeted question or mark an assumption when a missing choice would
change the design. Never smooth an evidence gap into confident copy.
## Aesthetic quality bar — beat distributional convergence
The rules in this section are fallback quality heuristics for prompts that
leave the look open. They do not override an active Agent-Native design system,
a supplied reference, or an explicit brand constraint. **A banned item stops
being banned the moment the user's own material specifies it** — if their Brand
Kit's body font is Inter, the design ships Inter; if their screenshot is a
centered hero with three icon cards, the design ships that. Substituting a
"better" choice for a specified one is the single most-reported failure of this
app. When you follow a specified value that appears on a "don't" list below,
just follow it — no apology, no note, no compensating flourish elsewhere.
You sample toward the "on-distribution" center by default; refuse it. **Every
"don't" here carries a "do"** — a banned default plus where to go instead —
because banning Inter alone just makes you reach for Roboto next. Use a banned
item only if the user explicitly asks, or their design system or reference
already uses it.
- **Fonts.** Don't: Inter, Roboto, Arial, Open Sans, system-ui. Do: a distinctive
Google Font pairing matched to the chosen aesthetic (see the table) — editorial
serif, grotesk display + mono, or one variable font pushed across weight extremes.
- **Color.** Don't: the indigo/violet slop palette (`#6366F1`, `#8B5CF6`,
`#A855F7`), a purple gradient on white, or everything in default grays. Do:
anchor on one non-default family — clay/ochre/terracotta, ink/bone/mustard,
charcoal/lime, oxblood/cream, navy/copper, warm paper (`#FBF7F0`) over pure
white — with one decisive accent used sparingly for hierarchy, not decoration.
- **Layout.** Don't: centered hero + one CTA + a row of three icon cards,
rounded-everything, `0.1`-opacity drop shadows, blanket glassmorphism, or the
badge-above-headline cliché. Do: asymmetric 60/40 or 70/30 splits, uneven
visual weight, one clear focal point, and flat confident surfaces.
- **Background.** Don't: a single flat fill. Do: layered gradients, a geometric
pattern, grain, or a contextual texture that matches the theme.
- **Copy & voice.** Don't: lorem ipsum or buzzword filler ("empower", "seamless",
"leverage", "revolutionize", "in today's fast-paced world"). Do: realistic
domain content in a specific voice — copy is design material.
**Second-order convergence is real.** Even your "creative" picks converge (Space
Grotesk everywhere; teal accent + blinking dot + left accent bars). Vary
deliberately across generations so two designs never share a fingerprint —
but only across designs *you* had to invent. A token the user's own system
specifies is theirs, not your fingerprint: consistency with their brand is the
goal, and varying away from it to avoid repeating yourself is a bug.
**Principles to quote back while building:** color creates hierarchy, not
decoration · density over decoration · earn every animation · commit to one point
of view. **Match code to the vision** — maximalist themes want elaborate motion
and effects; minimal themes want restraint and precise spacing. Elegance is
executing one vision fully.
**References beat adjectives, but only with a reason.** "Linear: the quiet
confidence of its spacing" or "Stripe: dense but never crowded" points somewhere
specific; "Linear" alone collapses back to the average, and replying to your own
output with "make it cleaner / more premium" means you're negotiating with vibes.
## Prompt the design in four layers
Decide each layer explicitly before writing HTML. This is what makes variations
genuinely distinct instead of three near-identical layouts:
1. **Context** — audience, domain, and the one job this screen must do.
2. **Structure** — a named layout topology: bento grid, sidebar app shell,
editorial column, split-screen, masonry, dashboard tiles.
3. **Aesthetic** — a named visual movement: editorial serif, neo-brutalist,
glassmorphic, Swiss/International, warm organic, technical/mono, etc. Each
variant gets a *different* aesthetic + font pairing.
4. **Tech stack** — Tailwind v4 + Alpine, mobile-first, light/dark via tokens.
Push each dimension to extremes rather than safe middles: weight extremes
(100–200 vs 800–900), 3×+ type-scale jumps, one dominant color + a single sharp
accent, layered gradient/pattern backgrounds (not flat fills), and one
orchestrated staggered page-load reveal (via `animation-delay`).
Pick a preset by `projectType`:
- **Brand / marketing** (landing pages, decks): expressive, atmospheric,
animated, full-bleed.
- **Product / app** (dashboards, tools): dense, restrained, token-driven, fast.
## Measurable rules (bake these in)
- 8px spacing grid — all padding/margins/gaps are multiples of 4/8.
- Every px length is a whole number: position, width, height, padding, gap,
radius, font size, translate. Absolute `left`/`top` are also multiples of the
screen's `layoutGrid.size` when `design-selection` reports one. A fractional
or off-grid value reads as a mistake to a designer and does not match what
dragging the same element produces. Set a screen's grid with
`set-layout-grid`.
- Flat layer tree. Every element is a layer the user selects, drags, and
resizes, so each one must earn its place: one flex or grid container per
visual group, content directly inside it, spacing from `gap` and `padding`.
A wrapper with a single child and no layout, paint, or spacing of its own is
a defect. Sections are usually three or four levels deep.
- Body text ≥ 16px, labels ≥ 12px.
- Big type-scale jumps for hierarchy (don't rely on tiny size deltas).
- WCAG contrast: 4.5:1 normal text, 3:1 large text. Verify accent-on-background.
- Mobile-first breakpoints; never ship a layout that breaks under 640px.
- No arbitrary one-off Tailwind values where a scale step exists.
- **Token grounding is non-negotiable:** every color/font/radius references a
`:root` CSS variable. Never hardcode `text-white` / `bg-black` / hex literals
in the markup — that's what keeps brand + multi-screen consistency automatic.
### Type-scale recipe
Use this as a starting scale, then adjust to the chosen Aesthetic:
- Display: 56-96px · H1: 40-64px · H2: 28-36px · Body: 16-18px · Caption: 12-13px.
- Each adjacent step should be at least 1.25× the one below it — smaller jumps
read as "almost the same size" rather than a deliberate hierarchy.
- A hero/display line should be at least 3× the body size.
- Line-heights: display/H1 tight at 1.05-1.15, H2/H3 at 1.2-1.3, body relaxed
at 1.5-1.7.
- Measure (line length) for body copy: 60-75 characters; constrain with
`max-width` in `ch` units, not a raw pixel guess.
### Section rhythm
Pick one section padding value and repeat it for every top-level section on
the page/screen: 96-128px on desktop, 48-64px on mobile. Don't let each
section invent its own padding — that's what makes a page feel unplanned.
Use spacing to encode grouping: gaps *inside* a card or cluster should be
visibly smaller than the gap *between* cards/sections — if inside-group and
between-group spacing match, the eye can't tell where one group ends and the
next begins.
### Verifiable contrast pairs
Don't just assert "WCAG AA" — check the actual token pairs the design ships.
The most common real failure is muted text (`--color-text-muted`) directly on
a card/surface background rather than the page background; verify that pair
specifically, not just text-on-page. If an accent color doubles as text (a
link, an active nav item, a price), it usually fails 4.5:1 against typical
surfaces — add a separate `--color-accent-text` variant tuned for text-on-
background contrast rather than reusing the decorative accent for copy.
### Richer tokens
Go beyond the minimal `:root` block in the HTML Structure Requirements below
when the design needs it — add `--space-section` (see Section rhythm),
`--color-border`, `--color-accent-text` (see contrast pairs above),
`--shadow-card`, and a success/warning/danger trio
(`--color-success` / `--color-warning` / `--color-danger`) once the design has
status states, alerts, or form validation to express. Keep font tokens as
placeholders you fill per design (see HTML Structure Requirements) rather than
hardcoding a concrete family in a shared template.
## Building on existing code, screens, or a design system
When a design system, tokens, current screens, or a connected codebase already
exist, the slop risk flips: the failure is ignoring the brand and reverting to
defaults. The banned-defaults list above still applies, plus:
- **Inspect before inventing.** Read the linked design system, the current
`:root` tokens, and existing screens (or the connected localhost/repo) first.
Derive the type scale, palette, radius, density, and component language from
what is actually there — don't restate a generic direction.
- **Treat every reversion as a missing spec entry.** If output drifts to a
default (Inter, pill buttons, a stock radius) despite the brand, don't just
re-prompt — pin the explicit value into `:root` so it can't drift again.
- **Consistency is not sameness.** Tokens alone make every screen "the same in
your colors". Keep structure and layout genuinely varied per screen while the
palette, type, and components stay on-brand.
## Generation Application State
- `design-generation-session:<designId>` — multi-screen generation planning
state from `generate-screens` (canvas region assignments, per-frame
instructions consumed by `generate-design`).
- `show-design-questions` opens pre-generation questions in the main canvas
(`show-questions` state).
- `guided-questions` may hold a one-click chat choice for the current variant
set.
## Generation Workflow — the canonical 5-phase flow
This flow mirrors Claude Design's UX: clarify only what's unclear → show variants → user picks → refine. Don't collapse phases into one shot for new, open-ended designs.
### Creative-context gate
Before Phase 1, read the `creative-context` skill and retrieve components,
interaction examples, visual style, and factual evidence as separate roles.
Respect `contextMode: "off"` and pinned packs. Apply its reuse ladder exactly:
use an approved native component/template/asset unchanged, compose approved
pieces, lightly adapt a real example, generate from narrowly retrieved
references, then generate net-new only when the relevant corpus is empty.
Retrieval is a separate operation from `generate-screens`, `generate-design`,
or `present-design-variants`.
Keep the selected immutable `contextPackId` and reuse labels on the generation
session and every resulting screen/variant. Rendered HTML and screenshots are
not provenance. App-local design systems and components remain the fallback
when shared retrieval finds no relevant evidence.
### Phase 1 — Create the project + ask before generating
```bash
pnpm action create-design --title "Project Name" --projectType prototype
pnpm action navigate --view editor --designId <returned-id>
```
The `navigate` step is only for first-party/local app agents that can write
application state. External MCP hosts should surface the `create-design`
returned "Open design" link, then use `present-design-variants` to open the
visual picker.
Then, for a brief or ambiguous first prompt to a **new** design, call
`show-design-questions` BEFORE generating. The editor renders a full-canvas
overlay; answers come back as a chat message. Size the question count to how
much is actually unresolved — most prompts warrant 2-4 questions, not the full
1-8 range — and never ask about something the prompt already specified (a
stated color, audience, or layout is settled; don't re-ask it as a choice).
Skip the questions entirely when:
- the prompt is already specific enough to generate from (names the audience,
purpose, and a visual direction, e.g. "a dark, data-dense analytics
dashboard for ops engineers" or "re-skin this with my brand colors");
- it's a tweak/edit/refinement to an existing design rather than a new one —
go straight to `edit-design` (see Phase 3 and "Making edits" below);
- the user already answered a question set for this design and is now
iterating — don't re-ask settled ground on follow-up prompts for the same
design; or
- the user says "decide for me," "surprise me," "just build it," or similar.
The turn that opens `show-design-questions` already checked Creative Context
and any published Brand DNA before this skill loads, and the directives it
built name exactly which topics (form factor, aesthetic direction,
features/content, interactions/polish, exploring variations) are already
answered. Never re-ask a topic those directives list as covered, and never
skip a topic they don't — a single unrelated context member (a doc, a spec)
must not suppress a question it has no bearing on.
Asking on every prompt is as much a failure mode as never asking: a detailed
prompt that already answers the obvious questions should generate
immediately, and a design already in flight should not be interrupted with a
second questionnaire.
```bash
pnpm action show-design-questions \
--designId "<id>" \
--title "Quick questions about your todo app" \
--questions '[{"id":"form_factor","type":"text-options","question":"What form factor?","options":[{"label":"Desktop web app","value":"desktop"},{"label":"Mobile app","value":"mobile"},{"label":"Both / responsive","value":"responsive"},{"label":"Decide for me","value":"decide"}],"allowOther":true}]'
```
Favor choice-first questions (2-5 concrete options, `allowOther: true`, and a
"Decide for me" option when any answer is acceptable) over open-ended
`freeform` text, and avoid `multiSelect` unless the question genuinely allows
combining multiple answers — stacking multi-select questions multiplies
follow-up ambiguity instead of resolving it.
**Carry the form-factor answer through to generation — do not just ask and discard it.** Map the answer to the design's device SET, not to separate per-device screen files. Device widths of the SAME page are breakpoint frames of one document (see the `responsive-breakpoints` skill), never a `mobile.html` + `desktop.html` pair. Pass the answer through `generate-design`'s `devices` param — `("mobile"|"tablet"|"desktop")[]`, default `["desktop","mobile"]`:
- An exact pixel size in the original user request defines one fixed-size canvas. Use those exact dimensions and pass `devices: []` unless the user explicitly asks for device variants; then preserve exactly those requested devices. For `present-design-variants`, use `responsive: false` for a single fixed canvas. Keep each action prompt to one distinct exact canvas size; make separate calls scoped to each screen when a request names different sizes.
- Ads, banners, social posts/stories, flyers, posters, logos, and other static artwork are fixed canvases, not responsive apps. Do not add mobile or tablet breakpoints unless the user explicitly asks for device variants; if they do, preserve exactly those choices and add no others. If a `present-design-variants` chat caption is generic, pass the original request separately as `brief` so its size and output type stay attached to the variants.
- For a follow-up on an existing fixed artboard, read its exact frame geometry first. Content-only edits keep that size. When regenerating it for a resize or named-format follow-up (for example, “make it taller” or “now a story version”), target the same file with `generate-design`, pass `devices: []`, and include one `canvasFrames` entry with exact numeric `width` and `height` plus its current placement. Preserve the unchanged dimension and resolve the new one from the conversation or the named format's canonical preset (Instagram Story is 1080×1920). Never let device defaults add breakpoints.
- If the prompt/answer names specific devices, generate EXACTLY those, deduped ("mobile" only → one mobile frame; "mobile, tablet, desktop" → all three).
- If no exact pixel size or form factor is specified — or the answer is "Both / responsive" or "Decide for me" — default to `["desktop","mobile"]`: a Desktop base + a Mobile frame only. Never auto-add a tablet, a redundant desktop, or a stray duplicate frame.
The WIDEST requested device is the base/primary frame; narrower devices become breakpoint frames (never at the primary width). Device frame sizes: mobile 390×844, tablet 768×1024, desktop 1440×900. `present-design-variants` takes explicit `width`/`height` per variant to size its exploration screens (see Phase 2). Pass the original request in `brief` when the chat caption is generic; dimensions and output intent come from that brief, never variant labels or feature text.
### Phase 2 — Generate side-by-side variations (2-5, three by default)
Skip this phase entirely in reproduce mode (see "First decide the mode" above) —
a screenshot, a written outline, or a linked design system means the direction
is already chosen, and offering three alternatives to it is the behavior users
report as "the agent ignored my design".
For new designs whose direction is genuinely open, default to **three**
variations (`present-design-variants` accepts 2-5; three is the sweet spot).
Call `present-design-variants` for both first-party and external MCP-host
flows. It saves each candidate as a normal
overview-board screen, then renders an inline chat choice with one button per
screen name.
```json
{
"designId": "<the design id>",
"prompt": "Pick a direction",
"brief": "<the original request, including its exact canvas dimensions>",
"variants": [
{ "id": "a", "label": "Editorial Serif", "width": 1440, "height": 900, "content": "<!DOCTYPE html>...full self-contained HTML..." },
{ "id": "b", "label": "Bold Brutalist", "width": 1440, "height": 900, "content": "<!DOCTYPE html>..." },
{ "id": "c", "label": "Soft & Spacious", "width": 1440, "height": 900, "content": "<!DOCTYPE html>..." }
]
}
```
Each `content` is a complete, self-contained document (Alpine.js + Tailwind via CDN, full `<head>`, CSS variables in `:root`). Variations should be **structurally and compositionally distinct** — different layout grammars, hierarchy, density, and focal points — never just color swaps. When a design system is linked, keep its tokens, typography, components, and imagery rules fixed across variants; vary those only when the user explicitly asks to explore a replacement system. Label the directions with concrete names ("Editorial split", not "Variant A").
Pass `width`/`height` on every variant to match the form-factor answer (mobile ≈ 390×844, tablet ≈ 768×1024, desktop ≈ 1440×900) — the example above is desktop-sized. When the original brief specifies exact pixel dimensions, use that exact size on every variant; Design will preserve it even if a variant supplied another size. Static artwork has no responsive frames even if `responsive: true` is passed. Omit `content` only for open-ended app exploration: a fixed canvas, attached reference image, layout spec, or linked design system rejects direction-only variants. When `content` is omitted, `present-design-variants` infers a size from the brief/label/description text and the width/height you pass still wins when given.
Wait for the user's pick before refining. Once they choose, keep the selected
screen, delete the unchosen variant screens with `delete-file`, and continue
from the kept screen by calling `get-design-snapshot` with the selected
screen's `fileId`, then calling `edit-design` on that same `fileId`. Use
`mode: "replace-file"` when expanding the representative placeholder into the
full chosen direction. Do not call `generate-design` after a variant pick. If
inline chat choice buttons are unavailable in the host, ask the user to tell you
the preferred screen name. Do not ask them to paste HTML or a generated handoff
summary; the variants are already real screens on the board.
### Phase 3 — Save with `generate-design` (when not using variants)
Skip variants and call `generate-design` directly for: a brand-new first
renderable file, multi-screen additions to an existing design, or one-shot
prompts where the direction is unambiguous. For refinements to an already-picked
design or selected screen, use `get-design-snapshot` followed by `edit-design`
instead.
```bash
pnpm action generate-design \
--designId "<id>" \
--prompt "Description of the design" \
--files '[{"filename":"index.html","content":"<full HTML>","fileType":"html"}]' \
--tweaks '[{"id":"accent","label":"Accent","type":"color-swatch","options":[...],"defaultValue":"#0EA5E9","cssVar":"--color-accent"}]' \
--devices '["desktop","mobile"]' \
--canvasFrames '[{"filename":"index.html","x":0,"y":0,"width":1440,"height":900}]'
```
Pass the `devices` param (`("mobile"|"tablet"|"desktop")[]`, default `["desktop","mobile"]`) so a new design renders the right device frames: the widest device becomes the primary `canvasFrames` placement and each narrower device is added as a breakpoint frame of the SAME document — not an extra file. Default to Desktop + Mobile when the form factor is unspecified; never auto-add a tablet or a redundant desktop. When you also pass `canvasFrames` explicitly, size the primary to the widest device (mobile ≈ 390×844, tablet ≈ 768×1024, desktop ≈ 1440×900) — a screen saved without a placement falls back to a generic default that won't match the intended device. For genuinely distinct screens generated together (Home / Dashboard / Checkout — NOT per-device copies of one page), call `generate-screens` first; it returns the matching `canvasFrame` to forward to each `generate-design` call.
#### Non-web sizes — ad units, print one-pagers, social sizes
`canvasFrames` accepts exact `width`/`height` in px, so "create a 300x250
ad" style requests work the same way — use the requested dimensions verbatim
and pass `devices: []`. Keep each action prompt to one distinct exact
size; use separate calls for screens with different dimensions. This also
applies to exact-size email and social assets. The editor's own Frame tool preset
list (`shared/frame-size-presets.ts`) documents the
canonical sizes to reuse instead of guessing:
- **Ad units (IAB standard)**: Medium Rectangle 300×250, Leaderboard 728×90,
Wide Skyscraper 160×600, Mobile Leaderboard 320×50, Billboard 970×250.
- **Print (96dpi CSS px, matching this app's PNG/PDF export unit)**: US
Letter 816×1056, A4 794×1123, A5 559×794, Tabloid 1056×1632. Design print
one-pagers with real multi-column layout, tables, and a footer — a
fixed-size print artifact has no responsive fallback, so lay out the exact
canvas once. The editor's Download PDF export renders these at a
print-quality floor (2x raster, ~192dpi) regardless of the export panel's
default scale setting.
- **Social and platform assets**: LinkedIn Single Image Ad 1200×627 (distinct
from LinkedIn Cover 1584×396), Meta Feed Square Ad 1080×1080, Meta Feed
Landscape Ad 1200×628, Instagram Portrait Post 1080×1350, Instagram Story
1080×1920, Open Graph Image 1200×630, YouTube Thumbnail 1280×720, X Promo
Graphic 1200×675, Email Header 600×200, X Post 1200×675, and Facebook Cover
820×312.
Frame geometry is always numbers — `"width": 800`, never `"800"` or `"800px"`. String dimensions are rejected.
At small ad-unit sizes (320×50, 160×600), text commonly runs 9-11px — smaller
than this skill's general 16px body-text floor — because there is no room to
reflow. Higher-resolution exports make edges sharper, but do not increase text's
relative size when the asset is displayed at its intended dimensions.
### Promotional artwork typography at delivery size
For ads, social graphics, flyers, posters, and banners, size and space type for
the fixed canvas and where the asset will appear. The web prototype type scale
and page section rhythm above are not defaults for artwork. If the user supplies
a `design.md` or Brand Kit, use its typography, spacing, color, and voice as the
source of truth and ground the output in those tokens.
Use the canvas's shorter edge as a starting measure: on feed graphics and
poster-like formats, a headline around 6–10% of that edge and supporting text
around 2.5–4% often gives a clear first hierarchy (about 65–108px and 27–43px
on a 1080px square; 38–63px and 16–25px on a 1200×627 canvas). Treat these as
starting ranges, then adjust for the brand's typeface, copy length, composition,
and intended display scale; raise type or recompose if the display-size check
fails. Choose weights by role from the brand-approved type tokens: give the
headline the strongest available emphasis, keep date/details quieter, and give
the CTA enough emphasis to scan. If the brand offers one weight, use size,
position, or contrast for hierarchy instead of inventing another. Estimate
displayed type size as `authored px × displayed width / canvas width`: 36px type
on a 1080px-wide asset appears about 12px tall in a 360px feed slot. A 2× export
adds pixel density, not apparent size. On short banners and small ad slots, use
a compact message and size type against the available height instead of
carrying over those ratios. Set line length, line height, safe margins, and
spacing from the actual canvas so the headline leads and supporting copy stays
readable. When text does not fit, shorten or recompose it before shrinking
every line.
### Phase 4 — Always ship tweaks with the design
`generate-design` accepts a `--tweaks` array — pass 3-6 of the most impactful knobs bound to CSS custom properties the design's `:root` block actually defines. Surface controls users will actually want to adjust (accent color, density, radius, dark-mode toggle, font choice). Don't ship a generic preset; let the design's structure pick the knobs. When a user asks to add a tweak control to an existing design, preserve the existing useful tweaks and add/update only the requested definitions — read the current file with `get-design-snapshot` first if source edits are needed, and persist the complete updated tweak list through `generate-design`.
### Phase 5 — Audit, screenshot, fix, and eyeball before calling it ready
Run `run-design-audit` against each screen (`designId` + `fileId`/`filename`).
It returns `A11yFinding[]` covering missing alt/labels, tap-target size,
focus-visibility, reduced-motion coverage, a contrast hint, and — for
multi-screen designs — token drift against `index.html`'s `:root` block. For
every `error`-severity finding with `fixAvailable: true`, call
`apply-a11y-fix`; for findings that aren't auto-fixable (missing alt text,
structural issues, token drift), fix them directly with `edit-design`. **A
design with audit errors is not ready** — don't report a design as done while
`run-design-audit` still returns unresolved errors.
A `design-system-drift` finding means the screen uses none of the linked
system's fonts, colors, or token names. That is a generation bug, not a style
disagreement: the user linked that system precisely so the output would use it.
Call `get-design-system` for the linked id, apply the real values through
`edit-design`, and re-audit — never resolve it by explaining the deviation or
by unlinking the system. It is the one check that can catch a design that
looks fine and is still not the user's.
After the audit is clean, call `take-design-screenshot` on each changed screen
(default: 1280px desktop + 375px mobile). For an exact-size screen, capture
only its specified viewport by passing `widths: [width]` and
`heights: [height]`. Fix everything its `diagnostics`
report flags — real computed contrast ratios, horizontal/container overflow,
broken images, zero-size or off-screen text, console errors — before reporting
the design as ready; this is the same "visually inspect the result" pass, done
against the real rendered DOM/CSS instead of by eye. If Chromium isn't
available in the current environment, it returns `{ ok: false, reason }`
instead — fall back to a careful read of the HTML plus the audit findings. The
returned screenshot `url` is for human review (embed it as `` in
your reply); also still scan the rendered output yourself for anything the
diagnostics don't catch — broken hierarchy, empty/loading/error states for app
UI, and whether the copy/content still sounds real. To compare two design
snapshots/branches for a file-level visual diff (added/removed/modified) —
e.g. after a large refactor or before/after a review pass — call
`get-design-review` instead of eyeballing both versions.
For fixed promotional artwork, inspect the exact-size screenshot at 1:1 and at
the intended display scale (for example, as a feed thumbnail). Confirm the main
message reads first and the supporting copy remains legible at that scale; if
it fails, revise the type, spacing, or copy and capture it again. Export scale
alone does not fix type that is too small for the intended display.
After generation or a broad update, leave the user in the screen overview
(`navigate --view editor --editorView overview`) when the work involves
multiple screens or artboard placement — it's the primary editing surface for
selecting, moving, resizing, and entering focused single-screen editing via a
frame's Interact button. Reserve single-screen mode as the default landing
view only when the user asked to focus one specific screen.
## HTML Structure Requirements
### The save gate rejects malformed markup
Every path that persists model-authored HTML — `generate-design`, `create-file`,
`present-design-variants`, `edit-design`, `update-file`, canvas edits — runs an
integrity check, and a **new** file is held to it strictly. Import paths
(Figma, localhost, templates, duplication) deliberately do not fail closed:
that markup comes from a real app or an existing design, and rejecting it would
block bringing work in rather than prevent a bad generation.
Rejections name the file, line, column, and offending source line — fix exactly
what it points at instead of re-sending the payload differently. Blocked: an
attribute quote that is never closed; an unclosed element or a closing tag with
no opener; content ending mid-tag (a payload cut off in transit); a
`<style>`/`<script>` missing its opener or closer; duplicated editor-managed
style blocks; more than one `<html>`/`<body>`.
This exists because the HTML parser never throws — it recovers silently and
renders a page, just not the one you wrote. An unterminated quote in `<head>`
swallows every following tag into that attribute, so the Tailwind runtime stops
applying and the screen renders unstyled with no console error to show it.
Saves may also return non-blocking `warnings` (e.g. utility classes with no
reachable Tailwind runtime). Fix those before reporting the design as ready.
### Mandatory Elements
Every `index.html` must include:
```html
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><!-- Design title --></title>
<!-- Tailwind v4 browser runtime -->
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<!-- Alpine.js — MUST be deferred -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.15.11/dist/cdn.min.js"></script>
<!-- Google Fonts — pick distinctive fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=...&display=swap" rel="stylesheet">
<style>
/* Required: hide elements until Alpine initializes */
[x-cloak] { display: none !important; }
/* Required: CSS custom properties for theming */
:root {
--color-primary: #0F172A;
--color-accent: #0EA5E9;
--color-surface: #1E293B;
--color-text: #F8FAFC;
--color-text-muted: #94A3B8;
--font-heading: '<HEADING_FONT>', sans-serif; /* pick per Font Recommendations below — do not default to Space Grotesk */
--font-body: '<BODY_FONT>', sans-serif; /* pick a pairing, not a repeat of every other generation */
--radius: 12px;
}
/* Base styles */
body {
font-family: var(--font-body);
min-height: 100vh;
min-height: 100dvh; /* fill the frame — see Full-height frames below */
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading);
text-wrap: balance;
}
p { text-wrap: pretty; }
/* Respect users who ask for less motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
</style>
</head>
<body class="bg-[var(--color-primary)] text-[var(--color-text)]">
<!-- Root wrapper fills the device viewport: prefer min-h-screen over inline height:100vh -->
<div x-data="{ /* component state */ }" class="min-h-screen">
<!-- Content -->
</div>
</body>
</html>
```
**Full-height frames.** Overview frames now default to at least the device
viewport height and grow to fit their own content at their own width
(Framer-style), so a short page must still fill its frame instead of leaving a
stubby box. The mandatory `body` rule above sets `min-height: 100dvh` (with a
`100vh` fallback) for exactly this. Put full-height intent on the top-level
wrapper with Tailwind's `min-h-screen` rather than raw inline `height: 100vh` —
the editor keeps device-anchored full-height utilities pinned to the device
viewport, so a full-height hero fills the frame without triggering runaway
growth.
### Alpine.js Patterns
**State management** — use `x-data` on the highest-level container:
```html
<div x-data="{
currentPage: 'home',
mobileNav: false,
theme: 'dark',
items: [
{ id: 1, title: 'Item 1', active: false },
{ id: 2, title: 'Item 2', active: true }
]
}">
```
**Conditional rendering** — use `x-show` with transitions (not `x-if` for simple toggles):
```html
<div x-show="mobileNav" x-cloak x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0">
<!-- Mobile nav content -->
</div>
```
**Lists** — always use `<template x-for>`:
```html
<template x-for="item in items" :key="item.id">
<div class="p-4 rounded-lg bg-[var(--color-surface)]">
<h3 x-text="item.title" class="font-semibold"></h3>
</div>
</template>
```
**Two-way binding** — use `x-model` for form elements:
```html
<input x-model="searchQuery" type="text" placeholder="Search..."
class="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-[var(--radius)] px-4 py-2 text-sm text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] outline-none focus:border-[var(--color-accent)]">
```
**Click outside** — use `@click.outside`:
```html
<div x-show="dropdownOpen" @click.outside="dropdownOpen = false" x-cloak>
<!-- Dropdown content -->
</div>
```
## Responsive Design Patterns
### Breakpoint Strategy
| Breakpoint | Width | Target |
| --- | --- | --- |
| (default) | < 640px | Mobile phones |
| `sm:` | >= 640px | Large phones / small tablets |
| `md:` | >= 768px | Tablets |
| `lg:` | >= 1024px | Laptops |
| `xl:` | >= 1280px | Desktops |
| `2xl:` | >= 1536px | Wide screens |
### Mobile-First Layout Patterns
```html
<!-- Stacked on mobile, side-by-side on desktop -->
<div class="flex flex-col lg:flex-row gap-6">
<aside class="w-full lg:w-64 shrink-0"><!-- Sidebar --></aside>
<main class="flex-1"><!-- Main content --></main>
</div>
<!-- 1 col -> 2 col -> 3 col grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Cards -->
</div>
<!-- Hide on mobile, show on desktop -->
<div class="hidden md:block">Desktop-only content</div>
<div class="md:hidden">Mobile-only content</div>
```
### Mobile Navigation Pattern
```html
<nav class="fixed top-0 inset-x-0 z-50 border-b border-white/5 backdrop-blur-xl bg-[var(--color-primary)]/80">
<div class="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
<a href="#" class="font-bold">Brand</a>
<!-- Desktop nav -->
<div class="hidden md:flex items-center gap-8">
<a href="#" class="text-sm text-[var(--color-text-muted)]">Link</a>
<a href="#" class="text-sm px-4 py-2 rounded-[var(--radius)] bg-[var(--color-accent)]">CTA</a>
</div>
<!-- Mobile hamburger -->
<button @click="mobileNav = !mobileNav" class="md:hidden p-2 cursor-pointer" aria-label="Menu">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
:d="mobileNav ? 'M6 18L18 6M6 6l12 12' : 'M4 6h16M4 12h16M4 18h16'"/>
</svg>
</button>
</div>
<!-- Mobile menu -->
<div x-show="mobileNav" x-cloak x-transition
class="md:hidden border-t border-white/5 bg-[var(--color-primary)] px-6 py-4 space-y-3">
<a href="#" class="block text-sm text-[var(--color-text-muted)]">Link</a>
</div>
</nav>
```
## Common Component Patterns
### Stat Cards
```html
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div class="p-4 rounded-[var(--radius)] bg-[var(--color-surface)] border border-white/5">
<p class="text-xs text-[var(--color-text-muted)] mb-1">Label</p>
<p class="text-2xl font-bold tracking-tight" style="font-family: var(--font-heading)">$12,345</p>
<p class="text-xs text-emerald-400 mt-1">+12.5%</p>
</div>
</div>
```
### Tab Navigation
```html
<div x-data="{ tab: 'overview' }">
<div class="flex gap-1 border-b border-white/5 mb-6">
<template x-for="t in ['overview', 'analytics', 'settings']" :key="t">
<button @click="tab = t"
:class="tab === t ? 'text-[var(--color-text)] border-[var(--color-accent)]' : 'text-[var(--color-text-muted)] border-transparent hover:text-[var(--color-text)]'"
class="px-4 py-2 text-sm font-medium border-b-2 -mb-px capitalize cursor-pointer"
x-text="t">
</button>
</template>
</div>
<div x-show="tab === 'overview'"><!-- Overview content --></div>
<div x-show="tab === 'analytics'" x-cloak><!-- Analytics content --></div>
<div x-show="tab === 'settings'" x-cloak><!-- Settings content --></div>
</div>
```
### Modal/Dialog
```html
<div x-data="{ modalOpen: false }">
<button @click="modalOpen = true" class="cursor-pointer">Open</button>
<!-- Backdrop + modal -->
<div x-show="modalOpen" x-cloak class="fixed inset-0 z-50 flex items-center justify-center p-4"
@keydown.escape.window="modalOpen = false">
<div x-show="modalOpen" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-black/60" @click="modalOpen = false"></div>
<div x-show="modalOpen" x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
class="relative bg-[var(--color-surface)] rounded-[var(--radius)] border border-white/10 p-6 w-full max-w-md shadow-2xl">
<h3 class="font-semibold mb-4" style="font-family: var(--font-heading)">Modal Title</h3>
<p class="text-sm text-[var(--color-text-muted)] mb-6">Modal content goes here.</p>
<div class="flex justify-end gap-3">
<button @click="modalOpen = false" class="px-4 py-2 text-sm rounded-[var(--radius)] border border-white/10 cursor-pointer">Cancel</button>
<button @click="modalOpen = false" class="px-4 py-2 text-sm rounded-[var(--radius)] bg-[var(--color-accent)] font-medium cursor-pointer">Confirm</button>
</div>
</div>
</div>
</div>
```
### Toast/Notification
```html
<div x-data="{ show: false, message: '' }"
@notify.window="message = $event.detail; show = true; setTimeout(() => show = false, 3000)">
<div x-show="show" x-cloak
x-transition:enter="transition ease-out duration-300 transform"
x-transition:enter-start="opacity-0 translate-y-4"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="fixed bottom-6 right-6 z-50 px-4 py-3 rounded-[var(--radius)] bg-[var(--color-surface)] border border-white/10 shadow-xl text-sm">
<span x-text="message"></span>
</div>
</div>
```
## Font Recommendations
Good distinctive font pairings (heading / body). **Rotate through these** — don't
reach for Space Grotesk (or any single pairing) every time; matching the pairing
to the chosen Aesthetic layer is what keeps designs from sharing a fingerprint:
| Heading | Body | Mood |
| --- | --- | --- |
| Space Grotesk | DM Sans | Modern tech |
| Playfair Display | Source Sans 3 | Editorial luxury |
| Sora | Outfit | Clean geometric |
| Bricolage Grotesque | Schibsted Grotesk | Contemporary startup |
| Fraunces | Work Sans | Warm editorial |
| JetBrains Mono | IBM Plex Sans | Developer tool |
| Unbounded | Sora | Bold statement |
| Archivo | Nunito Sans | Friendly SaaS |
| Instrument Serif | Schibsted Grotesk | Editorial minimal |
All of the above are served by Google Fonts (`fonts.googleapis.com/css2`). The
mandatory `<head>` only loads the Google Fonts CDN (see HTML Structure
Requirements) — Fontshare-only families (Cabinet Grotesk, Satoshi, Clash
Display, General Sans) are not on Google Fonts and will silently fail to load,
so the browser falls back to a system sans and quietly reintroduces the exact
slop this skill bans. Only use a Fontshare family if you also add its
Fontshare `<link>`/`@import` and confirm it renders.
## Multi-screen prototypes & navigation
A prototype with more than one screen is **multiple files** in the same design
(e.g. `index.html`, `dashboard.html`, `checkout.html`). The editor shows them
all in the artboard/overview and as screen tabs. This is only for genuinely
distinct screens — the mobile and desktop views of the *same* page are
breakpoint frames of ONE document (the `devices` param + the
`responsive-breakpoints` model), never a second file.
The preview renders each file in a sandboxed `srcdoc` iframe. A real
`<a href="/pricing">` or `<a href="page.html">` resolves against the *app* URL
and navigates the iframe to the Design app itself ("Design not found"), nuking
the prototype. **Never link screens with real URLs.** Use one of:
1. **In-screen flows (preferred):** keep the flow in one file with Alpine state
and `x-show`. Links become buttons:
```html
<div x-data="{ screen: 'home' }">
<button @click="screen = 'pricing'" class="cursor-pointer">See pricing</button>
<section x-show="screen === 'home'">…</section>
<section x-show="screen === 'pricing'" x-cloak>…</section>
</div>
```
2. **Cross-file screen links:** to jump to another file in the design, use
`data-screen` (or a bare filename href). The editor intercepts the click and
switches to that screen instead of navigating:
```html
<a data-screen="checkout.html" class="cursor-pointer">Checkout</a>
```
The target should match the other file's name (`checkout.html` →
`checkout.html`; the `.html` is optional in the match).
3. **In-page scroll:** `href="#features"` is fine and scrolls within the screen.
External links (`https://…`) are allowed — the editor opens them in a new tab.
Never use `target="_top"` or relative paths expecting a real page load.
## Locked subtrees
Treat `data-agent-native-locked="true"` as authoritative: locked elements and
their descendants stay byte-for-byte unchanged, and the server enforces this.
Ask the user to unlock the layer in the Layers panel if they want it changed.
## Making edits — minimal, scoped "smart" diffs
When refining an existing design, change the **smallest** amount possible. Full
regeneration is slow, expensive, and regresses unrelated parts.
1. **Read before you edit.** Pull the current file with `get-design-snapshot`
(or `get-design`) so you edit the live content, not a stale memory of it.
If the design has persisted review comments, fetch the open queue with
`get-review-feedback` and use `.agents/skills/design-review-feedback` to
apply and verify one anchored thread at a time.
2. **Prefer `edit-design` for small changes.** It applies one or more
search/replace blocks to a file's HTML — surgical, cheap, and it preserves
everything you didn't touch (Alpine state, scroll, other screens):
```bash
pnpm action edit-design --designId "<id>" --filename index.html \
--edits '[{"search":"<h1 class=\"text-4xl\">Hello</h1>","replace":"<h1 class=\"text-5xl\">Hello there</h1>"}]'
```
Each `search` must match the file **exactly and uniquely** — include enough
surrounding context to be unambiguous. When a change needs a new container,
first try styling the existing parent; add a wrapper only when no existing
element can hold the layout, as a search/replace whose `replace` adds it
around the original.
3. **Reserve `generate-design` for** net-new files. For large structural
rewrites of an existing selected file, call `edit-design` with
`mode: "replace-file"` and the exact `fileId` from `get-design-snapshot`.
Never resend files you aren't changing.
4. **Treat `:root` as the global spec.** For theme-wide restyles, edit the
tokens in `:root` rather than touching every element.
5. **Don't add unrequested features** during a refinement pass.
For selecting/editing DOM elements as code layers (layer projection, the
deterministic `apply-visual-edit` slice, and the semantic React/TSX handoff),
read `references/code-layers.md`. For the VS Code-style source workbench
(explorer, quick open, `list-source-files`/`apply-source-edit`), read
`references/code-workspace.md`.
## Tailwind v4 + motion gotchas
- **Gradients:** Tailwind v4 renamed the utilities. Use `bg-linear-to-r`,
`bg-radial`, `bg-conic` — the v3 `bg-gradient-to-*` classes silently do
nothing on the v4 browser CDN.
- **Respect reduced motion.** Wrap non-essential animation so it's disabled for
users who ask for it (already included in the mandatory `<style>` below).
- Use Tailwind scale steps, not arbitrary `[…px]` values, unless truly needed.
## What NOT to Do
- For aesthetics (fonts, palette, layout, backgrounds, copy), see the Aesthetic
quality bar above — every banned default there is out unless the user asks.
- Never link prototype screens with real/relative URLs — use Alpine state,
`data-screen`, or `#` anchors (see Multi-screen prototypes & navigation).
- Never hardcode colors — always reference CSS custom properties (no raw
`text-white` / `bg-black` / hex literals in markup).
- Never define or rely on a CSS custom property starting with `--agent-native-`
in generated design content. That prefix (plus `--design-editor-`) is
reserved for editor-internal state (selection chrome, editor-chrome scale
compensation, clipboard/surface tokens) — the editor strips any property
under those prefixes before persisting a cross-screen style capture
(`isEditorInternalCssVar` in `app/pages/design-editor/portable-style.ts`),
so a design that
defines its own theming under that namespace would silently lose those
values on a cross-screen move. Use `--color-*`, `--font-*`, or another
design-owned prefix for tokens/tweaks instead.
- Never use the v3 `bg-gradient-to-*` classes — use v4 `bg-linear-to-*`.
- Never use `<script>` blocks with raw DOM manipulation — use Alpine.js directives
- Never inline `onclick="..."` handlers — use `@click`
- Never use `!important` except in `[x-cloak]`
- Never forget `cursor-pointer` on interactive elements
- Never use `<img>` with placeholder/stock URLs — generate real imagery (see
Imagery below) or use tokened colored divs/gradients only for pure UI
chrome (icons, avatars-as-initials, decorative fills), never as a stand-in
for a hero, product shot, or portrait that should be a generated image
- Never set font-size below 16px for body text or 12px for labels
- Never nest wrapper divs that add no layout, paint, or spacing, and never
write fractional px values such as `left: 256.96px`
## Multi-screen consistency contract
When a design has more than one screen, the shared system must be
byte-identical across every screen file, not just similar: the `:root` token
block, the Google Fonts `<link>`, the nav, and the footer should match exactly
between `index.html` and every other screen. Before saving a new or edited
screen, diff its `:root` block against `index.html`'s (or the design system's
tokens) and reconcile any drift instead of letting each screen accumulate its
own slightly-different palette. Consistency is not sameness — keep structure
and layout varied per screen (see "Building on existing code" above) while the
token layer, typography, nav, and footer stay identical.
## Breakpoints & screen states
- **Breakpoints**: `add-breakpoint` adds a device-width frame (Framer
defaults Phone 390 / Tablet 810 / Desktop 1200, or a custom width) to the
design's breakpoint set stored in `designs.data`; duplicate widths are
ignored. `remove-breakpoint` removes one by id. `set-active-breakpoint`
sets which frame is the current edit scope. Breakpoint frames are ONE
document rendered at different widths with a Framer-style cascade: the
primary (widest) frame is the base, and edits at a narrower active
breakpoint persist as width-scoped overrides (`max-[<bound>px]:` classes
or managed `@media` rules) that cascade down. Always check the active
breakpoint before a responsive-only edit, and pass `activeFrameWidthPx`
to `apply-visual-edit` so the write lands at the right scope. Read the
`responsive-breakpoints` skill for the full model.
- **Design states**: `create-design-state` creates a named alternate
DOM/Alpine snapshot (`kind: "state"` — Loading, Empty, Error), a static data
fixture (`kind: "fixture"`), or a placeholder for a live capture
(`kind: "capture"`). `apply-design-state` updates an existing state row
(rename, change breakpoint, update fixture/capture data, set the preview
reference). `capture-design-state` records a running app's current route,
props, and API data into a `capture` row — it requires the design's source
to advertise the `captureState` bridge capability (localhost/fusion); for
inline designs without a live bridge, use `create-design-state` instead.
`list-design-states` lists all states/fixtures/captures for a design, and
`delete-design-state` removes one (irreversible; the design itself is
unaffected).
## Component reuse
Before hand-rolling another near-duplicate card/button/nav item, check
whether the pattern already exists as a recognised component. Once a visual
pattern repeats 3+ times in a design, promote it: call `create-component` on
the selected root element to stamp deterministic
`data-agent-native-component="<Name>"` and `data-agent-native-prop-*`
annotations, so it becomes a recognised component instance for the canvas
outline and the Component inspector section. Use `index-components` to scan a
design's HTML for existing `data-agent-native-component` annotations and
persist the discovered component list before inventing something that may
already exist. `get-component-details` returns a selected instance's name,
props, variants, and source info. `preview-component-prop-edit` previews a
prop/class change on the canvas without saving; `apply-component-prop-edit`
persists it. `open-component-source` navigates to the component's source
location (the design file for inline/Alpine designs, or the resolved external
file for localhost/fusion sources).
## Suggested auto layout
For an absolute/freeform container, first measure its direct children and
present the proposed direction, visual order, gap, four-side padding,
alignment, and sizing — do not mutate source until the user applies the
preview. Inline HTML/Alpine applies the reviewed proposal through one
`apply-visual-edit`-backed content transaction so undo restores the exact
prior structure. Local React uses the semantic source handoff (see
`references/code-layers.md`, never generic AST rewriting), preserves nested
absolute descendants and responsive logic, and applies the approved proposal
as one reversible source edit.
## Editor extensions
Design editor extensions render in the right inspector slot
`design.editor.inspector` (`create-extension` →
`add-extension-slot-target` → `install-extension`). Read
`references/editor-extensions.md` for the context shape and the AI-driven
style/artboard change flow.
## Realistic app-state content
For app/product UI (not marketing pages), populate lists and tables with
plausible mid-life data — not a pristine "just signed up" empty account and
not obviously fake placeholder rows (avoid "Lorem Ipsum User", "Item 1", "Item
2"). Include at least one realistically long name/title/label so truncation
and wrapping behavior is visible. Always design the empty state and a loading
skeleton for the screen's primary data surface — don't only show the
happy-path populated state.
## Motion craft
Push motion the same way you push type and color: on purpose, not as a
uniform default. Duration bands: 150-250ms for micro-interactions (hover,
toggle, button press), 300-500ms for panel/sheet/modal transitions, and
500-800ms reserved for exactly one orchestrated page-load reveal (stagger
individual elements 60-100ms apart, capped at about 6 staggered elements —
more than that reads as sluggish, not polished). Ease functions: `ease-out`
for elements entering, `ease-in` for elements leaving. Animate only
`transform` and `opacity` for performance; avoid animating `width`/`height`/
`top`/`left`. Every non-essential animation must respect
`prefers-reduced-motion` (see the mandatory `<style>` block below). For
motion edits, keep a versioned keyframe timeline as the editable source
model, then compile it back into the design code. Inspect the selected screen
with `get-motion-timeline` before writing. Inline HTML/Alpine uses its `fileId`
as `sourceRef`; a localhost React/TSX screen uses the same selected screen as
`sourceRef` plus the live module path, connection id, and version hash in
`localSource`. Write through `apply-motion-edit` so edits stay parseable and
round-trip between timeline and CSS/JSX. The inline output is standard CSS
`@keyframes`; the React adapter updates verified JSX/source spans and emits the
same managed CSS contract.
Inline HTML `<style>` elements and React JSX `<style>` elements can adopt static
CSS animations from top-level rules that use one uniquely resolved simple
selector, one `animation-name`, and a matching top-level `@keyframes` block.
React requires a static id selector with one matching static JSX host and one
document-unique id. JSX `<style>` CSS is global, so React import does not adopt
class, type, or attribute selectors. React also requires the complete style
content to be one lossless JSX text or string/template literal with no
substitutions. Dynamic style elements and style props that cannot be proven
free of motion are unsupported. Importable animations use a positive fixed
`ms`/`s` duration, a non-negative fixed delay, a Motion-supported easing,
`animation-fill-mode: both`, one iteration, normal direction, running state,
replace composition, and static keyframe stops from 0% through 100% with both
endpoints and one or more supported properties: `opacity`, `transform`,
`translate`, `scale`, `rotate`, `color`, `background-color`, `border-radius`,
`border-color`, `border-width`, or `box-shadow`. Each property needs static 0%
and 100% endpoints, and duplicate declarations at one stop are unsupported;
Motion maps properties to parallel tracks with shared target and animation
timing. Each target can have one imported animation. Shorthand, multiple
animation names, conditional animation
definitions, ambiguous targets, CSS variables in keyframe values, and external
stylesheets are unsupported; the source reader fails closed so Motion does not
silently discard or flatten them. The exact global
`prefers-reduced-motion: reduce` override generated by this skill is preserved
and does not change the imported timeline.
On the first successful `apply-motion-edit` after import, Motion removes only
the adopted animation declarations from the inline CSS and writes them into
its managed stylesheet. Global `@keyframes` definitions and unrelated
declarations remain in place because other rules may use them.
`remove-motion-timeline` removes the managed stylesheet, adopted animation
declarations, and Text runtime; it leaves authored keyframe definitions in
place. For localhost source, pass the current `sourceVersionHash` returned by
`get-motion-timeline` as `timelineVersionHash`; stale or missing hashes preserve
the source and timeline so you can reload before retrying. This CSS importer
does not read or edit JavaScript runtime animations, GSAP timelines, Web
Animations API calls, or Alpine `x-transition`/`x-effect` behavior. Generic
Alpine `x-bind` on a Motion target is unsupported because it can change
attributes dynamically. In localhost React/TSX, write through the verified
module path, connection id, and version hash in `localSource`. Include the
current `moduleVersionHash` and captured `timelineVersionHash`; autosave
compares both before writing so a code edit cannot replace the source
underneath a dirty timeline. React inline `style` props such as
`style={{ animationName: "fade" }}` are detected and rejected because they
cannot be imported losslessly. This is separate from static JSX `<style>`
elements, which can be imported under the constraints above. Keep runtime-only
animation code source-owned and do not claim that Motion round-trips it.
Text animations use `textDocument` version 2 for `animationDirection`, `order`,
`overlapPercent`, and `ignoreSpaces`; preserve their unit, stagger interval,
property, duration, and keyframes when editing. Version 1 text documents remain
readable and are normalized on write. Do not keep the only copy of animation
state in a GSAP runtime timeline or a one-way export.
## Imagery
Generate real images for anything a real product would photograph or
illustrate: hero backgrounds, product shots, portraits/avatars, testimonial
photos, marketing/editorial imagery. Don't generate images for utility UI —
icons, data tables, form chrome, and dashboard widgets should stay as
tokened SVG/CSS, not photos.
- **Use the Assets generation tool** (`generate-asset`, or `insert-asset` once
an asset is chosen) instead of `<img>` placeholder URLs or colored-div
stand-ins — for raster generation, restyling, or editing existing
screenshots/photos. Always pass `callerAppId: "design"`. If no Assets MCP
tool is available, use the first-party Assets app via `call-agent` with
agent `"assets"` when available. If the user attached an image, use its
hosted chat-attachment URL or call `upload-image` to create one before
delegating. If no image/upload provider is configured, say that specific
setup is needed and continue any non-image Design work separately.
- **Write image prompts as art direction, not a one-line label.** Specify
subject, composition, lens/framing, lighting, and palette, and tie the
palette/mood back to the design's own `:root` tokens so the image reads as
part of the same system rather than a stock photo dropped in. If a design
system is linked, fold its `imageStyle.styleDescription` into the prompt
(see `design-systems` skill) so generated imagery matches the brand's
established photographic/illustration style.
- **Default to `tier: "fast"`** (the cheap Gemini Flash "nanobanana"-class
model) for exploration and every non-final variant. Only request
`tier: "best"` for the final, user-approved hero image — not for every pass.
- **Match `aspectRatio` to the layout slot**: `21:9` for a full-bleed hero,
`4:3` for a card/feature image, `1:1` for an avatar or square thumbnail.
Mismatched aspect ratios force ugly crops in the browser.
- **Always write real `alt` text** describing the image's content — never
leave `alt=""` on a meaningful (non-decorative) image.
- **Placement is a two-step pass**: when the Assets picker returns a selected
asset, preserve its `assetId`, `runId`, and URLs verbatim; call `insert-asset`
to place the chosen image, then do one `edit-design` pass (using
`get-design-snapshot` first) to adjust surrounding layout/spacing if the
inserted figure doesn't sit flush with the rest of the design.
COMPATIBILITY & REFERENCES
Platform guidance
Imported from the listed AgentSkill.sh category. Complete supplied package files and notices are retained. Required external tools and credentials are not installed. No bundled code was executed during import. Individual agent task behavior has not been tested.
Creates comprehensive frontend UI/UX designs including user interfaces, design systems, component libraries, responsive layouts, and accessibility implementations. Produces wireframes, mockups, design specifications, and implementation guidelines. Use when designing user interfaces, creating desi...
Expert in building scalable design systems with Atomic Design, design tokens, and theming infrastructure. Use when creating component libraries, implementing dark mode, or establishing typography and color systems. Covers multi-brand support, Storybook-driven development, and accessibility-first ...
Use when implementing Liquid Glass effects, reviewing UI for Liquid Glass adoption, debugging visual artifacts, optimizing performance, or requesting expert review of Liquid Glass implementation - provides comprehensive design principles, API patterns, and troubleshooting guidance from WWDC 2025....