Loomgetting started

Scope and status

What loom renders faithfully, what it accepts and ignores, and what it will never be.

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.

Implemented

AreaStatus
Scene IR + WASM layout + DOM rendererComplete — the vertical slice all frontends share.
Roblox datatypes, Instance model, signals, services, schedulerComplete 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 libraryComplete 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 adapterComplete: a custom react-reconciler host config driving live loom instances.
Layout: list, grid, table, pages, padding, constraints, AutomaticSize, text measurementComplete — every UIGridStyleLayout the engine has, as of 0.7.0.
Visuals: text, UICorner, UIStroke, UIShadow, gradients, clipping, transparency, rotation, scrolling framesComplete.
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 measurementComplete, 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, registerFontThe 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 barsComplete as of 0.9.3 — before it, a canvas never outgrew its window and no bar was drawn.
Debug panelAs of 0.10.0 — timings, viewport, live tree, loaded typefaces, and a stage inspector. Off unless asked for.
Flex: UIListLayout flex and Wraps, UIFlexItemComplete.
UIScale, TextScaled, BorderSizePixel / BorderColor3 / BorderModeHonored as of 0.12.0. All four were accepted and ignored before it.
TweenService, TextService, Debris, bindings, @rbxts/rippleComplete.
Pointer and keyboard input, focus, UserInputServiceComplete, 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 buildComplete.
vide adapterShipped, 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. SortOrder defaults to Enum.SortOrder.Name on every layout, as it does in Studio. Children with distinct Names sort by name rather than by source order. Set SortOrder={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.
  • print types oddly. The globals declaration omits print because lib.dom already 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, DataStoreService and 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 shims at a browser module. @rbxts/react, @rbxts/react-roblox, @rbxts/services, @rbxts/vide, @rbxts/ui-labs, @rbxts/ripple and @rbxts/react-ripple are 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 under loom 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 own setImageResolver.
  • The React version is pinned in effect. The adapter’s reconciler targets React 18. The plugin aliases bare react to 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. TextSize is divided by the face box rather than spent as a font-size. And advances are quantized to the half pixel and kerned the way the engine spends them. Against GetTextBoundsAsync at 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.