Velahome

Vela

Tailwind-style className compilation for roblox-ts UI — React or Vide.

vela-rbxts · v0.13.0 · pre-1.0

Vela adds className to roblox-ts UI elements and compiles it away. You write Tailwind-shaped utility classes on Roblox host elements and on your own components. A TypeScript transformer rewrites them into Roblox properties and helper instances before roblox-ts emits Luau. p-4 becomes a UIPadding child, rounded-lg a UICorner, bg-slate-700 a BackgroundColor3.

Vela emits for React by default, and for Vide when your tsconfig.json compiles Vide JSX. Nothing survives into the shipped game unless a class needs the player’s screen or input device to resolve.

Compiled by the real Vela compiler and rendered live. The Classes tab is the source as written; the Lowered tab is what rbxtsc emits.

Every class on that card resolves at build time. Classes is the component as written. Lowered is the output: plain Roblox properties, a few helper instances, no trace of Vela. Every preview in these docs comes from the real compiler.

What you get

Static by default. A plain string of classes lowers entirely at compile time. The className attribute disappears, the element keeps its tag, and Vela’s cost is paid once on your machine. Two things opt an element into runtime work: a className the compiler cannot read, and variants, which depend on the player’s device. A class list that only branches still resolves at compile time, diagnostics intact.

Diagnostics instead of silence. A class Vela cannot lower is reported, not dropped. tracking-wide warns that Roblox has no letter spacing. bg-brand says the palette needs a shade. A typo in a theme key names the scale it searched. The diagnostics reference lists every code. A few cases still fall through silently, and the docs flag each one where it lives.

Editor tooling from the same compiler. The vela-rbxts-lsp language server links the same Rust crate the build uses, so completions, hover, swatches, quickfixes and squiggles agree with the compiler by construction. See editor setup.

A Roblox-shaped vocabulary. Vela is not Tailwind parity. It implements the slice that maps onto what Roblox gives you: colours, gradients, strokes, corners, padding, gap, sizing, position, flex layout and typography. Everything else is rejected loudly rather than approximated. The edges are mapped in scope and status.

Get started

Read these in order:

  1. Installation — the package, the tsconfig plugin, the config file, and a smoke test.
  2. Your first component — build a panel and read what each class lowered to.
  3. How it works — the pipeline and the two paths a className can take.
  4. Scope and status — what is deliberately absent, and the gaps that do not announce themselves.

To skip setup, the playground runs the real compiler in the browser. Type class strings and watch them lower with no project at all.

Next

className in, Roblox props out.
<frame className="flex flex-col gap-4 p-4 w-80 rounded-lg bg-slate-800 border border-slate-700">
<textlabel className="text-lg font-semibold text-slate-50" Text="Party" />
<textbutton className="h-10 rounded-md bg-blue-600 text-white" Text="Invite" />
</frame>
Compiled, not interpreted
Static classes lower to Roblox props and helper instances during the roblox-ts build. No runtime cost, no generated folder.
Roblox-shaped, not Tailwind-shaped
Utilities map onto UIPadding, UIListLayout, UICorner, UIStroke, and the rest — the instances Roblox actually gives you.
Loud about its gaps
Unsupported families and unknown theme keys emit diagnostics instead of silently doing nothing.

Guides

Learn what lowers, and what doesn't.

Reference

Every class, value, and code.

Pre-1.0, and narrow on purpose.

Packages version in lockstep. Whole Tailwind families are not implemented, a dynamic className resolves against a much smaller utility set than a static one, and per-element restrictions are enforced by the editor rather than the compiler. Read the scope before you depend on a class.

Scope and status →