Individual skill
artifact-design
Design guidance and fundamentals for Artifacts.
Anthropic/claude-code/skills/artifact-design/SKILL.md
Source commit 9e00d9751f95564bc2de984d3f1c5b4b14aa50cc
What was checked
- Matches the observed repository HEAD
- Yes
- Scanner rules are current
- Yes
- Repository scan complete
- No
- Skill scan complete
- No
- Observed behavior
- Network or broad shell
- Last checked
- Nov 9, 2026
These are observations at the recorded commit, not a guarantee of safety. Review the source before running it.
Install this skill
Choose an agent in your Stack or copy a command below. Commands fetch the pinned commit and copy only this skill directory. Existing destinations are refused. Copying a command does not install anything.
An install command is unavailable: current rules, complete repository and skill scans, an eligible behavior level and a permitted license are required.
Repository context
- Repository stars (not skill ratings)
- 71,071
- Repository last updated
- Nov 8, 2026
- License
- CC0-1.0
Observed agent integration: No specific integration evidence. Standard SKILL.md installation does not imply verified compatibility.
Files and excerpt
File metadata and permitted, redacted excerpts come from the recorded scan. Source links are pinned to that commit.
Read excerpt
## Page contract — read before your first publish
These are the Artifact tool's own rules for the file you publish; the design guidance below builds on them.
**Format**: Always author the page as `.html`. Publish a `.md` file only when a loaded skill explicitly instructs it. When the user shares a markdown document or asks to turn one into an artifact, author an HTML page based on its content — preserve its substance, and design the page as you would any other artifact rather than transcribing the markdown one-to-one.
**Skeleton**: The file is wrapped in a `<!doctype html>…<head>…</head><body>` skeleton at publish time, so write the page content directly — no `<!DOCTYPE>`, `<html>`, `<head>`, or `<body>` tags of your own. Its head carries only a charset and viewport meta (with `viewport-fit=cover`) plus a small reset — light `color-scheme`, `:root` padded top and bottom by the phone's safe-area insets, zero body margin with a 14px system font, `img{max-width:100%}`, and `[hidden]{display:none!important}` (toggle visibility with `el.hidden`, not `style.display`) — so put your own `<title>` and `<style>` at the top of the file. Keep the `:root` padding. Put `<meta name="claude:viewport-fit-sides" content="cover">` at the top of the file beside your `<title>`: it promises that the side gutter uses the two `max()` paddings given under Responsive, so the tag goes in only together with both paddings, never alone. An edit of an older page that has neither leaves both out, unless the ask redoes the page's layout. With it the page also runs under a system bar at the phone's side, where backgrounds reach that edge while the gutter keeps text and controls clear. Anything fixed to a screen edge stays at `0` and adds that edge's `env(safe-area-inset-*, 0px)` to its own padding (one that spans the width takes the side gutter too), a floating or absolutely placed control adds the nearby edges' insets to its offsets, and a sticky page header uses `top: env(safe-area-inset-top, 0px)`, not `0`, with the band above it painted by a fixed `body::before` (`content: ""; position: fixed; inset: 0 0 auto; height: env(safe-area-inset-top, 0px); z-index: 10`) in its background, or in the page's where it starts lower.
**Title**: Put a `<title>` at the top of the HTML; only the first 8KB of the file is scanned for it. It is the artifact's name in the browser tab and the gallery, so write a name, not a summary: a short noun phrase, typically two to four words, specific enough to pick this page out among many, the way an app or a document is named. When the user already has a specific name for the thing, use that name for the title rather than coining a new one. Never use a generic category label alone, and never append an explainer after a dash or colon. If you shorten a title that pairs the name with a generic word, keep the name, not the generic word. A multi-word title that already reads as one specific name is finished; do not shorten it further. The explanation goes in the one-sentence `description` parameter, which becomes the gallery card's subtitle. The `title` parameter fills in only when an HTML file has no `<title>` tag (Markdown pages keep their filename). Keep the title stable across redeploys.
**External resources — CDN allowlist (CSP-enforced)**: external scripts load ONLY from https://cdnjs.cloudflare.com (preferred), https://cdn.jsdelivr.net/npm/, https://unpkg.com, https://cdn.tailwindcss.com (Tailwind's play-CDN script) and https://code.jquery.com; external stylesheets ONLY from https://fonts.googleapis.com, with the font files they pull from https://fonts.gstatic.com (give every face a real fallback stack). Everything else is blocked, with no visible error: every other host (esm.sh included) and, even on those CDNs, anything but a script — stylesheets, images, media, fetch/XHR/WebSocket, a library's runtime fetches. So inline all other CSS and JS and embed assets as data: URIs. **How to load a library**: `<script src="https://cdnjs.cloudflare.com/ajax/libs/<lib>/<exact version>/<file>">` — pick the UMD build, which defines a global (e.g. react/18.3.1/umd/react.production.min.js, then react-dom) — placed BEFORE any inline `<script>` that uses it. Pin every library URL to an exact version (`react@18.3.1` on jsDelivr or unpkg, `react/18.3.1/…` on cdnjs, `cdn.tailwindcss.com/3.4.17`), never a bare name, a range or a tag (`react`, `react@18`, `@latest`). Choose a version at least two weeks old. Any version you knew before this conversation is old enough; if you look versions up, skip any published in the last two weeks. The viewer's sandbox also blocks any download the page starts itself — `<a download>` links (data:/blob: hrefs included) and script-driven saves are inert for viewers — so never offer a file through a plain link. Links to other websites (`https://…`) open in a new tab, but email, phone and app links (`mailto:`, `tel:`, `sms:`, other custom schemes) are unreliable inside an artifact: for many viewers (for example anyone outside the user's organization, or anyone viewing through a public link) following one, by link or by script, often does not work, and the page cannot tell whether it did. So show the address or number itself as selectable text (a copy button helps), treat such a link as a convenience that may do nothing, and never tell the viewer a message was sent or a call placed because the viewer tapped one. Artifacts render mermaid diagrams natively — markdown via ```mermaid fences, HTML via `<pre class="mermaid">` blocks — no library needed, don't load one. The viewer never shows `alert()`, `confirm()` or `prompt()` dialogs — `confirm()` returns false and `prompt()` returns null immediately — so build any confirmation step into the page itself.
**What the viewer's frame allows**: The page runs in a locked-down frame; what it refuses below, it refuses for every viewer (anonymous, signed-in, embedded, desktop and mobile apps), so build around these limits instead of detecting them. The page cannot open the print dialog — `window.print()` does nothing — so never offer a Print or "Save as PDF" button. Forms work as page UI (inputs, validation, submit events), but a real submission has nowhere to go: handle `submit` in script with `preventDefault()` and never point `action` at another site or a mailto: address. Copy buttons work when `navigator.clipboard.writeText` is called inside the click handler — catch its rejection (older desktop apps and some app views refuse it) and fall back to selecting the text; reading the clipboard never works, though the viewer's own Paste (the `paste` event) does. Camera, microphone, screen capture, location, motion, Web Share and similar device APIs are refused without a prompt — build on one only when this user has a runtime capability for that device (the `artifact-capabilities` skill lists them) and the page declares it; otherwise take photos, audio and data as uploaded files instead (file inputs, drag-and-drop of files and `FileReader` work in browsers). A screen wake lock may be granted while the page is visible: request it and tolerate rejection. Fullscreen and pointer lock work from a click in desktop browsers; treat both as optional (handle the rejection) since phones and some app views lack them. Sound plays only after the viewer interacts (muted autoplay is fine), so start audio from a button. Other sites cannot be embedded — no YouTube, map or form iframes, and no `<object>`/`<embed>`; link out instead: links open outside the artifact, normally in a new tab, while `window.open` works only for some signed-in viewers in the artifact's own organization and returns null for everyone else, so use real `<a href>` links. Web Workers work from your own files or `blob:` URLs; service workers and WebRTC do not. `fetch()` of files published alongside the page works with relative URLs; images from your own files, `data:` or `blob:` URLs draw to canvas and export cleanly. Only a plain `#anchor` (letters, digits, `.` `_` `~` `-`) from the artifact's link reaches `location.hash` — never `#/path`, `#key=value` state or the query string — and the page's own URL changes (`pushState`, path routes, its hash) never show in the viewer's address bar. Only where a page has several views worth bookmarking or sending (tabs, sections, records), name each with a bare token (never `settings`, `sidebar`, `customize`, `upgrade`, `downloads` or anything starting `comment-`, which the viewer keeps), open the one in `location.hash` on load, keep all other state in the page, and give the user each view's link (the artifact's link plus `#token`), because the address bar will not follow them from view to view.
**Browser storage**: `localStorage` (also `sessionStorage` and IndexedDB) works, but each artifact has its own origin and the data lives only in that viewer's browser — it survives republishes to the same URL and never reaches other viewers, other devices, or Claude. It can come back empty or the accessor can throw (a private window, cleared or blocked site data, previews or thumbnail capture), so wrap every read and write in try/catch and render the page correctly without it. Use it only for per-viewer conveniences (a remembered tab or filter, a collapsed section, an unsent draft), never for state that must persist reliably, be shared between viewers, or be read back by Claude — state like that belongs in a runtime capability when this user has one: load the `artifact-capabilities` skill before writing the page.