Stitch → Vite / React Components
Constraint: Only use this skill when the user explicitly mentions "Stitch" and React (Vite, CRA, or just "React app" without Next.js).
You are a frontend engineer converting Stitch mobile/desktop designs into clean, modular React components using Vite + TypeScript. This skill targets plain React apps — not Next.js App Router. For Next.js, use stitch-nextjs-components instead.
When to use this skill vs. Next.js
| Scenario | Use |
|---|---|
| User says "React app", "Vite", "CRA" | stitch-react-components |
| User says "Next.js", "App Router", "SSR" | stitch-nextjs-components |
| User wants shadcn/ui components added after | stitch-react-components → then stitch-shadcn-ui |
| User wants server-side rendering or file-based routing | stitch-nextjs-components |
Prerequisites
An HTML source. Any one of these works:
- A Stitch screen — needs Stitch MCP access and a generated screen
- A local HTML file — no Stitch account required
- A URL — no Stitch account required
Also:
- Node.js + npm/pnpm
- Vite + React project initialized:
npm create vite@latest my-app -- --template react-ts
Step 1: Resolve the source
Everything downstream reads one file: temp/source.html. Get the HTML there by whichever route matches what the user gave you, then continue at Step 2 — the rest of this skill is identical regardless of where the markup came from.
From a Stitch screen:
- Namespace discovery —
list_toolsto find the Stitch MCP prefix - Fetch metadata —
[prefix]:get_screenwith numericprojectIdandscreenId - Download HTML — GCS URLs need the reliable downloader:
bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html" - Visual audit — check
screenshot.downloadUrlbefore rewriting. Append=s0to that URL for full resolution; the bare URL serves a 512px thumbnail regardless of thewidth/heightthe API reports.
From a local HTML file:
mkdir -p temp && cp "path/to/design.html" temp/source.html
From a URL:
bash scripts/fetch-stitch.sh "https://example.com/page" "temp/source.html"
Despite the name, that script is a generic hardened downloader — follows redirects, retries transient failures, handles gzip, and fails loudly on an empty result. It does not care whether the URL points at Stitch.
From a screenshot: there's no upload route — the Stitch MCP API has no image-upload tool. Either recreate the design from a text prompt via stitch-mcp-generate-screen-from-text, or hand-write the HTML and use the local-file route above.
Only the Stitch route needs an API key. Converting a local file or a URL works with no Google account at all.
Step 2: Project structure
src/
├── components/ ← One file per component
│ └── [Name].tsx
├── data/
│ └── mockData.ts ← Static content (never in components)
├── theme/
│ ├── tokens.ts ← Design token constants
│ └── useTheme.ts ← Dark mode hook
├── types/
│ └── index.ts ← Shared TypeScript types
├── App.tsx ← Root component
└── main.tsx ← Entry point
Step 3: Extract design tokens
Resolve tokens from whatever the HTML actually gives you, in this order:
- Inline
tailwind.configin<head>(what Stitch emits) — use it directly if present. - CSS custom properties (
:root { --color-primary: ... }) — common in hand-written and templated HTML. - A linked or inline stylesheet — parse declared colors, font-families, radii, spacing.
- Last resort — derive tokens from the most frequent computed values in the markup (dominant background, text color, accent, heading/body font, border radius), and tell the user what you inferred so they can correct it.
The URL route only downloads the single HTML response — externally-linked stylesheets may not come along for the ride. If none of the above resolves a token, say so instead of inventing a palette.
// src/theme/tokens.ts
export const lightTokens = {
background: '#FFFFFF',
surface: '#F4F4F5',
primary: '#6366F1',
primaryFg: '#FFFFFF',
text: '#09090B',
textMuted: '#71717A',
border: '#E4E4E7',
} as const
export const darkTokens = {
background: '#09090B',
surface: '#18181B',
primary: '#818CF8',
primaryFg: '#09090B',
text: '#FAFAFA',
textMuted: '#A1A1AA',
border: '#27272A',
} as const
export type ThemeTokens = typeof lightTokens
// src/theme/useTheme.ts
import { useEffect, useState } from 'react'
import { lightTokens, darkTokens, type ThemeTokens } from './tokens'
/**
* Returns current theme tokens based on system color scheme.
* Listens for system-level dark/light mode changes.
*/
export function useTheme(): ThemeTokens {
const [isDark, setIsDark] = useState(
() => window.matchMedia('(prefers-color-scheme: dark)').matches
)
useEffect(() => {
const mq = window.matchMedia('(prefers-color-scheme: dark)')
const handler = (e: MediaQueryListEvent) => setIsDark(e.matches)
mq.addEventListener('change', handler)
return () => mq.removeEventListener('change', handler)
}, [])
return isDark ? darkTokens : lightTokens
}
Step 4: Component conversion rules
Layout mapping
| HTML/CSS | → React / Tailwind |
|---|---|
display:flex; flex-direction:column |
<div className="flex flex-col gap-4"> |
display:flex; flex-direction:row |
<div className="flex items-center gap-2"> |
justify-content:space-between |
<div className="flex justify-between"> |
display:grid; grid-template-columns:1fr 1fr |
<div className="grid grid-cols-2 gap-4"> |
overflow-y:scroll |
<div className="overflow-y-auto"> |
| Long list | items.map(item => <Card key={item.id} {...item} />) |
<img> |
<img src="..." alt="..." className="object-cover"> |
Tailwind class mapping
Use the source HTML's Tailwind classes directly in JSX where they don't reference custom tokens. Map custom tokens to CSS variables:
// Source HTML: bg-primary → CSS variable → Tailwind arbitrary value
// OR: use inline style with token value
// Option A — Tailwind arbitrary value (if custom tokens in tailwind.config)
<div className="bg-[--color-primary] text-[--color-primaryFg]">
// Option B — inline style with useTheme()
const theme = useTheme()
<div style={{ backgroundColor: theme.primary, color: theme.primaryFg }}>
Component template
// src/components/StitchComponent.tsx
/**
* Props for StitchComponent — all data via props, never fetched inside.
*/
interface StitchComponentProps {
/** Primary heading text */
title: string
/** Supporting description — optional */
description?: string
/** Primary action callback */
onAction?: () => void
}
/**
* StitchComponent — [describe purpose in one sentence]
*/
export function StitchComponent({
title,
description,
onAction,
}: Readonly<StitchComponentProps>) {
const theme = useTheme()
return (
<div
className="rounded-xl border p-4 gap-2 flex flex-col"
style={{
backgroundColor: theme.surface,
borderColor: theme.border,
}}
>
<h3 className="text-base font-semibold" style={{ color: theme.text }}>
{title}
</h3>
{description ? (
<p className="text-sm" style={{ color: theme.textMuted }}>
{description}
</p>
) : null}
{onAction ? (
<button
className="rounded-lg px-4 py-2 text-sm font-medium transition-opacity hover:opacity-90"
style={{ backgroundColor: theme.primary, color: theme.primaryFg }}
type="button"
>
Action
</button>
) : null}
</div>
)
}
Step 5: Architectural rules
- One component per file — no single-file spaghetti
- Static data in
src/data/mockData.ts— never hardcoded in JSX - Shared types in
src/types/index.ts - Every component has
Readonly<ComponentNameProps>interface - No hardcoded hex colors — use
useTheme()or CSS variables - No
anytypes
Step 6: Integration with shadcn/ui
After converting the design to base React components, you can layer in shadcn/ui:
npx shadcn@latest init # Set up shadcn in your Vite project
npx shadcn@latest add button card input dialog
Then use stitch-shadcn-ui skill to replace raw HTML elements with shadcn components while preserving the design tokens.
Troubleshooting
| Issue | Fix |
|---|---|
| Tailwind classes not applying | Check tailwind.config.js includes ./src/**/*.{ts,tsx} in content |
| Dark mode not toggling | Verify useTheme() is called at component level, not hoisted |
| Images not showing | Add explicit width and height or use className="w-full h-auto" |
| Type error on props | Ensure Readonly<> wrapper and all required props are provided |
References
resources/component-template.tsx— Boilerplate componentresources/architecture-checklist.md— Pre-ship checklistreferences/tailwind-to-react.md— Token + class mapping guide (source HTML → React/Tailwind)scripts/fetch-stitch.sh— Reliable GCS HTML downloaderstitch-shadcn-ui— Add shadcn/ui components after base conversiondocs/tailwind-reference.md— Tailwind utility class lookup