Design Generation
How to generate complete, interactive HTML prototypes using Alpine.js + Tailwind CSS (via CDN). This is the core skill for the design agent.
Technology Stack
Every generated design uses:
- Tailwind CSS v4 — via
@tailwindcss/browser@4CDN (NOT the old v3 CDN) - Alpine.js 3.x — via
alpinejs@3.15.11CDN withdeferattribute - 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:
- Direction —
show-design-questions(or a stated thesis) sets taste on purpose. - Exploration —
present-design-variantscompares genuinely different directions before committing to one. This step kills sameness; never skip it for open-ended work. - Spec — a linked design system and the
:roottoken block capture the chosen look as reusable rules. - Code —
generate-design/edit-designexecute 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-questionsabout 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:
- The current-turn product, audience, content, accessibility, and explicit brand constraints.
- The active Agent-Native design system and its registered tokens, components, type, imagery rules, and custom instructions.
- Approved Creative Context assets, components, and examples.
- Impeccable-inspired quality heuristics for hierarchy, subtraction, composition, contrast, motion, and bounded review.
- 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:
- Context — audience, domain, and the one job this screen must do.
- Structure — a named layout topology: bento grid, sidebar app shell, editorial column, split-screen, masonry, dashboard tiles.
- 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.
- 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.
- Absolute
left/topare whole pixels, and multiples of the screen'slayoutGrid.sizewhendesign-selectionreports one. A fractional or off-grid position reads as a mistake to a designer and does not match what dragging the same element produces. Set a screen's grid withset-layout-grid. - 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
:rootCSS variable. Never hardcodetext-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-widthinchunits, 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
:roottokens, 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
:rootso 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 fromgenerate-screens(canvas region assignments, per-frame instructions consumed bygenerate-design).show-design-questionsopens pre-generation questions in the main canvas (show-questionsstate).guided-questionsmay 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
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.
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"]:
- If the prompt/answer names specific devices, generate EXACTLY those, deduped ("mobile" only → one mobile frame; "mobile, tablet, desktop" → all three).
- If nothing about 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 still takes explicit width/height per variant to size its exploration screens (see Phase 2).
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.
{
"designId": "<the design id>",
"prompt": "Pick a direction",
"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 content is omitted, present-design-variants infers a size from the prompt/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.
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 any exact width/height in px, so "create a 300x250
ad" style requests work the same way — no special action, just the pixel
dimensions the artifact actually needs. The editor's own Frame tool preset
list (app/components/design/inspector/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: Instagram Post 1080×1080, Instagram Story 1080×1920, X Post 1200×675, Facebook Cover 820×312, LinkedIn Cover 1584×396.
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. That is expected for these formats; it stays legible in @2x+ exports since the export scale multiplies raster resolution, not the authored CSS size. Avoid dense multi-line copy at these sizes regardless.
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). 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.
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:
<!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:
<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):
<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>:
<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:
<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:
<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
<!-- 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
<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
<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
<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
<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="relativ
…(truncated)