Skip to main content
skill Claude Code CX Codex Cursor GitHub Copilot official · Google

stitch::extract-static-html — Claude Code skill for static HTML

This skill saves a rendered app page as a standalone HTML file with CSS and images embedded. Use it to share a static view or prepare a page for Stitch.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add google-labs-code/stitch-skills --skill stitch::extract-static-html -a claude-code
npx skills add google-labs-code/stitch-skills --skill extract-static-html -a claude-code
Open on GitHub

What stitch::extract-static-html does

The skill opens a locally running app in headless Chrome and saves its rendered DOM. It embeds CSS and images, then removes scripts, development overlays, and Vite HMR styles. You can capture a route, set the viewport or HTML class, and convert canvas elements to images.

Before starting, the agent must offer a choice between a Puppeteer snapshot and a browser subagent, then wait for approval. With Puppeteer, it also pauses after starting the local server so you can check the URL and port before the snapshot runs. That confirmation step adds a pause. The app must be running, and Node.js must have puppeteer available; --auth-script supports pages behind a login.

What it helps with

  • save a rendered page as standalone HTML
  • prepare a static page for Stitch upload
  • capture several app routes into separate files
  • embed page styles, images, and canvas content

Example prompts for the agent

  • Capture the locally running page as standalone HTML at `.stitch/home.html`.
  • Save snapshots of `/`, `/pricing`, and `/dashboard`; add the `dark` class to the last page.
  • Capture the page behind login using `./auth.ts`.

How to install stitch::extract-static-html

1Skills CLI (Vercel)

npx skills add google-labs-code/stitch-skills --skill stitch::extract-static-html -a claude-code
npx skills add google-labs-code/stitch-skills --skill extract-static-html -a claude-code

2Manually: put the files in place

~/.claude/skills/extract-static-html/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/extract-static-html/

From the same set15

The whole set →
design-md official google-labs-code Extracts screens and styles from Google Stitch via MCP to synthesize a semantic design system into a DESIGN.md file. Claude Code CX Codex Cursor +1 64,700installs enhance-prompt official google-labs-code Turns rough UI concepts into detailed, structured prompts for Google Stitch. Pulls tokens from DESIGN.md and formats colors, sections, and component keywords. Claude Code CX Codex Cursor +1 58,300installs stitch-loop official google-labs-code Guides agents through iterative website creation with Stitch MCP. The agent reads the baton file, generates UI, integrates the page, and prepares tasks for the next run. Claude Code CX Codex Cursor +1 56,300installs shadcn-ui official google-labs-code Helps discover, add, and customize shadcn/ui components in React projects. Useful when building interfaces with Tailwind CSS. Claude Code CX Codex Cursor +1 53,400installs stitch::react-components official google-labs-code Turns Stitch designs into modular Vite and React components. Can also sync existing components with updated Stitch designs. Claude Code CX Codex Cursor +1 50,400installs remotion official google-labs-code Turns Stitch project screens into a Remotion walkthrough with transitions, zoom effects, and text overlays. Useful for presenting an interface screen by screen. Claude Code CX Codex Cursor +1 41,300installs taste-design official google-labs-code Generates agent-ready DESIGN.md files for Google Stitch screens. It sets strict rules for typography, palettes, and spring motion to avoid generic UI tropes. Claude Code CX Codex Cursor +1 20,100installs stitch::generate-design official google-labs-code Turns a text prompt or an image into a finished UI screen through Stitch MCP, then edits it with targeted prompts. Claude Code CX Codex Cursor +1 10,700installs

Similar

frontend-a11y affaan-m Accessibility patterns for React and Next.js covering forms, ARIA, keyboard input, and focus. Use it during implementation or code review. Claude Code CX Codex Cursor +1 4,700installs brand-landingpage Travis D. Elliott This skill interviews you about the product and its visual direction, then builds a landing page through Stitch. Use it when there’s no established design yet. Claude Code CX Codex Cursor 39,476installs vite Anthony Fu This skill covers Vite configuration, plugin development, SSR builds, and the Vite 8 move to Rolldown. Use it with vite.config.ts work. Claude Code CX Codex Cursor +1 39,800installs motion-advanced jeff Advanced gesture and animation patterns for React and Next.js. Guides your coding agent through drag-and-drop, SVG drawing, and imperative useAnimate sequences. Claude Code CX Codex Cursor +1 4,600installs make-interfaces-feel-better affaan-m This skill helps review and refine interface details such as spacing, typography, interaction states, and motion. Use it when a component feels unfinished or visually off. Claude Code CX Codex Cursor +1 4,600installs dart-flutter-patterns affaan-m Copy-paste Dart and Flutter patterns: null safety, state, navigation, networking. Useful when you write or review Flutter code. Claude Code CX Codex Cursor +1 4,600installs frontend-design official anthropics This skill guides intentional visual design for new interfaces and redesigns. Use it when a generic generated UI is not enough. Claude Code Claude.ai 1,025,790installs design-taste-frontend Leonxlnx This skill directs the design of public pages and work screens, then checks the result in a browser through screenshots. Use it when an interface feels generic or unfinished. Claude Code CX Codex Cursor +1 673,100installs

Questions about stitch::extract-static-html

How do I install stitch::extract-static-html in Claude Code?

Run npx skills add google-labs-code/stitch-skills --skill stitch::extract-static-html -a claude-code. An alternative install command is npx skills add google-labs-code/stitch-skills --skill extract-static-html -a claude-code.

What does stitch::extract-static-html do?

It captures a rendered web app page and saves a standalone HTML file with styles and images embedded. Scripts and development overlays are removed.

What is required for a Puppeteer snapshot?

The app must be running locally, and Node.js must have puppeteer available. For a page that requires login, pass an auth script with --auth-script.

Source text

SKILL.md · Apache-2.0
---
name: stitch::extract-static-html
description: >-
  Extract self-contained static HTML from a built web application or React components by inlining CSS and images. Use this skill whenever you need to capture a specific UI state, share a static version of a page, or prepare assets for Stitch upload, even if the user just asks to 'save the HTML' or 'mock the view'.
allowed-tools:
  - "stitch*:*"
  - "Bash"
  - "Read"
  - "Write"
  - "web_fetch"
---

# Extract Static HTML

Extract a self-contained static HTML file from any web application.

## Which Strategy to Use

You MUST ask the user to choose which strategy to use before proceeding. Present the options clearly, **recommend Strategy A** as the preferred default, and **provide a brief pros/cons summary** for each option to help them make an informed decision.

| | Strategy A (Puppeteer) | Strategy B (Browser Subagent) |
| :--- | :--- | :--- |
| **When** | App runs locally, no auth wall | Need to interact with page first (click, fill forms) |
| **Fidelity** | **Highest — computed styles resolved** | High — rendered DOM |
| **Setup** | **Zero — no mock needed** | Zero — no mock needed |
| **Framework** | **Any** | Any |
| **Output** | **Writes to file — no size limit** | May truncate in agent context |

> [!WARNING]
> **Checkpoint — User Confirmation Required.**
> You **MUST** ask the user which strategy they prefer before proceeding.
> Present the comparison table above, recommend Strategy A as the default, and
> wait for explicit approval. Do **NOT** make the decision yourself or proceed
> until the user confirms.

***

## Strategy A: Puppeteer Snapshot (Recommended)

Launches headless Chrome, captures the fully rendered DOM, and produces a self-contained HTML file with all CSS inlined and images as base64. Works with **any framework** — no MockPage.jsx needed.

### Prerequisites

- App running locally (e.g., `npm run dev`)
- Node.js with `puppeteer` available (check: `node -e "require('puppeteer')"`)

### Workflow

1.  **Start the App** and note the port.

    > [!WARNING]
    > **Checkpoint — User Confirmation Required.**
    > After starting the local server, you **MUST** pause and ask the user for
    > confirmation before running the snapshot script or launching a browser
    > subagent. Report the URL and port to the user so they can verify the app
    > is running and rendering correctly. Do **NOT** proceed to the snapshot
    > step until the user confirms.

2.  **Run the Snapshot Script**:
    ```bash
    npx tsx <SKILL_DIR>/scripts/snapshot.ts \
      --url http://localhost:5173 \
      --output .stitch/home.html \
      --wait 2000
    ```

3.  **Multiple pages** — run once per route:
    ```bash
    npx tsx <SKILL_DIR>/scripts/snapshot.ts \
      --url http://localhost:5173 --output .stitch/home.html --wait 2000
    npx tsx <SKILL_DIR>/scripts/snapshot.ts \
      --url http://localhost:5173/pricing --output .stitch/pricing.html --wait 2000
    npx tsx <SKILL_DIR>/scripts/snapshot.ts \
      --url http://localhost:5173/dashboard --output .stitch/dashboard.html --wait 2000 --html-class dark
    ```

4.  **Clean Up Dev Server**:
    If a local dev server was started specifically for snapshot extraction, make sure to stop the server process or terminate the background task once extraction is completed.


### Script Flags

| Flag | Default | Description |
| :--- | :--- | :--- |
| `--url` | *(required)* | URL to capture |
| `--output` | *(required)* | Output file path |
| `--wait` | `1000` | Extra wait (ms) after network idle. Increase for lazy-loading apps. |
| `--viewport` | `1280x800` | Viewport size as `WIDTHxHEIGHT` |
| `--html-class` | — | Class(es) for `<html>` element (e.g., `dark`) |
| `--remove-fixed` | `false` | Remove fixed/sticky elements (cookie banners, chat widgets) |
| `--full-height` | `false` | Resize viewport to full scroll height |
| `--title` | — | Override page title (set to the route path, e.g. `/dashboard` or `/settings/profile`) |
| `--auth-script` | — | Path to a JS/TS module that exports a default `async (page) => void` function for authentication |
| `--inline-canvas` | `false` | Convert `<canvas>` elements (ECharts, Chart.js, D3) to base64 `<img>` tags |

### What It Does Automatically

- Captures all CSSOM rules from `document.styleSheets` (preserves dynamic Vite/Tailwind dev styles and CSS-in-JS)
- Inlines all `<link rel="stylesheet">` → `<style>` blocks
- Converts `<img>` `src` **and `srcset`** → base64 data URIs (skips external fonts)
- Inlines same-origin and relative icon font files (`@font-face`) as base64 data URIs so ligatures never render as ASCII text
- Inlines `<source srcset>` URLs as base64
- Removes failed/dead `srcset` entries so the browser falls back to the inlined `src`
- Removes `<script>` tags, Vite HMR dev style blocks (`createHotContext`, `import.meta.hot`), and dev overlays
- Resolves relative CSS `url()` paths before inlining

### Framework Notes

| Framework | Notes |
| :--- | :--- |
| **React + Vite** | Works out of the box. `--wait 1000`. |
| **Next.js** | `--wait 3000` for SSR hydration. URL: `http://localhost:3000`. `<img srcset>` from `/_next/image` is auto-inlined as base64. |
| **Angular (@angular/cli / v17+)** | Works out of the box with `ng serve` (default URL: `http://localhost:4200`). `--wait 2000` for Angular Material / PrimeNG animation hydration and lazy-loaded routes. |
| **Vue / Nuxt** | Works out of the box. |
| **Svelte / SvelteKit** | Works out of the box. |
| **Storybook** | Use story URL: `--url http://localhost:6006/?path=/story/...` |
| **SSR (Webpack)** | May need longer `--wait`. |

### Troubleshooting

| Issue | Solution |
| :--- | :--- |
| Images missing | Increase `--wait` |
| Images show as broken after server stops | Verify `srcset` was inlined — check log for "Inlined N images". If `srcset` URLs failed, they are auto-removed so `src` (inlined) is used. |
| Icons display as text / Serif unstyled font | Ensure `snapshot.ts` captures CSSOM from `document.styleSheets` (step 0) and same-origin icon fonts (`@font-face`) are inlined as base64 data URIs. |
| Next.js `/_next/image` not inlined | Ensure the dev server is running when snapshot runs — the script fetches optimized images from the running server. |
| Dark mode not applied | `--html-class dark` |
| Cookie banner in output | `--remove-fixed` |
| Page requires login | Use `--auth-script ./auth.ts` (see Auth-Gated Pages below) |
| Charts/graphs show as blank boxes | Use `--inline-canvas` to serialize `<canvas>` to base64 `<img>` |
| `Cannot find module 'puppeteer'` | `npm install -g puppeteer` |

### Auth-Gated Pages

For apps with login guards (Vue Router `beforeEach`, React `ProtectedRoute`, etc.), create a small auth script that runs in the Puppeteer session:

```ts
// auth-myapp.ts
import type { Page } from 'puppeteer';

export default async function authenticate(page: Page) {
  // Example 1: Fill and submit a login form
  await page.type('#username', 'admin');
  await page.type('#password', 'password123');
  await page.click('#login-button');
  await page.waitForNavigation({ waitUntil: 'networkidle2' });

  // Example 2: Inject cookies/localStorage directly
  // await page.evaluate(() => {
  //   localStorage.setItem('token', 'mock-jwt-token');
  // });

  // Example 3: Call the app's own login API via module injection (Vue/Vite)
  // await page.evaluate(() => {
  //   return new Promise((resolve) => {
  //     const script = document.createElement('script');
  //     script.type = 'module';
  //     script.textContent = `
  //       import { useUserStore } from '/src/store/modules/user.ts';
  //       import { fetchLogin } from '/src/api/auth.ts';
  //       const res = await fetchLogin({ userName: 'Admin', password: '123456' });
  //       useUserStore().setToken(res.token, res.refreshToken);
  //       window.dispatchEvent(new CustomEvent('auth-done'));
  //     `;
  //     document.head.appendChild(script);
  //     window.addEventListener('auth-done', () => resolve(true), { once: true });
  //   });
  // });
}
```

Then use it:
```bash
npx tsx <SKILL_DIR>/scripts/snapshot.ts \
  --url http://localhost:5173/#/dashboard \
  --output .stitch/dashboard.html \
  --auth-script ./auth-myapp.ts \
  --inline-canvas \
  --wait 5000
```

The script navigates to the `--url` first (which may redirect to login), runs your auth function, then **re-navigates** to the original `--url` with the authenticated session.

***

## Strategy B: Browser Subagent Capture

Use when you need to **interact with the page** (click buttons, fill forms, navigate tabs) before capturing. The browser subagent gives you full control but output may truncate for large pages.

### Workflow

1.  **Start the App** locally.
2.  **Navigate** using a browser subagent.
3.  **Interact** as needed (click, scroll, fill forms).
4.  **Extract DOM**: `document.documentElement.outerHTML`

    > [!WARNING]
    > Large pages may truncate. To handle this:
    > - Remove `<style>` tags before extraction: `document.querySelectorAll('style').forEach(el => el.remove())`
    > - Re-add styles statically (Tailwind CDN link, source CSS)
5.  **Save** to file.

***

## Appendix: Static Fallback (MockPage.jsx)

> [!NOTE]
> This method is a **last resort** for when the app cannot run locally (broken deps, missing backend, auth walls with no bypass). It requires manually flattening React components into a single JSX file. **Prefer Strategy A whenever possible.**

### When to Use

- App can't run locally at all
- Page requires auth with no mock/bypass
- You need a specific UI state that's impossible to reach by navigation (error screens, empty states)

### Quick Reference

```bash
npx tsx <SKILL_DIR>/scripts/extract_inline_html.ts \
  --index-css src/css/App.css \
  --extra-css index.html \
  --outdir .stitch \
  --page src/MockPage.jsx:Page.html:"Page Title"
```

**Key flags**: `--no-tailwind` (non-Tailwind apps), `--html-class dark` (dark mode), `--css-files` (extra CSS files).

**Auto-detection**: Tailwind config is auto-detected. `@apply` directives automatically use `<style type="text/tailwindcss">`.

### MockPage.jsx Rules

1. **Include the full layout** — header, sidebar, footer (read `App.js` first)
2. **Flatten all conditionals** — pick one state, remove all ternaries and `&&` guards
3. **Hardcode all data** — replace `{variable}` with concrete values, unroll `.map()` loops
4. **Preserve logos** — use `<img>` with local paths (post-process will inline them)
5. **Remove floating elements** — cookie banners, chat widgets, feedback buttons

### Post-Processing

Inline local images:
```bash
npx tsx <SKILL_DIR>/scripts/post_process.ts \
  .stitch/Page.html --base-dir <app-directory>
```

© google-labs-code · Apache-2.0 · google-labs-code/stitch-skills

See also

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor