A CLI tool that turns a UI screenshot into a clean, interactive React TypeScript component using a multi-pass AI pipeline.
❯ s2c ./screenshots/navbar.png
✓ image loaded
✓ screenshot analysed
✓ interactions analysed
✓ component generated (3 files)
✓ wrote Navbar.tsx
✓ wrote useNavbar.ts
✓ wrote navbar.types.ts
✓ opened in VS Code
❯ s2c ./screenshots/navbar.png --animate
✓ image loaded
✓ screenshot analysed
✓ interactions analysed
✓ component generated (3 files)
✓ animations added
✓ wrote Navbar.tsx
✓ wrote useNavbar.ts
✓ wrote navbar.types.ts
✓ opened in VS Code
❯ s2c ./screenshots/navbar.png --single
✓ image loaded
✓ screenshot analysed
✓ interactions analysed
✓ component generated
✓ wrote Navbar.tsx
✓ opened in VS Code
❯ s2c new-navbar.png --refine src/components/Navbar.tsx
✓ image loaded
✓ existing component loaded
✓ screenshot analysed
✓ interactions analysed
✓ component refined
✓ wrote src/components/Navbar.tsx
✓ opened in VS Code
❯ s2c closed.png open.png
✓ images loaded (2)
✓ screenshot analysed
✓ state transition analysed
✓ interactions analysed
✓ component generated (3 files)
✓ wrote Closed.tsx
✓ wrote useClosed.ts
✓ wrote closed.types.ts
✓ opened in VS Code
- Node.js ≥ 18
- A Gemini API key (see API Key)
# 1. Clone the repo
git clone https://github.com/your-username/screenshot-to-code
cd screenshot-to-code
# 2. Install dependencies and build
npm install
npm run build
# 3. Install globally so `s2c` is available anywhere
npm install -g .After this, s2c will work from any directory.
Create a .env file in the project root with your key:
GEMINI_API_KEY=AIza...
The tool picks this up automatically. You can also export it in your shell instead:
export GEMINI_API_KEY=AIza...Get a key at aistudio.google.com/apikey.
# Convert a single screenshot
s2c <image> [options]
# Two-states mode — generate one component with toggle logic
s2c <image1> <image2> [options]
# Refinement mode — update an existing component to match a new screenshot
s2c <image> --refine <Component.tsx> [options]
# Watch a directory for new screenshots
s2c --watch <dir> [options]| Flag | Short | Description | Default |
|---|---|---|---|
--watch <dir> |
-w |
Watch a directory and convert new images automatically | — |
--animate |
-a |
Add Framer Motion animations (4th AI pass) | — |
--style <preset> |
-s |
Styling approach: tailwind, css-modules, styled-components |
tailwind |
--refine <file> |
-r |
Update an existing component to match the new screenshot (sends file contents to Gemini API) | — |
--single |
-1 |
Output a single .tsx file instead of the default 3-file feature slice |
— |
--name <Name> |
-n |
Override the component name | Derived from filename |
--output <path> |
-o |
Output file or directory | Current directory |
--model <id> |
-m |
Use a different AI model | gemini-2.5-flash |
--components <dir> |
Component library directory for design system context injection | Auto-detected | |
--no-design-system |
Skip component and Tailwind context injection entirely | — | |
--no-open |
Skip opening VS Code | — | |
--help |
-h |
Show help | — |
.png, .jpg, .jpeg, .webp
# Basic — writes Navbar.tsx next to the image and opens it
s2c ./screenshots/navbar.png
# Custom component name and output directory
s2c hero.jpg --name HeroSection --output ./src/components
# Use a higher quality model
s2c dashboard.png --model gemini-2.5-pro
# Generate without opening VS Code
s2c button.png --no-open
# Write to a specific file path
s2c button.png --output ./src/components/Button.tsx
# Add Framer Motion animations
s2c navbar.png --animate
# Use CSS Modules instead of Tailwind (writes Navbar.tsx + Navbar.module.css)
s2c navbar.png --style css-modules
# Use styled-components
s2c navbar.png --style styled-components
# Full pipeline — custom name, output dir, CSS modules
s2c hero.png --name HeroSection --output ./src/components --style css-modules
# Refinement — update an existing component to match a new screenshot
s2c new-navbar.png --refine src/components/Navbar.tsx
# Two-states — generate one component with toggle logic from two states
s2c modal-closed.png modal-open.png
# Combine: refine + animate
s2c new-design.png --refine src/components/Hero.tsx --animate
# Design system — point at your component library explicitly
s2c dashboard.png --components ./packages/ui/src
# Skip design system injection (faster, blank-slate output)
s2c dashboard.png --no-design-systems2c can scan your component library and Tailwind tokens before generating, so the output reuses your existing components and design language instead of inventing new ones.
❯ s2c ./screenshots/dashboard.png
✓ image loaded
✓ design system loaded (12 components, 24 color tokens)
✓ screenshot analysed
✓ interactions analysed
✓ component generated (3 files)
✓ component used 4 design system components: Card, Button, Badge, Avatar
✓ wrote Dashboard.tsx
...
Component scanning — uses the TypeScript compiler to extract exported component names, prop types, and JSDoc descriptions from your .tsx files.
Tailwind tokens — reads color tokens from tailwind.config.js (v3) or CSS @theme blocks in globals.css (v4) and injects them into the prompt so the model uses your exact color scale.
Auto-detection — s2c looks for components in these directories automatically (in order):
src/components, components/, app/components/, packages/ui/src, packages/design-system/src, libs/ui/src
Override with --components <dir> or opt out entirely with --no-design-system.
Watch mode monitors a directory and automatically converts any new image dropped into it.
s2c --watch ./screenshots --output ./src/components
s2c --watch ./screenshots --output ./src/components --animate◉ watching /screenshots
→ output: /src/components
Press Ctrl+C to stop
[Navbar] reading image...
[Navbar] analysing screenshot (gemini-2.5-flash)...
[Navbar] analysing interactions...
[Navbar] generating component...
[Navbar] ✓ wrote /src/components/Navbar.tsx
[HeroBanner] reading image...
...
Each new image gets its own component name derived from the filename. Multiple images can land in the folder at the same time — each is processed independently. Press Ctrl+C to stop.
--output is optional; without it, components are written to the directory you ran the command from.
s2c runs up to four AI passes on every screenshot before writing a single line of code:
- Visual analysis — the model inspects the screenshot and produces a structured description of the layout, colour palette, typography, spacing, background, and every UI component visible.
- Interaction analysis — identifies every interactive element (buttons, inputs, tabs, toggles, forms) and describes exactly what each one should do: what state it needs, what triggers it, and what behaviour to implement.
- Component generation — the model receives the image alongside both analyses and generates a fully typed
.tsxfile with realuseStatehooks and handlers wired up, Tailwind classes, andlucide-reacticons. - Animation pass (optional,
--animate) — takes the generated component and layers in Framer Motion: entrance animations, hover and tap feedback on interactive elements,AnimatePresencefor conditionally rendered content, and staggered list animations. Uses the interaction analysis to target the right elements.
Refinement mode (--refine) adds a "load existing component" step before the pipeline and swaps the generation prompt for an update prompt — the model preserves structure, state, and handlers, only changing what the new screenshot requires. Overwrites the target file by default.
Two-states mode (two positional images) inserts a dedicated state-transition pass between visual and interaction analysis. The model identifies what user action triggers the transition, what elements appear/disappear, and what React state variables are needed — then generates one component with the toggle already wired up.
This pipeline is what separates s2c from pasting a screenshot into a chat window — you get a component that actually works and moves, not just one that looks right.
Control the styling approach with --style:
| Preset | Output | Notes |
|---|---|---|
tailwind |
Single .tsx |
Default. Tailwind CSS classes, cn() helper, lucide-react icons. |
css-modules |
.tsx + .module.css |
Companion CSS file written alongside the component. |
styled-components |
Single .tsx |
Requires styled-components installed in your project (npm i styled-components). |
The default model is gemini-2.5-flash. Swap to any vision-capable model with --model.
| Model | Notes |
|---|---|
gemini-2.5-flash |
Default. Fast and capable. |
gemini-2.5-pro |
Slower, higher quality. |
gemini-2.0-flash |
Previous generation flash. |
Groq offers a generous free tier with much higher rate limits than Gemini's free plan. Get a key at console.groq.com/keys.
| Model | Notes |
|---|---|
meta-llama/llama-4-scout-17b-16e-instruct |
Recommended. Fast, vision-capable. |
meta-llama/llama-4-maverick-17b-128e-instruct |
Higher quality, slower. |
Note: Llama 4 models must be enabled in your Groq project before use. Visit console.groq.com/settings/project/limits to enable them.
# Set your Groq key
export GROQ_API_KEY=gsk_...
# Use a Groq model
s2c ./screenshots/navbar.png --model meta-llama/llama-4-scout-17b-16e-instructs2c detects the provider from the model name — any model starting with llama, mixtral, gemma, deepseek, qwen, or whisper is routed to Groq automatically.
The extension bundles the full generation pipeline — no CLI install required.
- Open the
extension/folder in VS Code - Press
F5to launch the Extension Development Host - The s2c icon appears in the Activity Bar (left sidebar)
To build a .vsix for distribution:
cd extension
npm install
npm run build
npx vsce packageClick the s2c icon in the Activity Bar to open the panel, then enter your API key when prompted. The key is stored in VS Code's SecretStorage — it never touches settings.json.
You can also set keys via the command palette:
s2c: Set API Key— Geminis2c: Set Groq API Key— Groq
- Drag a
.png,.jpg,.jpeg, or.webpscreenshot onto the drop zone (or click to pick) - Configure options: animations, style, single-file mode, design system path
- Click Generate Component
- Watch the per-pass progress — the component opens in your editor when done
# CLI
npm run dev # watch mode — recompiles on save
npm run build # one-shot build
npm test # run tests
# Extension
cd extension
npm run build # one-shot esbuild
npm run watch # watch mode