Stitch → Vite / React Components

SkillFiles & storage

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.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Stitch → Vite / React Components skill

What this skill tells your AI

The instructions your AI receives, as published by gabelul/stitch-kit in skills/stitch-react-components/SKILL.md and read by ahel’s review.

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

ScenarioUse
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 afterstitch-react-components → then stitch-shadcn-ui
User wants server-side rendering or file-based routingstitch-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:

  1. Namespace discoverylist_tools to find the Stitch MCP prefix
  2. Fetch metadata[prefix]:get_screen with numeric projectId and screenId
  3. Download HTML — GCS URLs need the reliable downloader:
    bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html"
    
  4. Visual audit — check screenshot.downloadUrl before rewriting. Append =s0 to that URL for full resolution; the bare URL serves a 512px thumbnail regardless of the width/height the 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:

  1. Inline tailwind.config in <head> (what Stitch emits) — use it directly if present.
  2. CSS custom properties (:root { --color-primary: ... }) — common in hand-written and templated HTML.
  3. A linked or inline stylesheet — parse declared colors, font-families, radii, spacing.
  4. 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 listitems.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 any types

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

IssueFix
Tailwind classes not applyingCheck tailwind.config.js includes ./src/**/*.{ts,tsx} in content
Dark mode not togglingVerify useTheme() is called at component level, not hoisted
Images not showingAdd explicit width and height or use className="w-full h-auto"
Type error on propsEnsure Readonly<> wrapper and all required props are provided

References

  • resources/component-template.tsx — Boilerplate component
  • resources/architecture-checklist.md — Pre-ship checklist
  • references/tailwind-to-react.md — Token + class mapping guide (source HTML → React/Tailwind)
  • scripts/fetch-stitch.sh — Reliable GCS HTML downloader
  • stitch-shadcn-ui — Add shadcn/ui components after base conversion
  • docs/tailwind-reference.md — Tailwind utility class lookup

Signals

GitHub stars
45
Forks
5
Last commit
Jul 2026
Advanced
Catalog kind
skill
Gateway key
stitch-react-components
Source
github.com/gabelul/stitch-kit