# Diagnostics

> Every diagnostic code, how it reaches your build or editor, and the ones easy to miss.

Source: https://docs.astra-void.xyz/vela-rbxts/reference/diagnostics/

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:

```text
[@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.

> **Two things that will surprise you**
>
> **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](https://docs.astra-void.xyz/vela-rbxts/reference/utilities.md#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](https://docs.astra-void.xyz/vela-rbxts/reference/utilities.md#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`](https://docs.astra-void.xyz/vela-rbxts/reference/config.md#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:

```tsx title="Warns, and the className survives untouched"
<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.

```tsx title="A typo"
<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:

```text title="Compiler output"
[@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](https://docs.astra-void.xyz/vela-rbxts/reference/utilities.md#not-implemented) 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`.

```tsx title="Fails"
<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](https://docs.astra-void.xyz/vela-rbxts/reference/config.md#merge-semantics).

### color-missing-shade

You named a palette family without a shade, and that palette has no `DEFAULT` to fall back on.

```tsx title="Fails, if your brand palette defines only 500 and 700"
<frame className="bg-brand" />
```

```text title="Compiler output"
[@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.

```tsx title="Fails"
<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](https://docs.astra-void.xyz/vela-rbxts/reference/utilities.md) for the values each family accepts.
- [Editor setup](https://docs.astra-void.xyz/vela-rbxts/guides/editor-setup.md) for getting these into your editor.
- [Troubleshooting](https://docs.astra-void.xyz/vela-rbxts/guides/troubleshooting.md) for the failures that produce no diagnostic at all.
