Loom is a development tool. It renders your UI tree in a browser so you can iterate without a Studio round-trip. Nothing it produces ships to Roblox, and its fidelity is deliberately partial.
A preview is a high-fidelity approximation of Roblox layout and painting, not a reimplementation of the engine. Use it to iterate on structure, sizing, state and interaction. Verify final visuals in Studio before shipping.
Implemented
| Area | Status |
|---|---|
| Scene IR + WASM layout + DOM renderer | Complete — the vertical slice all frontends share. |
Roblox datatypes, Instance model, signals, services, scheduler | Complete enough to run real component libraries. Attributes since 0.11.0; Clone, Random, DateTime, typed property read defaults and the roblox-ts Promise since 0.12.0. |
| Luau standard library | Complete as of 0.8.0 — table, string, math, os, bit32, utf8, debug, buffer and the free functions, installed before your entry. coroutine became real in 0.12.0. Metatables are the deliberate omission. |
@rbxts/react adapter | Complete: a custom react-reconciler host config driving live loom instances. |
Layout: list, grid, table, pages, padding, constraints, AutomaticSize, text measurement | Complete — every UIGridStyleLayout the engine has, as of 0.7.0. |
Visuals: text, UICorner, UIStroke, UIShadow, gradients, clipping, transparency, rotation, scrolling frames | Complete. |
Images: every ScaleType, 9-slice, tiling, sprite windows, tints, rbxassetid:// | Complete as of 0.7.0, including asset ids in a static build — ids composed at runtime since 0.9.5. |
Text: FontFace, RichText markup, wrapping, LineHeight and measurement | Complete, and calibrated against the engine as of 0.9.5 — TextSize as a face height, half-pixel advances, kerning, one wrap authority for measurement and paint. |
Fonts: the engine’s typefaces, registerFont | The preview loads 28 of Roblox’s families as of 0.9.0, and names the rest; unbacked families warn, and since 0.9.6 a registered face that never arrived warns too. |
Scrolling: ScrollingFrame, canvas growth, scroll bars | Complete as of 0.9.3 — before it, a canvas never outgrew its window and no bar was drawn. |
| Debug panel | As of 0.10.0 — timings, viewport, live tree, loaded typefaces, and a stage inspector. Off unless asked for. |
Flex: UIListLayout flex and Wraps, UIFlexItem | Complete. |
UIScale, TextScaled, BorderSizePixel / BorderColor3 / BorderMode | Honored as of 0.12.0. All four were accepted and ignored before it. |
TweenService, TextService, Debris, bindings, @rbxts/ripple | Complete. |
Pointer and keyboard input, focus, UserInputService | Complete, with Roblox input-sinking semantics. 0.12.0 began dispatching the mouse events that were connectable but never fired, and added keyboard input and the KeyCode digits, modifiers and gamepad buttons. |
Dev loop: Vite plugin, HMR, loom preview, loom build | Complete. |
vide adapter | Shipped, on the same Scene IR. |
Those rows are area-level. For the per-property answer, see
Supported instances and properties. Is
AutomaticCanvasSize honoured, does UIGradient.Rotation work, what happens to
ScrollBarThickness.
The practical proof point is a full headless component library. Dialogs, popovers, comboboxes,
selects, tabs, sliders, focus scopes, portals and layers all render and behave correctly under
loom preview. That is across more than fifty scenes, without a single source change on the
library’s side.
Not implemented
Gotham and BuilderSans, out of the box. Roblox’s default family, and the Builder faces
behind it today, are proprietary and cannot be redistributed. The preview loads only the openly
licensed families. Both are still named, so they resolve to a stack and warn rather than drifting
silently. A project that has the files registers them itself. See
Fonts and text metrics.
Metatables. setmetatable / getmetatable / newproxy are not installed. Loom runs the
author’s TypeScript, whose classes are JavaScript classes. There is no faithful way to give a plain
object a metatable’s __index behaviour without proxying every table in the program.
A roblox-ts compiler transform. The preview path deliberately skips rbxtsc and lets esbuild
transpile. A standalone transform, which would let loom consume compiled output rather than source,
is still open.
A Luau adapter. The IR was designed so a Luau frontend can be added. It has not been.
Things that are true, not bugs
- A list can flow alphabetically.
SortOrderdefaults toEnum.SortOrder.Nameon every layout, as it does in Studio. Children with distinctNames sort by name rather than by source order. SetSortOrder={Enum.SortOrder.LayoutOrder}for the other behaviour. - No typechecking. esbuild strips types, so a preview renders whether or not your project typechecks. See TypeScript setup.
printtypes oddly. The globals declaration omitsprintbecauselib.domalready declares one and redeclaring is a compile error. The runtime still overwrites the value.- Unimplemented services fail at the call, not the import. The ten services loom cannot honestly stand in for resolve to a warned stub. Its method calls throw by name.
MarketplaceService,TeleportService,DataStoreServiceand friends. A UI that merely imports one still mounts. - A Luau-only package needs a shim. Most roblox-ts packages redirect to their own TypeScript source automatically. A declaration-only one has none, and fails by name until you point
shimsat a browser module.@rbxts/react,@rbxts/react-roblox,@rbxts/services,@rbxts/vide,@rbxts/ui-labs,@rbxts/rippleand@rbxts/react-rippleare handled out of the box. rbxassetid://is resolved for you, twice over. Roblox’s thumbnail API sends no CORS headers. A dev-server route resolves the id underloom preview, the embedded server and Next dev. A static build has no server to ask, so the ids are resolved at build time. They are read out of the bundle, and found by mounting each gallery target and reading the tree, which catches an id composed at runtime. An image the first render never reaches still needs a real URL, or your ownsetImageResolver.- The React version is pinned in effect. The adapter’s reconciler targets React 18. The plugin aliases bare
reactto one absolute path, to guarantee a single instance. - Text metrics are the browser’s, corrected toward the engine’s. Loom measures with canvas
measureText(), so shaping and hinting are not Roblox’s. Three things narrow the gap. The preview loads the right typeface itself.TextSizeis divided by the face box rather than spent as afont-size. And advances are quantized to the half pixel and kerned the way the engine spends them. AgainstGetTextBoundsAsyncat 50 widths, the wrapped line count matches 49 times. What is left is a measurement about a percent roomy, so text wraps a hair early rather than overflowing. The detail.
Versioning
All @loom-dev/* packages release in lockstep — the changesets configuration marks them fixed, so
a bump to one bumps them all. Mismatched versions across the scope are never a supported combination.
The project is pre-1.0: expect the plugin’s config contributions and the gallery URL contract to be
the stable-ish parts, and internal package boundaries to move.