claude-design-handoff
What this skill solves
Claude Design produces layout and art direction in a league that HTML-by-hand, Word, or script-generated PDFs do not reach. But it behaves like an author: while designing, it also rewrites. Without guardrails it rephrases facts, merges sections, softens disclaimers, and introduces vocabulary of its own. In one real run (a family travel itinerary, no lockdown), the designed version came back with 23 fact-check findings, 4 of them critical: a wrong hotel distance, an invented address, shifted event dates, and a dropped warning.
The fix is a pipeline, not a better prompt:
- Content first. All facts, numbers, links, and disclaimers are finalized before any design happens.
- FROZEN lockdown. Everything that must not change is wrapped in
<!-- FROZEN -->markers. - Hard brief. Claude Design gets the content plus three non-negotiable constraints (verbatim frozen blocks, fixed section order, no new vocabulary).
- Re-verify. The designed output is mechanically diffed against the source. The brief minimizes drift; the diff catches what got through.
Two tracks, decide first
| Doc track (phases 1 to 4 below) | Web track (own section below) | |
|---|---|---|
| Deliverable | PDF or document for an external recipient | Website, landing page, UI |
| Claude Design delivers | the finished layout (bundle = end product) | art direction (bundle = design spec) |
| Afterwards | re-verify + PDF render | engineering port in Claude Code |
Roles (doc track)
- Claude Design is the art director: typography, palette, layout, drop caps, columns. It also writes, which is exactly the drift risk this skill manages.
- The agent is the editor: researches, fixes the facts, locks them down, renders the final PDF, verifies at the end.
- No back channel. Content edits after the design pass stay with the agent, applied directly in the HTML. A new design iteration means a new Claude Design chat.
Doc track, phase 1: build the content source
Write a Markdown source containing every fact, number, hyperlink, and disclaimer. Separate frozen (must not be reworded) from prose (may be polished for flow).
Mark frozen blocks with HTML comments; Claude Design respects them far more reliably than prose instructions:
<!-- FROZEN: facts, do not reword -->
- Outbound flight XY1234, departs 09:15, arrives 11:40 local
- Booking reference ABC123
- Source: airline confirmation email, 2026-05-02
<!-- /FROZEN -->
The smaller airport is the relaxed option: parking next to the
terminal, check-in usually under 20 minutes.
What must be frozen: numbers, dates, flight numbers, booking references, prices, addresses, hyperlinks, hard disclaimers (legal obligations, deadline dates, safety warnings), verbatim recommendations ("we recommend option B because X").
What can stay prose: mood, narrative, transitions, anything where wording can shift without a fact tipping over.
Save the source in the project folder (for example content-source.md). The user reads and approves it before it goes anywhere near Claude Design.
Doc track, phase 2: the design brief
The user pastes the source into a new Claude Design chat together with the brief from references/design-brief-template.md. The brief names three hard constraints and three explicit freedoms; without them, Claude Design rewrites. Copy-paste ready, only the placeholders need filling.
Doc track, phase 3: re-verify
Pull the bundle with curl, not with an agent's built-in fetch tool. Fetch tools commonly cap response size around 10 MB; real bundles are 20 to 30 MB.
$url = "https://api.anthropic.com/v1/design/h/<hash>?open_file=..."
$dest = "<project>/_claude-design-handoff/handoff.tar.gz"
curl -L -o $dest $url
tar -xzf $dest -C "<project>/_claude-design-handoff/"
Remove-Item $dest
Read README.md and chats/*.md from the bundle first. The chats are the intent documentation: why Claude Design made its design decisions. Without them every diff is blind.
Typical bundle structure:
<name>/
├── README.md points to the chat transcripts
├── chats/ intent documentation, read first
└── project/
├── <name>.html main file (~100 KB, readable, external image refs)
├── <name>-print.html print variant (usually redundant)
├── <name>-standalone.html self-contained (~10 MB, for sending as one file)
└── images/ referenced by the HTML
Then diff, in this order:
- Frozen blocks, verbatim: run
scripts/verify-frozen.py <source.md> <designed.html>. Expected: zero deviations. Any deviation is a critical finding. - Section order matches the source outline.
- Hyperlinks live: HTTP status plus content match (does the booking link still show the right property?).
- New vocabulary: Claude Design sometimes introduces its own nicknames or insider slang. That does not belong in a document for an external recipient. Grep for terms that are not in the source.
- Prose spot checks: in 3 to 5 random sections, confirm numbers, proper names, and dates are unchanged.
Report findings in three buckets: critical (facts tipped, frozen violated, dead link), medium (sections reordered, disclaimer softened, slang), cosmetic (typos, formatting breaks). The user decides what gets fixed. Fixes are applied directly in the bundled HTML; nothing goes back to Claude Design.
Doc track, phase 4: render the PDF
Chrome headless is the default. The PDF comes straight from the verified HTML, so every hyperlink in the PDF is one you checked.
$chrome = "C:\Program Files\Google\Chrome\Application\chrome.exe"
# macOS: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
$html = (Resolve-Path "<project>/_claude-design-handoff/project/<name>.html").ProviderPath
$pdf = "<project>/<final-name>.pdf"
& $chrome --headless --disable-gpu `
--print-to-pdf="$pdf" `
--no-pdf-header-footer `
--virtual-time-budget=15000 `
$html
Why these flags:
--virtual-time-budget=15000: web fonts load from the network. Without it Chrome renders before the fonts arrive and the PDF falls back to a default serif.--no-pdf-header-footer: removes the browser's default print header and footer.Resolve-Path ... .ProviderPath: bundle paths often contain spaces and unicode characters; without resolving, Chrome fails with "file not found".- Do not set margins via Chrome flags. The bundle CSS already declares
@page { margin: ... }; setting both doubles the margins.
Sanity-check the link count in the rendered PDF (PyMuPDF, one line):
import pymupdf; print(sum(len(p.get_links()) for p in pymupdf.open("output.pdf")))
The count must match what the HTML declares.
Web track: Claude Design as art director for websites
Use this track whenever a site or UI should look exceptional rather than merely solid. Claude Design wins at composition, typographic courage, editorial detail systems (margin notes, folios, index numbers), and act structure. Claude Code wins at everything that has to work: motion implementation (GSAP/ScrollTrigger), real functionality, reduced-motion and no-JS fallbacks, accessibility, integration, QA. The bundle is a static design demo; motion exists in it only as intent comments.
Order matters (the expensive lesson): run the design pass after the content freeze and before the technical build. One real project built 8 sections to completion first, then ported all of them to the bundle's look afterwards, which meant doing the work twice. Freeze copy, get the design, then engineer sections directly in the bundle's visual language.
Workflow:
- Build the content source with FROZEN blocks (as in phase 1) plus the brief from
references/web-brief-template.md. Concatenate both into one paste file; the user pastes the whole thing into a new Claude Design chat. No file uploads. - In the brief, list sanctioned additions explicitly (for example factual coordinates, derived stat chips). Everything unsanctioned counts as a finding in verify.
- Retrieve the result. Project links (
claude.ai/design/p/...) import via Claude Code's DesignSync tool where available; the tar.gz share-link format (api.anthropic.com/v1/design/h/...) works with plaincurlas in phase 3. - Verify.
verify-frozen.pydoes not apply here: layout output does not echo the FROZEN markers. Instead, extract the frozen phrases from the source and check for their verbatim presence in the output text. Also check section order, links, no invented numbers, no new vocabulary, and additions against the sanction list. - Engineering port (Claude Code): translate the MOTION comments into real animation, merge functionality, build fallbacks. Measure contrast yourself. In one measured run Claude Design violated its own explicit 4.5:1 contrast instruction on 3 elements (accent color on light ground, 2.82 to 3.14:1). Fix with darker palette tokens and document the deviations.
- No back channel: design iterations mean a new Claude Design chat (iteration prompts are in the template); code fixes live in the repo permanently.
When not to use this
- Short administrative letters of 1 to 3 pages (a plain document builder is enough)
- Internal working documents for yourself (no drift risk that matters)
- Day plans, checklists, tracker tables
- Plain-text emails without layout ambitions
Pitfalls, compact
| Pitfall | Why it hurts | Fix |
|---|---|---|
| Fetching the bundle with a built-in fetch tool | ~10 MB caps; real bundles are 20 to 30 MB | curl -L -o file.tar.gz <url> |
| Share link treated as harmless | the hash is the auth; anyone with the link sees the document and the chat transcripts | treat the link like a secret, never forward it |
| Offline PDF render | web fonts do not load, layout collapses to fallback fonts | --virtual-time-budget=15000, render online |
| Picking single files out of the bundle | relative image refs break | treat the bundle as one atom |
Maintaining *-print.html alongside *.html |
two sources of truth diverge | keep *.html with @media print as the single source |
| Word COM automation for PDF/DOCX conversion | hangs, leaves lock files | use pdf2docx (Python) |
| Claude Design "improves" disclaimers quietly | a hard warning becomes a friendly tip | disclaimers always go inside FROZEN blocks |
| Silent section merging | two planned sections come back as one box | brief states: fixed section order, no merging without asking |
Antipatterns
- Design before the content freeze. The content then has to adapt to the design instead of the other way round. (The web track does not contradict this: there, design comes before engineering but still after the content freeze.)
- Exporting the PDF directly from Claude Design. No frozen diff, no hyperlink check, no section-order check.
- Skipping the FROZEN markers and relying on re-verify alone. On a 20-page document that is hours of word-by-word diffing. Set markers early.
Bundled resources
scripts/verify-frozen.py: extracts all<!-- FROZEN -->...<!-- /FROZEN -->blocks from the designed HTML and diffs them against the source Markdown. Doc track only; layout output on the web track does not echo the markers, use the phrase check described above.references/design-brief-template.md: brief template for the doc track (3 constraints, 3 freedoms). Copy-paste, fill the placeholders.references/web-brief-template.md: brief template for the web track (reference class, hard constraints, locked design system, sanctioned playground, MOTION comment convention, iteration prompts).