# Troubleshooting

> A symptom-first map of Vela's failure modes, the silent ones especially.

Source: https://docs.astra-void.xyz/vela-rbxts/guides/troubleshooting/

Vela reports most mistakes as [diagnostics](https://docs.astra-void.xyz/vela-rbxts/reference/diagnostics.md). This page is for
the other situation: **something is wrong on screen and the build said nothing.**

> **Check your version first**
>
> Most of the silent failures below were fixed releases ago. Run `npm ls @vela-rbxts/compiler` and read the [release notes](https://docs.astra-void.xyz/vela-rbxts/reference/release-notes.md) 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](https://docs.astra-void.xyz/vela-rbxts/getting-started/how-it-works.md#classname-on-components).

**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](https://docs.astra-void.xyz/vela-rbxts/guides/dynamic-class-names.md).

**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](https://docs.astra-void.xyz/vela-rbxts/guides/layout-and-sizing.md#justify--splits-across-two-properties).

**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](https://docs.astra-void.xyz/vela-rbxts/reference/config.md#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](https://docs.astra-void.xyz/vela-rbxts/reference/config.md#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](https://docs.astra-void.xyz/vela-rbxts/guides/theming.md).

## "Everything got bigger or smaller after upgrading to 0.12.0"

Expected. Every pixel offset is a [rem unit](https://docs.astra-void.xyz/vela-rbxts/guides/theming.md#rem) 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:

```ts title="vela.config.ts"
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:

```yaml title="pnpm-workspace.yaml"
nodeLinker: hoisted
```

npm 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](https://docs.astra-void.xyz/vela-rbxts/guides/vide.md).

## "`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](https://docs.astra-void.xyz/vela-rbxts/getting-started/installation.md#declare-the-classname-prop).

## "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](https://docs.astra-void.xyz/vela-rbxts/reference/api.md#prebuilt-compiler-binaries) — Windows on ARM and linux
arm64 musl do not.

## "Nothing is being transformed at all"

- The file must end in `.tsx` — a `className` built in a `.ts` helper is invisible to Vela.
- The tsconfig plugin must be `vela-rbxts/transformer`, the subpath. The root package's
  `createTransformer` is a different function, and passing it produces no transform and no error.
- The transformer only runs under `rbxtsc` — it throws under plain `tsc`.
- The file filter skips files with no literal `className` substring and no JSX open tag. Both are [toggleable](https://docs.astra-void.xyz/vela-rbxts/reference/config.md#tsconfig-plugin-options), 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](https://docs.astra-void.xyz/vela-rbxts/getting-started/how-it-works.md#five-rules-that-keep-you-on-the-static-path)
say how to move between them.
