A preview lays the scene out against the stage it is given. Whatever the mount measures is the
Roblox viewport. It is mirrored onto Workspace.CurrentCamera.ViewportSize and fed to the layout
engine. So a phone gets a phone-sized viewport and the scene reflows exactly as the engine reflows
there.
Nothing here needs configuration. The generated pages size with dvh, so a full-height stage does not hang under a mobile browser’s toolbars.
The stage is the viewport
Text re-wraps, a UIListLayout with Wraps re-flows, AutomaticSize re-measures, and a scene
branching on ViewportSize takes its narrow path. A layout of offsets wider than the screen runs
off the edge and is clipped. That is what it does on a real device, and usually what you opened the
preview on a phone to find out.
Roblox does not, so neither does loom. A scene that should adapt to a small screen adapts in the scene — a UIScale, a breakpoint on ViewportSize, an AbsoluteSize binding. The preview’s job is to show what the engine would show, including a layout hanging off the edge.
Why zooming is not the default
Zooming a narrow device to a fixed 960-wide logical viewport reads as the same layout drawn smaller.
It is not — it is a different layout, the one the engine gives at 960, shrunk to fit. TextWrapped
text keeps the line breaks it had at 960, a UIListLayout with Wraps keeps its row count, and
AutomaticSize settles at the wide measurement. Everything looks fine, just small, and is wrong in
exactly the places a narrow viewport is the thing being checked.
?base= keeps a wide composition
The zoom is still there, because a page embedding a preview at a fixed narrow width sometimes wants the composition rather than the reflow. It just has to be asked for:
| Value | Effect |
|---|---|
| absent | The default. The scene lays out against the real viewport at every width, on every device. |
?base=<px> | Keep this logical width. Below it the stage scales down to fit; at or above it nothing is applied. |
?base (bare) | The same, at 960. A value that cannot be read means this too — the param was typed to turn the zoom on. |
?base=none (or off, or 0) | The default, spelled out, for a host page templating the param. |
/loom-preview/index.html?target=src%2Fscenes%2FCounter.loom.tsx&chrome=none&base=1280960 is the bare-?base fallback rather than a constant with one right answer. The wider the base,
the more desktop layout survives and the smaller everything is drawn. It is wide enough for a
two-column layout, and small enough that a phone renders at roughly 40%. A 1280 base gives ~30%,
where body text stops being readable.
With a base in effect the world reads the mount’s untransformed layout size, so you are previewing the wide layout drawn smaller. The debug panel shows both boxes and the factor between them.
?base= sits alongside the rest of the URL
contract.
Pointer coordinates follow the scale
Whenever ?base= is scaling the stage, an on-screen pixel is not a layout pixel. The renderer
converts back by reading the mount’s rendered-to-layout ratio, so hit testing lands where the scene
looks like it is:
MouseEnter/Activated/InputChangedpositionsGetMouseLocation- Wheel deltas
Unscaled, which is the default, the ratio is 1 and the conversion is a no-op.
ScrollingFrames scroll from a touch drag
There is no wheel on a phone, so a drag inside a ScrollingFrame moves CanvasPosition with the
same clamping the wheel path uses. Past a small slop threshold it stops counting as a tap, so
dragging past a button does not activate it.
Two deliberate limits:
- Only
ScrollingFrames opt out of native touch panning. A preview embedded in a docs page never traps the reader’s scroll. The page keeps scrolling normally everywhere else. - Taps do not wait for the double-tap-zoom timeout. A tap registers immediately rather than after the browser’s ~300ms delay.
The gallery chrome stacks
In full-chrome gallery mode the 248px sidebar becomes a top bar with a targets button. Picking a
target closes it again. ?chrome=none is unaffected — there is no chrome to stack, so embedded
previews get the touch handling and nothing else.