Guide

Your responsive web app mobile prototype deserves a real phone viewport, not another Figma frame.

Most prototyping tools give you a screenshot and call it mobile. mk0r renders your app inside a live 390×844 iframe wired to Vite HMR, so every AI edit hot-swaps into the phone frame in under 800 ms, and your state survives.

m
mk0r
6 min
4.8from 10K+ creators
390×844 Pixel-exact frame
HMR-preserved state
Mobile-first Tailwind by default

The concept

The problem with every other mobile prototype

You describe a mobile app in a prototyping tool. The tool hands you back an image of a phone with your layout inside. You change a padding value, hit regenerate, and the tool rebuilds the image from scratch, state discarded, scroll position gone, modal closed, login screen back.

That loop is fine for landing pages. It is death for a responsive app prototype, because the bugs you are looking for only appear when you have three fields filled out, a date picker half open, and a keyboard pushed to a weird position on mobile Safari.

mk0r treats the preview as a real running browser, not a regenerated screenshot. The iframe is the app. It keeps its state. The AI edits files behind it. You keep testing.

The anchor fact: 390×844, 800 ms, zero reloads

Open src/components/phone-preview.tsx in the mk0r repo. Line 9 is where the entire mobile story starts.

src/components/phone-preview.tsx

DEVICE_SIZE.mobile is 390 by 844. That matches the Pixel 5 and iPhone 14 portrait viewport, which is the middle of every mobile device distribution that matters. The HMR_WAIT_MS constant is 800. If Vite posts hmr:after to the parent window inside that window, the preview never reloads. That is the line where prototype and product diverge.

0Preview width in px
0Preview height in px
0 msHMR wait window
0Accounts required

What actually happens when you ask for a change

Watch the log when the AI edits a file during a session. The preview and the dev server negotiate a reload that usually never happens.

mk0r-preview console

How the pieces fit together

Three signals feed one frame. Your prompt, the agent's file writes, and Vite's HMR bridge all end up inside the 390×844 iframe rendered in your browser.

inputs → mk0r → live preview

Your prompt
Agent file writes
Vite HMR bridge
mk0r preview
Mobile frame
Desktop frame
Share URL

Mobile-first is a rule, not a vibe

The agent's instructions are explicit. It writes base styles first and layers breakpoints on top, which is why the output works in the 390 frame before it works in desktop rather than the other way around.

agent Tailwind rules

  • Tailwind base styles define mobile layout first
  • sm: (640px) adds tablet adjustments
  • md: (768px) and lg: (1024px) expand to desktop
  • Spacing stays on a 4px multiple: p-2, p-4, p-8
  • No custom hex values, use Tailwind's palette

These rules live in docker/e2b/files/root/.claude/CLAUDE.md. Every agent session loads them into its system prompt, so the responsive contract is enforced at the model layer, not added as a polish step after generation.

Five steps, start to working prototype

1

Describe the prototype

Type a sentence. No wireframes, no component library selection, no template gallery. The first prompt is the whole onboarding.

2

VM spins up

An E2B sandbox boots with Vite, React, TypeScript, Tailwind v4, and Playwright pre-installed. Dev server runs on port 5173 with HMR on.

3

Agent writes mobile-first

The agent follows CLAUDE.md: base styles first, then sm:, md:, lg:. Every component it creates is responsive by construction, not retrofit.

4

Preview renders at 390×844

An iframe constrained to phone dimensions loads the dev server. You watch the AI stream code into a phone-sized surface in real time.

5

HMR keeps your state alive

Every follow-up prompt triggers file writes. The preview listens for Vite's hmr:after message for up to 800ms before falling back to a reload.

What you actually get

Real device viewport

The preview frame is locked at 390×844, the Pixel 5 / iPhone 14 portrait size. Not a Chrome DevTools simulation, a constrained iframe that actually renders at that width.

HMR-preserved state

Edits from the AI go through Vite's hot-reload first. Form inputs, open modals, and routes stay exactly where you left them.

Instant device flip

Toggle between the 390×844 frame and a full-width desktop view without rebuilding. Compare breakpoints side by side.

Playwright in the loop

The agent has a Playwright MCP server wired to Chrome DevTools Protocol on port 9222. It can actually navigate and snapshot the DOM before handing the preview back.

why this is different

A 0×0 iframe is not a screenshot.

Framer, Figma, and most AI prototype tools regenerate an image of a phone every time you change a prompt. mk0r renders a real browser at phone dimensions and keeps it open. You scroll, type, and test like a user. The AI edits the source in the background. State survives. That is the entire gap between prototype theater and a prototype you can actually show to someone.

Why no QR code, no tunnel, no Expo

Classic mobile prototyping means native: React Native plus Expo, plus an Expo Go app on the phone, plus a QR code, plus ngrok when the network refuses, plus a rebuild every time a dependency shifts. The first three hours are environment setup.

For the first ten iterations of a product idea, you do not need native. You need something that looks and behaves like a mobile app on a mobile viewport, that you can point at from your phone's browser, and that you can change by talking to it. A responsive web app inside a 390-wide frame clears that bar, and mk0r ships the full chain: prompt, VM, dev server, HMR bridge, iframe, share URL.

Port to React Native later if you need deep device APIs. Most prototypes never need to.

Frequently asked questions

Why 390×844 and not some other phone size?

390×844 is the Pixel 5 / iPhone 14 portrait viewport, the de-facto median of modern smartphones. If a layout works there, it works on both iOS Safari and Android Chrome without another pass. The size is hard-coded in src/components/phone-preview.tsx so every mk0r session renders the same frame.

What happens when I ask mk0r to change something — does the preview reload?

Usually no. The preview waits up to 800ms for Vite's HMR bridge to post an 'hmr:after' message. If HMR repaints in time, the iframe never reloads, so any form input, modal state, or route you were testing stays put. Only if HMR misses the window does the preview cache-bust and hard reload.

Do I need an Expo QR code or ngrok tunnel to see it on my phone?

No. The preview is a plain HTTPS URL on the mk0r domain, served from a cloud VM. Open it on your phone's browser to test on real hardware, or keep working in the 390×844 desktop frame. No bundler, no native build step, no tunnel.

Is this a real responsive app or just a desktop site squeezed into a phone frame?

It's a real responsive React app. The agent's system prompt instructs it to build mobile-first in Tailwind: base styles first, then sm:, md:, lg: breakpoints. That rule lives in docker/e2b/files/root/.claude/CLAUDE.md and applies to every file the agent writes.

Can I flip between mobile and desktop to check both layouts?

Yes. The preview has a device switcher that toggles between the 390×844 mobile frame and an unconstrained desktop viewport. Switching is instant because it just changes the iframe's CSS container, not the underlying dev server.

Do I need an account to build a responsive prototype here?

No account, no email, no payment. Open mk0r.com, describe the app, and the preview appears. Zero-friction is the reason this is a viable prototyping tool instead of another sandbox you have to configure.

What if I want the generated code?

Every file the agent writes lives in a real Vite + React + TypeScript + Tailwind project inside the VM. You can download the source, continue developing locally, and deploy anywhere Node runs. There's no proprietary format.

Describe your responsive app. Watch it load into a 390×844 iframe in under a minute. No account, no setup.