Stitch react components
Converts a Stitch screen, a local HTML file, or a URL into modular Vite + React components — TypeScript, theme-mapped Tailwind, dark mode via CSS variables, and clean component architecture. Use this for Vite/React apps without App Router. For Next.js 15 App Router, use stitch-nextjs-components instead. Only the Stitch route needs an API key.From its SKILL.md
npx -y skills add gabelul/stitch-kit --skill stitch-react-componentsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- runs commandsInstructs the agent to run 5 commands, including `bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html"` and 4 more.
- fetches URLsInstructs the agent to fetch 3 URLs, including [htmlCode.downloadUrl] and 2 more.
SKILL.md
9.8 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
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
onClick={onAction}
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
What ships with it: 4 files
7.7 KB alongside SKILL.md, 1 of them executable
references/
- tailwind-to-react.md3.9 KB
resources/
- architecture-checklist.md1.5 KB
- component-template.tsx1008 B
scripts/
- fetch-stitch.shruns1.3 KB
Gives 0 of the 12 instructions most css styling skills give in ~2.4k tokens
Counted across 512 of the 512 authors here whose files we hold, read 2026-09-06
- Animate only transform and opacityin 32 of 512, across 30 files
- Respect prefers-reduced-motionin 21 of 512
- Support reduced motion preferencesin 16 of 512, across 6 files
- Use Tailwind CSS for stylingin 14 of 512, across 13 files
- Specify AnimatePresence mode explicitlyin 12 of 512, across 2 files
- Set initial states explicitlyin 12 of 512, across 2 files
- Use semantic HTML elementsin 11 of 512, across 10 files
- Use oklch for color valuesin 11 of 512, across 10 files
- Honor prefers-reduced-motion in animationsin 10 of 512
- Provide a reduced-motion fallback for animationsin 10 of 512, across 9 files
- Use property names in camelCasein 9 of 512, across 4 files
- Ensure UI animations stay under 300msin 9 of 512, across 6 files
Said here and by no other author read
- Extract tokens in order
- Put static data in mockData
- Put shared types in index
- Use Readonly props interface
- Use useTheme for colors
- Avoid any types
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.