Stitch html components
Your coding agent designs terrible UI. stitch-kit wires it into Google Stitch MCP and teaches it the whole pipeline — ideation, screen generation, design systems, production components.
npx -y skills add gabelul/stitch-kit --skill stitch-html-componentsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Converts a Stitch screen, a local HTML file, or a URL into clean, platform-agnostic HTML5 + CSS — semantic markup, CSS custom properties for theming, dark mode via prefers-color-scheme, mobile-first responsive, zero framework dependencies. Works in browsers, WebViews, Capacitor, and Ionic. Only the Stitch route needs an API key.
SKILL.md
12.4 KB, as published. Nobody here has run it
Stitch → HTML5 + CSS (Platform-Agnostic)
You are a frontend engineer specializing in clean, dependency-free HTML and CSS. You convert HTML sources — Stitch screens, local files, or URLs — into semantic HTML5 with CSS custom properties — no React, no Svelte, no build step. The output runs anywhere: desktop browsers, mobile browsers, iOS WebView, Android WebView, Capacitor apps, and Ionic shells.
When to use this skill
Use this skill when:
- The user wants platform-agnostic output — "just HTML", "no framework", "works everywhere"
- The target is a WebView in a mobile app (Capacitor, Ionic, Cordova)
- Building a static site or embedding in a CMS
- The user hasn't chosen a framework yet and wants a working prototype
- Generating the HTML base before wrapping with Capacitor for native mobile
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
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_screenfor the design JSON - 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: File structure
output/
├── index.html ← Main page (or rename per screen)
├── css/
│ ├── tokens.css ← CSS custom properties (light + dark)
│ ├── base.css ← Reset, body defaults, typography
│ └── components.css ← Component styles
├── js/
│ └── main.js ← Minimal JS (theme toggle, mobile menu only)
└── assets/
└── images/
For multi-screen projects, each screen gets its own HTML file. Shared CSS lives in tokens.css and base.css.
Step 3: CSS custom properties
Map source colors to semantic tokens. Always generate both light and dark at the same time. Resolve the hex values in this order:
- Inline
tailwind.configin<head>(what Stitch emits) — use it directly if present. - CSS custom properties already in the source (
: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.
/* css/tokens.css */
:root {
--color-background: [hex];
--color-surface: [hex];
--color-primary: [hex];
--color-primary-fg: [hex];
--color-text: [hex];
--color-text-muted: [hex];
--color-border: [hex];
--font-sans: [font-stack];
--font-mono: ui-monospace, monospace;
--radius-sm: [value];
--radius-md: [value];
--radius-lg: [value];
--shadow-sm: 0 1px 3px rgb(0 0 0 / 0.1);
--shadow-md: 0 4px 12px rgb(0 0 0 / 0.1);
--transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* Dark mode — system preference */
@media (prefers-color-scheme: dark) {
:root {
--color-background: [dark-hex];
--color-surface: [dark-hex];
--color-primary: [dark-adjusted-hex];
--color-primary-fg: [dark-hex];
--color-text: [dark-hex];
--color-text-muted: [dark-hex];
--color-border: [dark-hex];
}
}
/* Dark mode — manual toggle via data-theme="dark" on <html> */
[data-theme="dark"] {
--color-background: [dark-hex];
--color-surface: [dark-hex];
/* ... same values as above */
}
If the project already has a design-tokens.css (from stitch-design-system), import it instead of recreating tokens.
Step 4: Base CSS
/* css/base.css */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html {
font-size: 16px;
/* Safe area insets for notched phones */
padding: env(safe-area-inset-top) env(safe-area-inset-right)
env(safe-area-inset-bottom) env(safe-area-inset-left);
-webkit-text-size-adjust: 100%; /* Prevent font scaling on iOS rotation */
}
body {
font-family: var(--font-sans);
background-color: var(--color-background);
color: var(--color-text);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/* Skip link for accessibility */
.sr-only {
position: absolute; width: 1px; height: 1px;
padding: 0; margin: -1px; overflow: hidden;
clip: rect(0,0,0,0); white-space: nowrap; border-width: 0;
}
.skip-link { /* ... see stitch-a11y skill */ }
/* Focus ring — keyboard only */
*:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
border-radius: 2px;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Step 5: Semantic HTML structure
Convert Stitch layout to semantic HTML5. Never use <div> where a semantic element fits:
| Stitch element | → HTML element |
|---|---|
| Page shell / app chrome | <body> → <header> + <main> + <footer> |
| Navigation bar | <nav aria-label="Main navigation"> |
| Primary content area | <main id="main-content"> |
| Article/post/card content | <article> |
| Sidebar | <aside> |
| Section of page | <section aria-labelledby="section-id"> |
| Button (no href) | <button type="button"> |
| Link with navigation | <a href="..."> |
| Form container | <form> with <label> for every input |
| Images | <img alt="descriptive text"> |
Mobile HTML template:
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<!-- Critical for mobile: prevent zoom, respect viewport -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<!-- iOS PWA meta tags -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<!-- Theme color (top browser bar on Android) -->
<meta name="theme-color" content="[--color-background hex]">
<title>[Screen title]</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="stylesheet" href="css/tokens.css">
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/components.css">
</head>
<body>
<!-- Skip nav for keyboard users -->
<a href="#main-content" class="skip-link">Skip to content</a>
<!-- App shell -->
<div class="app-shell">
<header class="app-header" role="banner">
<nav aria-label="Main navigation">
<!-- Nav content -->
</nav>
</header>
<main id="main-content" class="app-main">
<!-- Page content -->
</main>
<!-- Bottom nav (mobile) -->
<nav class="bottom-nav" aria-label="Tab navigation">
<!-- Tab items -->
</nav>
</div>
<script src="js/main.js" type="module"></script>
</body>
</html>
Step 6: Mobile-first CSS rules
Every component must follow these mobile constraints:
Touch targets
/* All interactive elements: minimum 44×44px tap area */
button, a, [role="button"] {
min-height: 44px;
min-width: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
Scrolling
/* Smooth scroll on iOS */
.scrollable {
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain; /* Prevent scroll chaining */
}
Bottom navigation bar
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
/* Account for iOS home indicator */
padding-bottom: env(safe-area-inset-bottom);
background: var(--color-surface);
border-top: 1px solid var(--color-border);
display: flex;
justify-content: space-around;
z-index: 100;
}
/* Push main content above bottom nav */
.app-main {
padding-bottom: calc(60px + env(safe-area-inset-bottom));
}
Responsive breakpoints
/* Mobile-first: base styles are for mobile */
/* Tablet (768px+) */
@media (min-width: 768px) {
.container { max-width: 720px; margin: 0 auto; }
}
/* Desktop (1024px+) */
@media (min-width: 1024px) {
.container { max-width: 1200px; }
.bottom-nav { display: none; } /* Hide bottom nav, use sidebar */
.sidebar { display: block; } /* Show desktop sidebar */
}
Step 7: Minimal JavaScript
Keep JS to an absolute minimum. The only things that need JS:
- Theme toggle (dark/light)
- Mobile menu open/close
- Accordion/tabs interactive behavior
// js/main.js
// Dark mode toggle
const html = document.documentElement;
const savedTheme = localStorage.getItem('theme') || 'light';
html.setAttribute('data-theme', savedTheme);
document.getElementById('theme-toggle')?.addEventListener('click', () => {
const next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
// Mobile menu toggle
document.getElementById('menu-toggle')?.addEventListener('click', () => {
const nav = document.getElementById('mobile-nav');
const expanded = nav.getAttribute('aria-expanded') === 'true';
nav.setAttribute('aria-expanded', String(!expanded));
nav.classList.toggle('is-open');
});
Step 8: Capacitor / WebView notes
If this HTML will be embedded in a Capacitor or Ionic app:
- All asset paths must be relative (no
/prefix):./css/tokens.css,./assets/logo.png - Capacitor reads from the
www/ordist/directory — output there - Capacitor's
capacitor.config.jsonsets the WebDir:"webDir": "www" - To call native APIs (camera, filesystem), use the Capacitor plugin JS APIs — beyond this skill's scope
Troubleshooting
| Issue | Fix |
|---|---|
| Fonts look zoomed on iOS rotation | Add -webkit-text-size-adjust: 100% to html |
| Content under notch | Add env(safe-area-inset-*) padding |
| Scrolling feels laggy on iOS | Add -webkit-overflow-scrolling: touch |
| Bottom nav overlaps content | Add padding-bottom to <main> equal to nav height + safe area |
| Click delay on mobile (300ms) | Add touch-action: manipulation to buttons |
Integration
- Run
stitch-design-systemfirst to generatedesign-tokens.css— import it instead of recreating tokens - Run
stitch-a11yafter for an accessibility pass - Run
stitch-animateto add CSS-only transitions (zero JS needed for most animations)
References
resources/mobile-template.html— Full mobile HTML boilerplateresources/architecture-checklist.md— Pre-ship checklistscripts/fetch-stitch.sh— Reliable GCS HTML downloader