Join the RoClicks Discord
RoClicks

The UI import prompt: HTML to a Roblox ScreenGui

The second half of the HTML UI workflow: turn the finished HTML page into a real ScreenGui with uploaded art, live text and tweens.

Copy the prompt with the button on its box, then paste it into your AI.

The second half of making UI with AI. Once the HTML UI prompt has produced a matching HTML page, this prompt has an AI assistant connected to Roblox Studio rebuild it as a real ScreenGui: art exported and uploaded as images, all text as live TextLabels, all motion as tweens, and every number taken from the HTML.

One line differs from older copies: Roblox now scales UIStroke with UIScale (measured in late 2026), so the step that multiplied stroke thickness by the scale has been removed.

The prompt

# Task: Import an HTML/CSS game UI into Roblox Studio as a real ScreenGui

You get a finished HTML page (`index.html`, usually generated by a `build.py`) that recreates a game UI 1:1 at a fixed pixel size (e.g. 1083×691), with absolute px positioning. Rebuild it in Roblox Studio as a real ScreenGui that looks identical: art as uploaded images, all text as live TextLabels, all motion as tweens. The HTML page is the source of truth for every position, size, colour and font size. Don't re-eyeball anything.

## Rules
1. **Art becomes images; text and icons stay native.** Panels, plates, gradients, checker patterns, rims and ornaments are rendered out of the HTML page as PNGs and uploaded. Text becomes TextLabels. Icons are uploaded from their original PNG files, not re-rendered.
2. **Never bake text into images.** Text must stay editable and localizable.
3. **All numbers come from the HTML.** Read positions, sizes, font sizes, stroke widths and colours from the build data or the DOM (`getBoundingClientRect`, computed style). Don't guess.
4. **Only use fonts Roblox has.** Montserrat and Fredoka One are built in (`rbxasset://fonts/families/Montserrat.json`, `.../FredokaOne.json`). If the HTML uses a font Roblox doesn't have, say so and pick the closest Roblox font before building.

## Step 1: Export the art from the page
- Add export modes to the page:
  - `?export=bg` shows only the art (panels, frame, plates); text, icons and the stand-in background world are hidden; the background is transparent.
  - `?export=parts` shows each plate, button and card by itself, so each can be cropped cleanly.
- Render with headless Chrome at **2× device scale** with a transparent background:
  `chrome --headless=new --window-size=W,H --force-device-scale-factor=2 --default-background-color=00000000 --run-all-compositor-stages-before-draw --virtual-time-budget=10000 --screenshot=out.png "http://localhost:PORT/?export=bg"`
- Crop each piece with PIL using its HTML box × 2. Check that the crops' transparent edges are clean.
- **Glass (see-through) panels:** check the PNG keeps its alpha (e.g. black at 80%), or rebuild the panel as a Frame with `BackgroundColor3` + `BackgroundTransparency` (0.2 for 80% black) and a UIStroke outline. Glass must stay see-through in game.
- Headless Chrome sometimes drops `<img>` elements from screenshots. Check every crop for missing pieces before uploading.

## Step 2: Upload the images
- Upload every PNG (art crops + original icon PNGs) as a Decal/Image asset and record each returned `rbxassetid://` in an ASSET table.
- If the upload path needs a URL, serve the files from a local HTTP server.

## Step 3: Write the Luau builder
Write one builder script that creates the ScreenGui from the HTML's numbers.
- **Build at K = 2:** multiply every px value by 2 so fractional sizes survive Roblox's integer TextSize. A UIScale (Step 4) shrinks everything to fit the screen.
- **Positions:** `UDim2.fromOffset(x*K, y*K)` relative to the panel's top-left, with the same parent/child nesting as the HTML.
- **Images:** ImageLabel/ImageButton with `BackgroundTransparency = 1`, `ScaleType = Enum.ScaleType.Fit`, and the right ZIndex order (world → frame → panels → plates → icons → text).
- **Text:** TextLabel with `FontFace = Font.new(family, weight)` (CSS 800 = `Enum.FontWeight.ExtraBold`, 900 = `Heavy`, 700 = `Bold`).
  - **TextSize = CSS font-size × 1.215 × K.** Roblox's TextSize is the full line height; CSS font-size is the em. For Montserrat and Fredoka One the ratio is about 1.215.
  - Grow the label box around its centre to fit the taller line, and keep the HTML's alignment and position.
  - **Outlines:** CSS `-webkit-text-stroke: W` shows W/2 outside the glyph, so use `UIStroke.Thickness = W/2 × K` with `LineJoinMode = Round`.
  - **Drop shadows** (`text-shadow: dx dy 0 col`): add a second TextLabel with the same stroke, in the shadow colour, offset by dx,dy × K, with ZIndex one lower.
  - **Gradient fills:** add a UIGradient (Rotation 90) with the same top and bottom colours.
  - CSS `scaleX` squashes: approximate by tweaking TextSize or the label width, then re-check the width in Step 5.
- **Buttons** are ImageButtons (or TextButtons) with `AutoButtonColor = false`, anchored so they can scale around their centre.
- **Get the Luau into Studio safely:** if your tool mangles newlines when setting script source, wrap the builder as a ModuleScript inside a `.rbxmx` file, import it, `require` it once, then delete it.

## Step 4: Controller LocalScript
- A **UIScale** fits the panel to the viewport. Recompute on `ViewportSize` change: scale = min(viewport width / (W×K), viewport height / (H×K)) × a margin.
- **UIStroke scales with UIScale** (measured in late 2026; older advice said it didn't). Set each stroke's thickness once, in design units, and never multiply it by the scale, or outlines come out far too thick.
- **Open/close API:** `gui:SetAttribute("Open", true/false)`. Open = UIScale pop from 0 with `TweenInfo.new(0.42, Enum.EasingStyle.Back, Enum.EasingDirection.Out)`; close = `0.22, Back, In`.
- **Buttons:** give each one its own UIScale.
  - Hover: grow to ~1.06 (Quad Out, 0.16s) and play a hover sound.
  - Press: sink to ~0.94 (0.07s).
  - Release: spring back with Back Out (0.35s) and play a click sound.
  - The close "X" sets `Open` to false.
- **Idle motion:** gentle bob or rotate on icons so the UI feels alive; keep it subtle.

## Step 5: Verify in Studio (numbers, not vibes)
Studio screenshots often can't capture GUIs, so verify by measurement:
- **Images:** every ImageLabel reports `IsLoaded == true`.
- **Text widths:** compare each TextLabel's `TextBounds.X` with the browser's rendered text width × K. Every label should be within about 1–2%; adjust TextSize where it isn't.
- **Clicks:** playtest with real clicks. Simulated mouse input can land about 58px off because of the top-bar GUI inset, unless the ScreenGui uses `IgnoreGuiInset = true`. Correct for that.
- **Errors:** check the output log for errors, then stop the playtest.
- **Report:** say what matches (with numbers), what differs and why (e.g. a font substitution), and anything the user should check by eye.