If you build a free browser tool with AI in 2026, you immediately hit three walls:
- Inference bills eat you alive the moment traffic spikes.
- Small/free models hallucinate broken layouts when asked to edit an existing component.
-
Hosting user-generated pages turns into an XSS nightmare the second someone prompts
<script>fetch(...)</script>.
Over the past week I've been building openconstructor — an MIT-licensed, zero-signup visual UI builder that runs directly in the browser with 320+ plain HTML/CSS components and exports to HTML, React, Vue, Svelte, LÖVE 2D, and Roblox Luau / .rbxmx.
If you dig open-source browser tools (or want to poke through the source), a ⭐ on the repo genuinely helps a ton right now while the project is brand new:
Kuloka
/
openconstructor
Open-source AI interface builder: drag components onto a canvas, ask an AI to build pages, export HTML.
openconstructor
Build interfaces with words and blocks — an open-source, in-browser AI interface builder.
openconstructor.pw — try it live
Roblox UI builder · AI UI generator · 320+ components · HTML to React / Vue / Svelte · LÖVE 2D UI · Gallery
Drop ready-made components onto one shared canvas, move them around freely, and ask an AI model to build or change anything on the page. When you're done, export a single self-contained HTML file.
Features
- Component catalog — 320+ components in 13 sections (AI & Chat, Dashboard, Commerce, Auth, Landing…), all plain HTML + CSS. Animated ones play on hover.
- Free-form canvas — drag, pan, zoom, desktop / laptop / tablet / phone sizes.
- Background editor — solid, linear, radial and mesh gradients, dot / grid / line / noise patterns, or raw CSS.
- ⚗ Fusion — right-click an element → fuse with… → click another: the AI…
Here are the 4 most useful engineering tricks that came out of building it on a strict $0/month budget.
1. Surviving Traffic on $0: The 413 + Quota Waterfall (~14,000 Free Requests/Day)
Most indie AI wrappers hardcode a single provider and put up a paywall after 3 prompts. Instead, you can chain free-tier and edge inference endpoints into a stateless fallback waterfall where each failure mode routes to a different architecture:
-
Primary (high taste, huge weights):
GPT-OSS 120B/Llama 3.3 70Bvia Cloudflare Workers AI & Groq for fast layout synthesis. -
High-volume safety net (~14k req/day):
Gemma 3 27Bon Gemini's free quota. One quirk with Gemma endpoints: system instructions aren't always accepted as a separatesystemrole, and smaller context gateways throwHTTP 413on big component trees.
Folding the system rules into the first user turn and cascading on 413 / 429 / 5xx made anonymous free generation reliable without touching a credit card:
const PROVIDERS = [
{ name: "workers-gpt-oss", maxTokens: 8000, supportsSystem: true },
{ name: "gemini-gemma-27b", maxTokens: 8192, supportsSystem: false },
{ name: "groq-llama-70b", maxTokens: 6000, supportsSystem: true },
];
async function generateWithFallback(systemPrompt, userPrompt) {
for (const p of PROVIDERS) {
const messages = p.supportsSystem
? [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt },
]
: [
{ role: "user", content: `${systemPrompt}\n\n---\nTask:\n${userPrompt}` },
];
const res = await callProvider(p, messages);
if (res.ok) return res.text();
if (![413, 429, 500, 502, 503].includes(res.status)) break;
}
throw new Error("All free inference routes exhausted");
}
And if a user brings their own API key (OpenRouter, Gemini, Groq, Anthropic, OpenAI, DeepSeek, Cerebras), the key stays in localStorage and talks directly from their browser to the provider — zero backend middleman.
2. Reading getComputedStyle to Compile HTML/CSS Into Game Engines (Lua / Luau)
Web developers aren't the only people who hate hand-coding UI coordinates. Building a shop menu or HUD in Roblox Studio (UDim2, UICorner, UIGradient, UIListLayout) or LÖVE 2D is notoriously tedious compared to Flexbox and CSS Grid.
Instead of writing a fragile CSS parser from scratch, we let the browser's own layout engine do the hard math first:
- Mount the component inside a hidden/sandboxed container at the target viewport (
1920×1080, phone safe-zone, etc.). - Walk the live DOM tree with
getBoundingClientRect()andgetComputedStyle(el). - Emit native engine primitives (
Frame,TextButton,UICorner,UIStrokein a.rbxmxXML model or LuauLocalScript, or draw calls in LÖVE 2D Lua).
function domNodeToEngineRect(el, rootRect) {
const rect = el.getBoundingClientRect();
const cs = getComputedStyle(el);
return {
xScale: (rect.left - rootRect.left) / rootRect.width,
yScale: (rect.top - rootRect.top) / rootRect.height,
wScale: rect.width / rootRect.width,
hScale: rect.height / rootRect.height,
bgColor: cs.backgroundColor,
radiusPx: parseFloat(cs.borderTopLeftRadius) || 0,
text: el.childElementCount === 0 ? el.textContent.trim() : null,
};
}
Because Flexbox, Grid, clamp(), and padding are already resolved by the browser before export, complex responsive cards translate cleanly into engines that have never heard of CSS — and Live Sync mode preserves existing click-handler scripts attached to named buttons.
3. "Component Fusion" & JSON-to-.glb 3D Scenes (Keeping Taste Out of the Prompt)
When you ask a small or mid-sized model to "redesign this pricing table" from scratch, you get style drift: fonts change, spacing collapses, and half the page turns into generic purple gradients.
Three constraints fixed this almost completely:
- Edit patches instead of full rewrites: When modifying an element on the canvas, the prompt locks the existing visual tokens (palette, border radius, typography) and asks only for the requested delta.
-
⚗ Component Fusion (
Structure A × Style B): Right-click any component on the canvas → Fuse with… → click a second component. Passing the DOM skeleton of A alongside the computed CSS tokens of B gives the model concrete anchors instead of an open-ended prompt. -
Data-first 3D (
.glbexport): Asking an LLM to write raw Three.js mesh code fails ~40% of the time. Asking it to emit a declarative JSON scene graph (primitives, PBR materials, lights, keyframe loops) succeeds almost every time — and a lightweight Three.js builder renders it live on the canvas and exports a standard.glbfile for Blender, Godot, Unity, or Roblox.
4. Zero-JS Sandboxed Publishing (script-src 'none')
Allowing anonymous users to generate and publish live pages (openconstructor.pw/p/...) usually requires heavy sanitization or isolated subdomains.
We went a simpler route: published pages execute zero JavaScript.
- Every preview tile in the editor runs inside
<iframe sandbox>with scripts and network requests disabled. - Published pages ship with strict CSP
script-src 'none'. - Interactive states (hover reveals, accordions via
<details>, multi-screen#anchorprototypes, marquee loops,@keyframesentrances) are 100% pure HTML + CSS.
No XSS vectors, no crypto-miners, instant loads, and zero runtime bundle overhead.
Try It / Break It
You can test the canvas right in the browser with no account and no API key at openconstructor.pw, or run it locally in 3 commands:
git clone https://github.com/Kuloka/openconstructor.git
cd openconstructor
npm install && npm run dev
Question for folks building with LLMs on the frontend: When you generate UIs, do you let the model stream raw HTML/JSX directly, or do you lock it behind a typed JSON schema / component registry? Curious where you drew the line between flexibility and reliability.

Top comments (0)