Vela is a compile-time utility-class system for roblox-ts React. You write Tailwind-shaped class names on Roblox host elements and on your own components, and a TypeScript transformer rewrites them into real Roblox properties and helper instances before roblox-ts emits Luau.
Nothing about Vela is a runtime library you render. It is a transformer you register in tsconfig.json, plus a config file it reads from disk. This page gets both in place.
Install the package
pnpm add vela-rbxtsnpm install vela-rbxtsyarn add vela-rbxtsbun add vela-rbxtsvela-rbxts expects the ordinary roblox-ts React stack alongside it. Install those yourself if the project does not already have them.
pnpm add @rbxts/react @rbxts/react-roblox @rbxts/services@rbxts/react and @rbxts/services matter beyond your own imports: when Vela takes its runtime path it inlines a helper module into your output that imports React from @rbxts/react and { UserInputService, Workspace } from @rbxts/services. Both must resolve in the built place even if you never import them by hand. @rbxts/react-roblox is what you need to mount a tree at all.
pnpm add -D roblox-ts typescript @rbxts/compiler-types @rbxts/typesNo package in the Vela repo declares a single peerDependency, and the only engines field anywhere is the VS Code extension’s vscode: ^1.90.0. TypeScript, roblox-ts and React are plain dependencies, so your package manager will not warn you about a mismatch. What follows is what Vela is developed and tested against, not a constraint you have to satisfy: Node 24 in CI, TypeScript 5.9.3 pinned exactly in the repo, roblox-ts ^3.0.0, @rbxts/compiler-types ^3.0.0-types.0, @rbxts/types ^1.0.935, @rbxts/react ^17.3.7-ts.1, and @rbxts/services ^1.6.0. One detail is worth knowing: the host adapter resolves typescript from your install at runtime without declaring a dependency on it, so a project that somehow has no resolvable typescript will fail when Vela tries to load your config.
Register the transformer
Vela runs as a roblox-ts transformer plugin. Add one entry to compilerOptions.plugins in your existing tsconfig.json:
{ "compilerOptions": { "plugins": [{ "transform": "vela-rbxts/transformer" }] }}That is the entire tsconfig change. Everything else in the file stays as your roblox-ts project already has it — Vela requires no compiler option of its own, and if the project builds today it will keep building. If you do not have a project yet, start from roblox-ts’s quick start and come back here.
vela-rbxts/transformer is a separate export from the package root. The root export (vela-rbxts) gives you defineConfig and types; the /transformer subpath is the callable roblox-ts consumes. Do not swap one for the other.
The plugin entry object is also where transformer options go — file-filter toggles, a diagnostic code base, a project root. The defaults are what you want to start with; the configuration reference covers the rest.
Add vela.config.ts
Vela looks for exactly one filename, vela.config.ts, walking up from the directory of each source file until it finds one or hits the filesystem root. There are no .js, .mjs or .cjs variants and no package.json key. If nothing is found, Vela uses its built-in default theme, which already contains 28 color families, ten radius keys, and the spacing fallback.
The minimum useful file accepts the defaults wholesale:
import { defineConfig } from "vela-rbxts";
export default defineConfig();When you do want your own values, put them under theme.extend. Every value is an expression string written in the roblox-ts dialect, not a color object or a number:
import { defineConfig } from "vela-rbxts";
export default defineConfig({ theme: { extend: { colors: { brand: { 500: "Color3.fromRGB(59, 130, 246)", 700: "Color3.fromRGB(29, 78, 216)", }, }, radius: { panel: "new UDim(0, 10)", }, }, },});A top-level theme.colors replaces the entire color registry and, critically, causes theme.extend.colors to be discarded silently — no error, no warning. Setting both means only the top-level scale survives, every built-in palette disappears, and your extend colors never resolve. This differs from real Tailwind, where extend is layered on top. The same replace-wholesale rule applies to theme.radius and theme.spacing. Unless you genuinely want to throw away every default, add colors through theme.extend.colors alone.
Full details on merge behavior and the shipped defaults are in the theming guide.
Declare the className prop
Roblox host elements have no className prop in @rbxts/types. Vela adds one by augmenting React.Attributes globally, and that augmentation only takes effect if the package is imported for its side effect somewhere in your program. Create a declaration file to do that once:
import "vela-rbxts";That single import pulls in declare global { namespace React { interface Attributes { className?: ClassValue } } }. Without it, every className you write is a type error, even though the transformer would still have processed it.
Keep the file inside rootDir so TypeScript picks it up with the rest of your sources.
A roblox-ts project sets baseUrl to src, and TypeScript tries baseUrl before node_modules for a bare specifier. Under that name, import "vela-rbxts" resolves to the declaration file itself rather than the package, so the augmentation never loads and className stays a type error — with no diagnostic pointing at the cause. Any other name works.
Build and serve
There is no Vela-specific build step. Compile with roblox-ts as usual:
npx rbxtscThen sync out/ into Studio with Rojo the same way you already do:
rojo serveYou do not need a Rojo mapping for Vela, and there is no Vela runtime package to place in the DataModel. Everything Vela produces is either an ordinary Roblox property, a helper instance emitted as a JSX child, or — on the runtime path — a helper module inlined directly into the same output file. The only things that must exist in your place tree are @rbxts/react and @rbxts/services, which your existing node_modules → ReplicatedStorage mapping already covers.
Next step
Write something and watch it lower in Your first component.