Vela reports most mistakes as diagnostics. This page is for the other situation: something is wrong on screen and the build said nothing.
Most of the silent failures below were fixed releases ago. Run npm ls @vela-rbxts/compiler and read the release notes first. Upgrading is the whole fix for several problems. Runtime-path coverage gaps, opacity-* not reaching a subtree, “out of local registers”, a motion driver taking the tree down at mount, and a slow build.
”A class did nothing”
Sorted by how often each turns out to be the answer.
1. The utility does not belong on that host element. <frame className="text-red-500" />
compiles and emits TextColor3 onto a Frame, which rejects it. The static path warns
unsupported-host-utility and writes it anyway. The runtime path drops it, so the same class list
can behave differently on the two paths.
2. The class is on a component that does not forward it. <Panel className="p-4" /> hands
Panel resolved props and helper children. If Panel does not spread its unrecognized props onto a
host element and render children, everything vanishes with no warning. See
How it works.
3. The element is on the runtime path and the class value is unchecked. A typo that would warn
in a literal is silent in a computed string. A runtime-path element renders as VelaRuntimeHost in
the compiled output — see dynamic class names.
4. Your build drops Vela diagnostics entirely. Diagnostics reach you through roblox-ts’s
context.addDiagnostic. On a version that does not expose it, every warning disappears. Confirm by
compiling className="bg-nope-500" and checking that a warning appears.
5. The editor stayed quiet, but the key is wrong. The editor suppresses unknown-theme-key
while your token is still a prefix of a real key. bg-slat shows no squiggle and still warns in the
build.
6. transition on a static element. A static element never changes, so it is dropped with
transition-without-runtime. Add the hover: or breakpoint rule that gives it something to tween.
”The editor flags a class the build accepts”
The build is the source of truth, so a squiggle the compiler disagrees with is an editor bug. Upgrade.
They genuinely disagree in one direction by design. The per-host rules, reported as
unsupported-host-utility, are enforced in the editor and never by the build. That is the first
entry on this page.
”A breakpoint or orientation variant never applies”
Check the token’s other conditions — a chained md:touch: needs both to match. Bare thresholds are
min-width on the viewport’s X dimension, so a phone-sized viewport matches none of them. A square
viewport counts as landscape.
”The layout is wrong”
left-* / top-* are ignored. The parent has a UIListLayout, created by any flex, gap-*
or space-*, and a list layout owns its children’s positions. Position an element only under a
plain frame.
An element sits outside its parent after right-* or bottom-*. Those set the coordinate from
the far edge but do not anchor the element. Its top-left corner lands there. Add the matching
origin-*, as in right-2 bottom-2 origin-bottom-right.
justify-* behaves strangely. justify-center and justify-between set two different
properties (HorizontalAlignment vs HorizontalFlex), so writing both makes them interact inside
the layout solver. Pick one. See
layout and sizing.
Spacing is bigger than you asked for. Margins are wrappers that participate in the parent’s list,
so they sum with its gap-*. Prefer gap alone for list spacing.
A grid stops laying out when you add gap-*. Still open: gap on a grid element also emits a
conflicting UIListLayout. Set CellPadding as a prop and keep gap-* off.
Children shuffle when divide-* is present. Separators are inserted by child position and break
under explicit LayoutOrder/order-*. Use source order with dividers.
order-* does nothing. The layout instance is one you wrote yourself, and SortOrder defaults
to Name, which sorts alphabetically and ignores LayoutOrder. Set it to LayoutOrder. Vela’s own
layouts set it for you.
”The colors are wrong”
An element that used to be visible disappeared. It relied on Roblox’s default grey background,
and preflight starts every classed host element at
BackgroundTransparency = 1. Give it the bg-* it was implicitly using, or set preflight: false.
Elements with no className, and components, are never preflighted.
A gradient renders as a flat fill. One of its stops is transparent, which is silently dropped.
This is the one transparency case that produces no diagnostic.
bg-* seems ignored next to a gradient. By design: a gradient forces BackgroundColor3 to white
so the stops read true, overriding any bg-* regardless of order.
A theme colour you defined reports unknown-theme-key. Two suspects. A top-level theme.colors
silently discards theme.extend.colors along with every built-in palette. And discovery walks
up from each source file, so a stray vela.config.ts in a subdirectory shadows the root one. See
merge semantics.
opacity-* fades a transparency you set yourself. Expected when the fade comes from an ancestor
component. The alpha arrives as React context, and a consumer cannot tell your
BackgroundTransparency prop from one Vela lowered.
Overlapping siblings darken where they overlap. opacity-* fades each instance rather than
compositing the group. Put them in a canvasgroup and fade that.
A themed colour works statically but not on a dynamically-classed element. The runtime path
re-parses theme values from text. It accepts exactly Color3.fromRGB(r, g, b) and new UDim(a, b)
with numeric literals. Anything else silently degrades. See theming.
”Everything got bigger or smaller after upgrading to 0.12.0”
Expected. Every pixel offset is a rem unit following the viewport, so a layout tuned by eye at one resolution keeps its proportions there and changes everywhere else.
A screen uniformly off means the viewport you are testing at. Off only at the extremes means the
clamp is doing its job, at min 8 and max 64, both configurable. To rule rem out:
export default defineConfig({ theme: { rem: { min: 16, max: 16 } } });Text stopped growing past a point. TextSize is capped at 100, where Roblox stops honoring it.
”@rbxts/vela-runtime does not resolve”
The runtime reaches your place as a dependency of vela-rbxts, and pnpm’s default isolated linker
does not put a transitive dependency where roblox-ts and Rojo look. Set the linker at the workspace
root:
nodeLinker: hoistednpm and Yarn hoist by default. A nested .npmrc will not do it — pnpm reads nodeLinker only from
the workspace root.
”A margin renders unspaced under Vide, with a warning”
A margin box is an instance above the element, and Vide parents an element as soon as it builds
one. The box has to be decided before the element exists. The compiler handles any m-* it can see,
in a static token or a branch, and warns on one arriving out of an opaque call. Name the token where
the compiler can read it. See the Vide guide.
”className is a type error”
The global augmentation only loads if vela-rbxts is imported somewhere in your program. Add the
one-line declaration file — and do not name it src/vela-rbxts.d.ts, which makes the import resolve
to itself under roblox-ts’s baseUrl. See
installation.
”The build fails on the first file with compiler-invocation-failed”
If the message is Cannot read properties of undefined (reading 'Latest'), your TypeScript is too
new. 7.x moved the API Vela’s host adapter calls. Pin typescript@5.9.3, which the repo tests
against. 6.x works too.
Any other compiler-invocation-failed means the native compiler call threw. Check the platform has a
prebuilt binary — Windows on ARM and linux
arm64 musl do not.
”Nothing is being transformed at all”
- The file must end in
.tsx— aclassNamebuilt in a.tshelper is invisible to Vela. - The tsconfig plugin must be
vela-rbxts/transformer, the subpath. The root package’screateTransformeris a different function, and passing it produces no transform and no error. - The transformer only runs under
rbxtsc— it throws under plaintsc. - The file filter skips files with no literal
classNamesubstring and no JSX open tag. Both are toggleable, but on by default.
”The output got bigger than expected”
A file larger than you expect is usually one whose host has to parse a class value: a computed
className, or a host taking a spread. That is the only case that keeps the full theme tables in the
emit. Writing the branch out instead of interpolating it is often enough to drop them.
Still stuck?
Compile the smallest possible reproduction and read the emitted TSX before the Luau. A static-path
element keeps its tag and gains plain props. A runtime-path element becomes VelaRuntimeHost with
__vela* props. Which of the two you are looking at answers most questions on this page, and the
five rules
say how to move between them.