screenshot-to-code
Applicationabi/screenshot-to-code
Converts screenshots, mockups, and Figma designs into clean code using AI.
Overview
Screenshot-to-Code uses AI models to turn screenshots, mockups, and screen recordings into functional code for HTML, React, Vue, and more. It supports multiple frontend stacks and can generate real assets from the image. The app includes a React frontend and FastAPI backend, with options to self-host or use a hosted service.
README Preview
# screenshot-to-code\n\nConvert screenshots, mockups, Figma designs, and screen recordings into clean, functional code using AI. The easiest way to try this is using the official, hosted product at screenshottocode.com →\n\n\nhttps://github.com/user-attachments/assets/ec08a5e6-9606-41c5-b03a-1bf47dfeba75\n\n\nSupported stacks:\n\n- HTML + Tailwind\n- HTML + CSS\n- React + Tailwind\n- Vue + Tailwind\n- Bootstrap\n- Ionic + Tailwind\n\nDefault AI models:\n\n- Gemini 3 Flash Preview and Gemini 3.1 Pro Preview - the best models\n- GPT-5.5 and GPT-5.4 Mini\n- Claude Opus 4.6, Claude Opus 4.8\n- z-image-turbo (using Replicate) for image generation\n\nSee the [Examples](#-examples) section below for more demos.\n\nScreenshot to Code also supports taking a screen recording of a website in action and turning that into a functional prototype.\n\n\n\n## 🛠 Getting Started\n\nChoose the path that fits what you want to do:\n\n- **Run locally:** best if you want to customize, self-host, or contribute.\n- **Use the hosted app:** the fastest way to try Screenshot to Code with no local setup. Open the hosted app →\n\nRunning locally requires API keys and a backend/frontend setup. The app has a React/Vite frontend and a FastAPI backend.\n\n### API keys\n\nYou need **at least one** model provider key (OpenAI, Anthropic, or Gemini).\n**Gemini and Replicate are strongly recommended for the best quality of\nscreenshot-to-code accuracy** — Gemini powers asset extraction (reusing the\nreal logos/images from your screenshot) and Replicate powers image\ngeneration, background removal, and image editing. Adding all four keys gives\nthe best results and lets you compare multiple models per generation.\n\n| Key | Required? | What it unlocks |\n|-----|-----------|-----------------|\n| `OPENAI_API_KEY` | One of these three | GPT code-gen variants (GPT-5.5, GPT-5.4 Mini) |\n| `ANTHROPIC_API_KEY` | One of these three | Claude code-gen variants (Opus 5, Opus 4.8, Fable 5, Sonnet 4.6) |\n| `GEMINI_AP
FAQ (5)
TroubleshootingHow to fix Starlette WebSocket crash: RuntimeError "Unexpected ASGI message 'websocket.close'" after abnormal closure code 1006?
Upgrade to the latest release containing the fix for issue #599. As a temporary workaround, downgrade to the previous major version. To patch the code, import WebSocketDisconnect from Starlette and make WebSocketCommunicator.close() and throw_error() handle the already-closed state, for example:
from starlette.websockets import WebSocketDisconnect
try:
await ws_comm.close()
except (
ConnectionClosedOK,
ConnectionClosedError,
WebSocketDisconnect,
RuntimeError,
):
pass
Apply the same exception handling in throw_error(). This prevents the finally block from sending another websocket.close ASGI message after uvicorn has already closed the connection. The crash was observed with Python 3.11, uvicorn 0.25.0, Starlette 0.41.3, and websockets 14.1.
TroubleshootingHow do I fix AsyncAnthropic connection pool exhaustion or connection leaks when streaming throws an exception?
Wrap the client lifecycle in try/finally so close() runs even if streaming or session initialization fails:
client = AsyncAnthropic(api_key=api_key)
try:
# streaming / session logic
...
finally:
await client.close()
Alternatively, use the client as an async context manager:
async with AsyncAnthropic(api_key=api_key) as client:
# streaming logic
...
Apply the same pattern to AsyncOpenAI and to both stream_claude_response and stream_claude_response_native. Without cleanup, exceptions leave HTTP connections open, which can exhaust the connection pool in long-running services.
TroubleshootingHow do I fix screenshot-to-code code generation failing with an OpenAI model not found, invalid API key, or rate limit error?
Check the backend logs first to identify the exact OpenAI error:
docker compose logs backend --tail=100
# or
docker logs <container_id>
Common fixes:
1. **Invalid or missing API key:** Ensure OPENAI_API_KEY is set correctly in your environment or .env file, then restart the container.
2. **Model not found or unavailable:** If generate_code.py pins a dated model such as gpt-4o-2024-11-20, change it to a model available on your OpenAI account, such as gpt-4o or gpt-4.
3. **Rate limit or account restriction:** Check your OpenAI billing/usage dashboard and reduce request frequency or wait for the rate limit to reset.
4. **Restart after configuration changes:** Run docker compose down && docker compose up --build.
For a permanent fix, add explicit OpenAI API error handling in generate_code.py so authentication, rate-limit, and model-not-found errors are surfaced instead of failing silently.
TroubleshootingWhy does Poetry fail installing typing-extensions 4.12.2 with "All attempts to connect to pypi.org failed"?
This is a network/DNS/proxy issue, not a problem with the typing-extensions package. In PowerShell, first test connectivity:
Resolve-DnsName pypi.org
Invoke-WebRequest https://pypi.org/simple/ -UseBasicParsing
poetry install -v
If you are behind a local proxy, set it before running Poetry (replace the port with your proxy port):
$env:HTTP_PROXY = "http://127.0.0.1:7890"
$env:HTTPS_PROXY = "http://127.0.0.1:7890"
poetry install
If PyPI is blocked on your network, use a mirror temporarily:
$env:POETRY_PYPI_MIRROR_URL = "https://pypi.tuna.tsinghua.edu.cn/simple/"
poetry install
Also try disabling or reconfiguring VPN/firewall software, then re-run poetry install.
TroubleshootingHow to fix DeepSeek API error "Failed to deserialize the JSON body into target type: messages[1] ... ChatCompletionRequestContent" when using the OpenAI SDK?
This 422 error means messages[1].content uses a format DeepSeek does not accept. Although DeepSeek is OpenAI-compatible, it may not accept OpenAI's multimodal content-part arrays. Send message content as a plain text string instead.
Inspect the failing message:
import json
print(json.dumps(params["messages"][1], ensure_ascii=False, indent=2))
Convert content arrays to text before calling the API:
def normalize_deepseek_messages(messages):
for message in messages:
content = message.get("content")
if isinstance(content, list):
message["content"] = "".join(
part.get("text", "")
for part in content
if isinstance(part, dict) and part.get("type") == "text"
)
elif content is None:
message["content"] = ""
return messages
params["messages"] = normalize_deepseek_messages(params["messages"])
stream = await client.chat.completions.create(**params)
Also remove unsupported parts such as image_url or input_audio; use DeepSeek's text/chat format only. If using an agent framework, disable vision/multimodal message formatting or force plain-text OpenAI-compatible messages.