AINode.jsAutomation

Screenshot to Code with AI

Build an AI screenshot-to-code tool with Node.js and GPT-4o Vision that converts UI screenshots into HTML/CSS, React components, or Tailwind markup automatically.

TT
Emily Ross
••6 min read
Screenshot to Code with AI

Screenshot to Code with AI

Translate any UI screenshot or Figma export into working code with a single API call. This tool uses GPT-4o Vision to analyse the visual layout and generate HTML/CSS, React + Tailwind, or Vue.js code that matches the design.

This is Tool 20 of the Build 50 AI Automation Tools course.


What You'll Build

  • POST /convert — upload a screenshot, specify framework, receive working code
  • Supports HTML+CSS, React+Tailwind, React+styles, and Vue
  • POST /convert/refine — send the generated code back with feedback to refine it

Setup

bash
mkdir screenshot-to-code && cd screenshot-to-code
npm init -y
npm install express multer openai dotenv
bash
# .env
OPENAI_API_KEY=sk-your-key-here
PORT=3000

Code Generation Service

js
// src/services/codegenService.js
import OpenAI from 'openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

const FRAMEWORK_PROMPTS = {
  'html': `Generate clean, semantic HTML5 with embedded CSS.
Rules: Use CSS custom properties for colors, flexbox/grid for layout, responsive at 320px and 768px breakpoints.
Return JSON: {"html": "complete HTML document", "cssNotes": "any notable CSS decisions"}`,

  'react-tailwind': `Generate a React functional component using Tailwind CSS utility classes only.
Rules: Use React hooks for state, Tailwind v3 classes only (no custom CSS), responsive classes (sm: md: lg:), accessible HTML semantics.
Return JSON: {"componentName": "string", "code": "complete React component with import statements", "dependencies": ["any npm packages needed beyond react"]}`,

  'react-styles': `Generate a React functional component using inline styles and CSS-in-JS patterns.
Rules: All styles as JavaScript objects, use React.useState for interactive elements.
Return JSON: {"componentName": "string", "code": "complete React component"}`,

  'vue': `Generate a Vue 3 Single File Component using the Composition API and scoped styles.
Rules: Use <script setup> syntax, scoped CSS, reactive() or ref() for state.
Return JSON: {"componentName": "string", "code": "complete .vue SFC file"}`,
};

export async function screenshotToCode(buffer, mimetype, framework = 'react-tailwind', additionalInstructions = '') {
  const dataUrl = `data:${mimetype};base64,${buffer.toString('base64')}`;
  const frameworkPrompt = FRAMEWORK_PROMPTS[framework] || FRAMEWORK_PROMPTS['react-tailwind'];

  const instructions = additionalInstructions
    ? `Additional instructions: ${additionalInstructions}`
    : '';

  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      {
        role: 'system',
        content: `You are an expert frontend developer. Convert the UI screenshot into production-ready code.
${frameworkPrompt}
${instructions}
Analyse the screenshot carefully: identify the layout structure, components, spacing, colors, typography, and interactive elements.
Generate complete, working code that visually matches the screenshot as closely as possible.`,
      },
      {
        role: 'user',
        content: [
          { type: 'text', text: 'Convert this UI screenshot to code:' },
          { type: 'image_url', image_url: { url: dataUrl, detail: 'high' } },
        ],
      },
    ],
    temperature: 0.2,
    max_tokens: 4000,
    response_format: { type: 'json_object' },
  });

  return JSON.parse(response.choices[0].message.content);
}

export async function refineCode(existingCode, feedback, framework = 'react-tailwind') {
  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      {
        role: 'system',
        content: `You are an expert frontend developer. Refine the ${framework} code based on the feedback.
Return JSON with the same structure as the original code, with changes applied.`,
      },
      {
        role: 'user',
        content: `EXISTING CODE:\n${existingCode}\n\nFEEDBACK TO APPLY:\n${feedback}`,
      },
    ],
    temperature: 0.3,
    max_tokens: 4000,
    response_format: { type: 'json_object' },
  });

  return JSON.parse(response.choices[0].message.content);
}

API Routes + Server

js
// src/server.js
import 'dotenv/config';
import express from 'express';
import multer from 'multer';
import { screenshotToCode, refineCode } from './services/codegenService.js';

const app = express();
app.use(express.json({ limit: '5mb' }));
const upload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: 10 * 1024 * 1024 },
  fileFilter: (_req, file, cb) =>
    ['image/jpeg', 'image/png', 'image/webp'].includes(file.mimetype)
      ? cb(null, true) : cb(new Error('PNG, JPEG, or WebP required')),
});

app.post('/convert', upload.single('screenshot'), async (req, res, next) => {
  try {
    if (!req.file) return res.status(400).json({ error: 'No screenshot uploaded' });
    const { framework, instructions } = req.body;
    const result = await screenshotToCode(req.file.buffer, req.file.mimetype, framework, instructions);
    res.json({ success: true, framework: framework || 'react-tailwind', ...result });
  } catch (err) { next(err); }
});

app.post('/convert/refine', async (req, res, next) => {
  try {
    const { code, feedback, framework } = req.body;
    if (!code || !feedback) return res.status(400).json({ error: 'code and feedback required' });
    const result = await refineCode(code, feedback, framework);
    res.json({ success: true, ...result });
  } catch (err) { next(err); }
});

app.use((err, _req, res, _next) => res.status(500).json({ error: err.message }));
app.listen(process.env.PORT ?? 3000, () => console.log('Screenshot to Code running'));

Testing

bash
# Convert a screenshot to React + Tailwind
curl -X POST http://localhost:3000/convert \
  -F "screenshot=@ui-design.png" \
  -F "framework=react-tailwind" \
  -F "instructions=Use a dark theme. The primary color is indigo-600."

# Refine the generated code
curl -X POST http://localhost:3000/convert/refine \
  -H "Content-Type: application/json" \
  -d '{
    "code": "const Card = () => ...",
    "feedback": "Make the card shadow softer and increase the padding",
    "framework": "react-tailwind"
  }'

Sample response:

json
{
  "componentName": "HeroSection",
  "code": "import { useState } from 'react';\n\nconst HeroSection = () => {\n  return (\n    <div className=\"min-h-screen bg-gradient-to-br from-indigo-50 to-white flex items-center justify-center px-4\">\n      <div className=\"max-w-4xl mx-auto text-center\">\n        <span className=\"inline-block px-4 py-2 bg-indigo-100 text-indigo-700 rounded-full text-sm font-semibold mb-6\">\n          New: AI-powered workflows\n        </span>\n        <h1 className=\"text-5xl font-bold text-gray-900 mb-6 leading-tight\">\n          Build faster with <span className=\"text-indigo-600\">AI automation</span>\n        </h1>\n        <p className=\"text-xl text-gray-600 mb-10 max-w-2xl mx-auto\">\n          Automate repetitive tasks and ship 10x faster.\n        </p>\n        <div className=\"flex gap-4 justify-center\">\n          <button className=\"bg-indigo-600 hover:bg-indigo-700 text-white px-8 py-4 rounded-xl font-semibold transition-colors\">\n            Get Started Free\n          </button>\n          <button className=\"border-2 border-gray-200 hover:border-indigo-600 text-gray-700 px-8 py-4 rounded-xl font-semibold transition-colors\">\n            See Demo\n          </button>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default HeroSection;",
  "dependencies": []
}

Batch Figma Export Processing

bash
# Process a folder of Figma exports
for f in figma-exports/*.png; do
  curl -X POST http://localhost:3000/convert \
    -F "screenshot=@$f" \
    -F "framework=react-tailwind" \
    -o "components/$(basename $f .png).json"
done

Build 50 AI Automation Tools — Tool 20 of 50

Screenshot-to-code is live. Continue to Tool 21 to build an ID card OCR and validation tool.


    Summary

    • detail: 'high' is essential for screenshot-to-code — low detail mode misses UI specifics
    • Framework-specific prompts produce code conventions appropriate to each framework
    • Refine endpoint implements a human-in-the-loop workflow — generate, review, refine iteratively
    • The batch Figma export script bridges design handoff without per-component manual effort
    • For pixel-perfect output, combine AI generation with a CSS-in-JS library like Styled Components

    Continue to Tool 21: ID Card & Document OCR with AI Validation →