Generator HTML code that returns a live URL, not a paste buffer
Every other tool in this category gives you a snippet to copy. mk0r gives you a preview URL where a real Chromium has already clicked through your page. You iterate by describing the next change, not by regenerating the whole file.
What most “HTML code generator” tools hand you
Open the pages that currently rank for this topic and the output is a blob of markup. You fill a form, you click generate, you copy-paste the result into your own project, and you hope it renders the same way in your environment as it did in the tool's preview. That hope is mostly how the whole category works. There is no verification step between the preview pane and your clipboard.
That is not a problem if the thing you are generating is a trivial card or a plain list. It becomes a problem the moment the HTML has behavior. A sortable table needs its click handler to fire. A form needs its submit to actually POST. A modal needs its escape key binding. The generator's preview cannot promise any of that, because all it rendered is a still image of the markup in isolation.
mk0r is built around a different assumption: the output of an HTML code generator should be a working page at a URL, not a string you paste and trust. The rest of this guide is the mechanical detail of how that assumption plays out in the repo.
Snippet tool vs. a URL tool
| Feature | Typical HTML code generator | mk0r |
|---|---|---|
| Output format | A static HTML blob, copy-paste into your own site | A live preview URL serving a running app |
| Verification before you see it | None. You trust the preview and hope it renders elsewhere | A real Chromium at /usr/bin/chromium clicks through it |
| Iteration method | Re-enter the form, regenerate, compare two snippets by hand | Describe the change in plain English, HMR updates the same URL |
| Javascript and forms | Often omitted or inert in generator output | Full Vite plus React runtime, JS executes, forms have real state |
| Signup to try | Usually none, but you pay when you want persistence | None for the build flow, anonymous Firebase session on mount |
| Path to a public URL | Copy HTML, find a host, upload, configure DNS | Hit publish, the same URL is mapped to your domain |
The anchor fact: the system prompt is one paragraph
If you open src/core/e2b.ts and jump to line 148, the entire system prompt that controls the HTML-generating agent is right there. One paragraph. It tells the agent it is inside an E2B sandbox with Vite plus React plus TypeScript plus Tailwind CSS v4 at /app, that the dev server is on port 5173 with HMR, and that Playwright MCP is available for browser testing. Everything else comes from CLAUDE.md files the agent reads at runtime.
There is no separate HTML template, no CSS starter file, no placeholder markup the agent copies from. The only context the agent starts with is that short paragraph and whatever it reads off the repo it is working in. When you ask for HTML code, everything you get is produced inside that blank slate, which is why the output is a real project tree rather than a reformat of a stored template.
The verification loop that the category skips
The function that wires Playwright MCP into the sandbox lives a few lines below the system prompt, at src/core/e2b.ts line 153. It registers a server named “playwright” that runs npx @playwright/mcp --cdp-endpoint http://127.0.0.1:9222 with PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium. That means the agent drives a real Chromium running inside the sandbox over the Chrome DevTools Protocol. It is not a headless mock. It renders fonts, runs JavaScript, fires real DOM events.
When you ask for a pricing table, the agent writes the component, Vite hot-reloads the dev server, and Playwright MCP opens the URL, takes a DOM snapshot, hovers the card, reads the console. Only if those return cleanly does the chat reply come back with a preview URL. If Playwright catches an error, the agent retries before you hear about it.
The path your prompt takes
From the moment you hit enter on the landing page, six things happen before you see a preview. None of them are a form click.
Prompt to preview URL
Prompt hits /api/chat
Your description lands on the chat route. The route pulls an active sandbox from the VM pool keyed by sessionKey.
ACP /session/prompt inside the sandbox
The chat route forwards the prompt into the ACP bridge running at localhost:51050 inside the E2B sandbox.
Agent edits .tsx files in /app/src
The agent uses Edit and Write tools to build a React component tree. Tailwind v4 compiles on demand.
Vite streams HMR updates to :5173
Every saved file triggers a hot reload on the dev server. The preview URL stays the same, the DOM updates.
Playwright MCP drives Chromium at 9222
The agent invokes browser_navigate, browser_snapshot, browser_click to verify the UI actually behaves.
Preview URL returned to you
Only after Playwright confirms the page renders cleanly does the chat reply with a live URL you can open.
How a description becomes a running HTML app
But I really do want one standalone HTML file
That path still works. Ask for it in the first message. “Give me a single standalone HTML file with inline CSS and JS for a 25 minute pomodoro timer.” The agent respects the constraint, writes one index.html with a style block and a script block, and the preview URL serves that file directly. The verification loop still fires, so the file you save to disk actually works when you double-click it in Finder.
The difference from a classic generator is still the verification: the agent opens the file in the real Chromium at /usr/bin/chromium, fires the Start button, and watches the clock tick down before the chat replies. If the script had a typo, the console.error would get caught and the agent would fix it before the turn ended.
Iteration is a conversation, not a regeneration
The worst part of a classic HTML code generator is that every change means filling the form again and getting a new blob. mk0r keeps the same source tree across turns. You describe a change, the agent edits the files, HMR reloads, the URL does not move. The state in the page survives the edit when it can.
Changing the table header on turn two
You open the form back up, re-enter the row data, tick a new box for 'sticky header,' click generate, diff two blobs of HTML, paste the new one into your project, hope nothing else changed.
- Re-enter all inputs
- Pray the unrelated parts are byte-identical
- Copy-paste over your last version
- No history, no undo
Numbers you can verify in the repo
All four are checkable against source. The first three are in src/core/e2b.ts. The last one is how long the paragraph in DEFAULT_APP_BUILDER_SYSTEM_PROMPT is: one sentence cluster, no bullets, no sub-sections.
When to pick a snippet tool instead of mk0r
You need one static row
A single <tr> of boilerplate for a doc example. No behavior, no styling that matters. A paste-and-go snippet generator is faster for exactly this.
Your target is plain email HTML
Mail clients are a constraint-heavy rendering target with no JavaScript and strict table layouts. The browser verification mk0r runs does not translate to Gmail or Outlook.
You are auditing a legacy snippet
If the goal is just to validate what someone else pasted, a formatter or linter is the right tool. mk0r is for producing new HTML, not policing old HTML.
You want a live URL, not markup
A one-page dashboard, a portfolio tile, a working form, a demo app, a widget you can paste into an iframe. This is what mk0r is actually built for.
You want to iterate by talking
Pick mk0r. Classic generators re-run the form on every change; mk0r edits the same file in place and keeps the preview URL stable.
Watch the verification loop run on your own prompt
Bring an HTML page you want to build. We will watch the agent write it, Playwright click it, and the URL go live on the call.
Frequently asked questions
Frequently asked questions
Why does mk0r not hand me a blob of HTML to copy and paste?
Because a blob of HTML lies to you. It looks correct in the generator's preview and breaks in your actual browser because a class was dropped, a quote was mismatched, a form field had no name attribute, or a script errored silently. mk0r skips that whole failure mode by rendering the HTML in a real Chromium inside the sandbox and having Playwright MCP click it before you ever see the preview. What you get back is a URL that loads a verified working page. If you want the raw HTML at the end, you can still grab it, but the handoff is an app, not a snippet.
Where is the agent that writes the HTML actually configured?
One paragraph, at src/core/e2b.ts line 148, in a constant named DEFAULT_APP_BUILDER_SYSTEM_PROMPT. It tells the agent it is inside an E2B sandbox with a Vite plus React plus TypeScript plus Tailwind CSS v4 project at /app, the dev server is running on port 5173 with HMR, and Playwright MCP is available for browser testing. That is the entire system prompt. Every rule the agent follows for writing HTML is either in that paragraph or in the CLAUDE.md files the agent reads at runtime.
How does the Playwright MCP actually run inside the VM?
buildMcpServersConfig at src/core/e2b.ts line 153 registers a server named 'playwright' that runs 'npx @playwright/mcp --cdp-endpoint http://127.0.0.1:9222' with the environment variable PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH set to /usr/bin/chromium. That means the MCP bridge opens a real Chromium process at /usr/bin/chromium inside the sandbox and talks to it over the Chrome DevTools Protocol on port 9222. When the agent wants to verify a button works, it drives that browser, not a mock. You can check this yourself in src/core/e2b.ts, the file is committed to the repo.
What does the HTML the agent writes actually look like?
It is not hand-rolled markup. The agent writes .tsx components into /app/src/ inside the sandbox, and Vite compiles them into HTML plus JS at runtime. Tailwind CSS v4 produces the stylesheet. The preview URL you receive points at the Vite dev server on port 5173, which streams the rendered HTML. If your prompt asked for a pricing table, the output is a PricingTable component in TypeScript with semantic HTML, Tailwind classes, and working state. You can copy that source out of the sandbox if you want to paste it into your own project.
Can I still use mk0r if I only want a single standalone HTML file?
Yes. Describe it that way in the first prompt, for example, 'give me one standalone HTML file with inline CSS and JS for a 90 second pomodoro timer.' The agent respects that request inside the sandbox, writes a single-file HTML document with inline style and script tags, and the preview URL serves it as /index.html. You can then right-click view source, or ask the agent to print the full file contents back to you in the chat. The verification loop still runs, so the file you save actually works.
Do I need to sign up or create an account?
No. Visit mk0r.com, type a description, hit enter. The app signs you in anonymously through Firebase on mount and the session owns your prototype. Sign-in is only requested when you ask to publish to a custom domain or switch to a paid model variant. The path from cold landing page to first generated HTML is a single prompt, no email, no verification step, no form.
How do I iterate on the HTML without starting over?
You talk to the agent. The same preview URL stays live across turns. Say 'add a dark mode toggle in the top right,' 'make the numbers tabular,' 'replace the hero image with a gradient,' and the agent edits the existing files in place. HMR reloads the preview in under a second. If a change breaks the page, Playwright MCP catches it, the agent sees the error output, and tries again before reporting back. You are always editing one source tree, not regenerating a new snippet each turn.
Can the output include JavaScript, forms, or API calls?
Yes. The sandbox is a full Vite plus React plus TypeScript environment, so there is no ceiling on what the HTML can do. Forms work with real state, JavaScript has access to the full browser API, and the agent can wire a /api route inside the same project if you ask for a backend. The preview URL is a real HTTPS endpoint, so fetch calls that need a trusted origin work too.
What is the E2B template this runs on?
The production template is named 'mk0r-app-builder' with template ID 2yi5lxazr1abcs2ew6h8. It is defined in docker/e2b/e2b.toml and referenced from the CLAUDE.md file at the repo root. The template bakes in Vite, React, TypeScript, Tailwind CSS v4, Chromium at /usr/bin/chromium, and the startup script at /opt/startup.sh. When you hit the landing page, a prewarmed sandbox from that template is handed to your session so your first prompt does not wait on a cold boot.