On the compile-time path, Vela reports most of what it cannot lower. This page lists every code it produces, what to do about the ones you will actually hit, and where nothing is reported at all.
How diagnostics reach you
The compiler produces diagnostics. The host adapter maps them into rbxtsc’s diagnostic list, using this message format:
[@vela-rbxts/compiler] unknown-theme-key: Unknown theme key "brand-500" for background color utility in className literal.The bracketed prefix is the phase: @vela-rbxts/compiler for anything found in your classes,
@vela-rbxts/rbxtsc-host for the one diagnostic the adapter synthesizes. Numeric codes are
diagnosticCodeBase + index per file, the base defaulting to 89000, so the numbering restarts for
each file. The category comes from the diagnostic’s level and defaults to Warning.
Diagnostics can be dropped entirely. They are delivered through context.addDiagnostic. On a roblox-ts that does not expose it, every Vela diagnostic is discarded, and your build looks clean while classes go missing. If you have never seen a Vela warning, verify with className="bg-nope-500".
Some spans are guessed, and some are not spans at all. A diagnostic carries a real source range
wherever the compiler could anchor it. That covers every token inside a static string className.
tsx-parse-failed is anchored too, and names the line and column.
With no range, the host adapter searches for the first textual occurrence of the diagnostic’s token.
That only happens when the diagnostic carries one, which in practice is the dynamic-className
case. Everything else falls through to {start: 0, length: 0} at the top of the file.
classname-on-unsupported-host is a middle case: a real range for a plain string literal, offset 0
for a computed expression.
So an underline can land on an unrelated comment, or on the first character of the file. Read the message rather than the underline.
Code table
Every code is a warning unless marked otherwise.
Families, variants, and payload shapes
| Code | Fires when |
|---|---|
unsupported-utility-family | The token does not match any known utility family — including typos |
no-roblox-equivalent | A real Tailwind family that cannot be expressed in Roblox UI (tracking-*, blur-*, cursor-*, …); the message names the family |
unknown-variant | A colon prefix that is not a supported variant; the message lists the set, the configured breakpoints, and the attr-[…] form |
unknown-breakpoint | Since 0.13.0: a max- in front of something that is not a breakpoint. The message names the configured ones and points at theme.screens |
invalid-breakpoint-range | Since 0.13.0: a chain whose width bounds leave no viewport, as lg:max-md: or md:max-md:. Emitting it would produce a rule that never fires |
malformed-attribute-variant | Since 0.13.0: an attr-[…] that does not parse — brackets left open, or no value to compare against. The message names which |
unsupported-arbitrary-value | A [...] payload the family cannot read — w-[abc], bg-[notacolor], font-[600]. Since 0.7.0 most length families do read brackets, and since 0.12.3 a space inside them does not split the token, so w-[calc(100% - 4px)] reports this once rather than three times; see arbitrary values |
unsupported-opacity-modifier | A /N opacity modifier on a family with no transparency channel. Since 0.12.0 that is placeholder-* alone, and the message says why |
Theme and color
| Code | Fires when |
|---|---|
unknown-theme-key | A color, radius, spacing, or font-family key is absent from the theme |
unsupported-color-key | current or inherit, or transparent on a property with no transparency channel |
color-missing-shade | A palette family referenced without a shade, and that palette has no DEFAULT |
color-invalid-shade | A literal given a shade, or a palette missing that shade |
unsupported-size-spacing-value | A spacing value used for a size is not offset-only |
Per-value rejections
Each family that accepts a closed set of values has its own code, so the message always tells you which family rejected what:
| Code | Family |
|---|---|
unsupported-border-value | border-* styles, sides, and off-list thicknesses. A /N modifier is no longer among them — since 0.12.0 border-slate-500/25 resolves |
unsupported-stroke-value | ring-* / outline-* off-list values, inset, offset-* |
unsupported-rotation-value | rotate-* outside the degree list |
unsupported-scale-value | scale-* outside the value list |
unsupported-opacity-value | opacity-* not an integer 0–100 |
unsupported-aspect-value | aspect-* outside square, video, valid brackets |
unsupported-flex-direction | flex-* that is neither a direction nor a flex-item keyword |
unsupported-alignment-value | An unresolvable justify-*, items-*, or content-* |
unsupported-grid-value | A grid-cols-*/grid-rows-* count outside 1–12 |
unsupported-layout-order-value | An unresolvable order-* |
unsupported-space-value | An unresolvable space-x-*/space-y-* |
unsupported-margin-value | An unresolvable margin payload |
unsupported-negative-margin | -mr-* or -mb-* — only top/left margins can be negative. Since 0.12.4 a side holds one signed value and the last class written to it wins; see margin |
unsupported-divide-value | An unresolvable divide-* payload |
unsupported-line-height-value | A leading-* outside the six named keys |
unsupported-text-size | A text-{size} that cannot be resolved (not reachable in practice) |
unsupported-font-weight | font-{x} is not a known weight name (not reachable since 0.7.0 — see below) |
unsupported-text-alignment | text-justify, or an align-* outside top/middle/bottom |
unsupported-whitespace-value | A whitespace-* outside normal/nowrap |
unsupported-gradient-direction | bg-gradient-to-{x} with a direction outside the eight |
unsupported-shadow-inset | shadow-inner |
unsupported-overflow-value | overflow-* other than hidden, clip, visible |
unsupported-anchor-value | origin-{x} outside the nine origin keys |
unsupported-object-fit-value | An object-* outside cover/contain/fill/tile |
unsupported-pointer-events-value | A pointer-events-* outside none/auto |
unsupported-overscroll-value | An overscroll-* outside auto/contain/none |
unsupported-scroll-value | A scroll-* outside x/y/xy/none — which is where Tailwind’s scroll-smooth and scroll-m-* land |
unsupported-transition-value | An unresolvable transition/duration/delay/ease payload. transition-shadow stopped landing here in 0.13.0, when transitions began moving the helper instances |
unsupported-animation-value | An animate-* outside the presets |
Z-index gets four codes of its own: z-auto is unsupported-z-index-auto, a negative -z-N is
unsupported-negative-z-index, a fractional z-[1.5] is unsupported-arbitrary-z-index, and any
other unbracketed number is unsupported-z-index-value. A whole-number z-[15] resolves.
font-* falls through to unknown-theme-key. The prefix resolves the fixed weight names first
and reads anything else as a key in theme.fontFamily.
font-nope, font-600 and font-extrablack all report unknown-theme-key.
unsupported-font-weight still exists in the code table, but no payload reaches it.
Contextual warnings
| Code | Fires when |
|---|---|
transition-without-runtime | transition/duration-*/ease-*/delay-* on an element with no variant rules and no dynamic className — there is nothing to animate, so the tokens are dropped |
motion-on-component | transition-* or animate-* on a component element — motion needs a real instance to drive |
decoration-on-richtext | underline/line-through on an element that already sets RichText itself — Vela backs off rather than double-wrapping your markup |
classname-on-unsupported-host | Build only. className sits on a JSX element that is neither a supported host element nor a component |
unsupported-host-utility | Editor only. The utility is not allowed on that host element tag |
Errors
| Code | Fires when |
|---|---|
tsx-parse-failed | The file failed to parse, or parsed with recovered errors |
tsx-emit-failed | Code generation failed after a successful transform |
invalid-config-json | The resolved config JSON handed to the compiler is malformed; the file falls back to the default theme |
compiler-invocation-failed | The native compiler call threw; synthesized by the host adapter |
The errors are structural rather than about your classes.
compiler-invocation-failed has one cause common enough to name. The host adapter resolves
typescript from your project and calls into its API. TypeScript 7 surfaces here as
Cannot read properties of undefined (reading 'Latest') on the first file. Pin typescript@5.9.3.
Everything else under this code is the native binding failing to load or throwing.
classname-on-unsupported-host is the one that tells you nothing happened. Vela lowers className
on the eight supported host elements and on components. Anything else keeps the attribute verbatim
and warns:
<screengui className="bg-slate-700" />That bucket is everything neither host nor component — lowercase intrinsics such as screengui, and
namespaced tags such as <svg:rect />, which never count as components. The message names only the
local part, so <svg:rect /> is reported as rect. Move the classes onto a frame inside the
element, or onto a component that forwards them.
This one is build only: the editor surface never collects an unsupported element, so it never emits this code.
unsupported-host-utility never appears in a build. The per-element restrictions it reports are
checked by the editor surface alone. <frame className="text-red-500" /> compiles and emits a
TextColor3 assignment on a Frame with no complaint. Treat it as an editor-only lint. It is never
raised on a component either, since the host element behind <Panel /> is unknown.
The runtime path does enforce the rule, silently. A utility the host element cannot carry is dropped rather than written, since the assignment would be a hard Roblox error. The same class list behaves differently on the two paths, and neither reports anything at build time.
The ones you will actually hit
unsupported-utility-family
A class Vela does not recognize at all — usually a typo. The token is dropped and the element compiles normally.
<frame className="shadwo-lg rounded-lg" />rounded-lg is applied, and shadwo-lg is dropped with a warning. The same code covers typos and
genuinely unknown families, so read the token before concluding a feature is missing.
no-roblox-equivalent
You wrote a real Tailwind utility that Roblox UI cannot express. The message names the family and says the token is ignored:
[@vela-rbxts/compiler] no-roblox-equivalent: Tailwind "tracking" utilities have no Roblox equivalent, so "tracking-wide" is ignored.This is a design decision, not a roadmap gap — the not-implemented table gives the reason per family and what to reach for instead.
unknown-theme-key
The class parsed fine, but the key it looked up is not in the resolved theme. The family in the
message tells you which scale was searched: colors, radius, or spacing.
<frame className="rounded-huge p-0.25" />rounded-huge fails because rounded-* is a pure theme lookup and huge is not one of the default
radius keys — add it under theme.extend.radius or use 4xl. p-0.25 fails because the spacing
fallback only accepts multiples of 0.5. Use p-0.5.
The most common form of this one is subtler. A color you thought you defined in theme.extend.colors
will report unknown-theme-key if your config also sets a top-level theme.colors, because that
combination silently discards extend. See the callout in
Configuration.
color-missing-shade
You named a palette family without a shade, and that palette has no DEFAULT to fall back on.
<frame className="bg-brand" />[@vela-rbxts/compiler] color-missing-shade: Color palette "brand" for background color utility has no "DEFAULT" shade, so it requires an explicit shade such as "brand-500" in className literal.Either write the shade, as bg-brand-500, or add a DEFAULT key to the palette in
theme.extend.colors so the bare form resolves. Literal colours are always usable without a shade.
That covers black, white, and anything you define as a plain string. Every built-in palette
ships a DEFAULT, so bg-slate works out of the box.
color-invalid-shade
Either you gave a literal a shade, or you asked a palette for a shade it does not have.
<frame className="bg-white-500 text-brand-400" />bg-white-500 fails because white is a literal — write bg-white. text-brand-400 fails if your
brand palette only defines 500 and 700 — add 400, or use a shade you defined. Palettes do not
interpolate missing shades.
Editor-only filtering
The editor surface applies one filter the build does not. It suppresses unknown-theme-key while
the payload you have typed so far is still a prefix of a real theme key. As you type bg-sla on
the way to bg-slate-800, no squiggle flashes, because sla can still become slate. bg-nope
warns immediately, because no theme key starts with nope.
A wrong key that happens to be a prefix of a real one, such as bg-sl or bg-slat, looks fine in
the editor. It still warns in the build. Codes other than unknown-theme-key are never filtered, so
color-missing-shade on a DEFAULT-less palette shows up immediately.
See also
- Utility reference for the values each family accepts.
- Editor setup for getting these into your editor.
- Troubleshooting for the failures that produce no diagnostic at all.