Skip to content

Getting Started

roblox-ts is the established TypeScript-to-Luau compiler for Roblox. rbx-tsx takes a different approach:

rbx-tsxroblox-ts
Runtime dependencyNone — compiles to standalone LuauRequires @rbxts/compiler-types runtime
Luau type outputFull Luau type annotations preserved — interfaces, generics, unions, optionalsTypes are erased; output is untyped Luau
React / JSXBuilt-in. JSX compiles directly to React.createElement for react-luaRequires separate @rbxts/react type packages and manual tsconfig.json JSX setup
HTML elementsWrite <div>, <button>, <input> — mapped to Roblox GUI classes automaticallyMust use Roblox class names directly
JS API coverageconsole, Math, JSON, Array, String, Object, RegExp, setTimeout — compiled inlineRequires runtime package for JS API polyfills
CSS supportCompanion rbx-css compiler with --css flagNot built-in
Output readabilityClean, 1:1 Luau with typesReadable but includes runtime calls, no types
EcosystemRojo + WallyRojo + npm (@rbxts/* packages)
Setupnpm install rbx-tsx — that’s itRequires @rbxts/* type packages, compiler config, and matching versions

Both compilers support async/await, generators, and decorators. roblox-ts handles these via installed runtime packages, while rbx-tsx inlines the polyfills directly into the output.

Terminal window
npm install rbx-tsx
Terminal window
rbx-tsx init my-app
cd my-app
rbx-tsx compile src/ -o out/

This generates a Rojo-ready project with wally.toml, default.project.json, and starter components.

Terminal window
# Compile a single file (to stdout)
rbx-tsx compile App.tsx
# Compile to a file
rbx-tsx compile App.tsx -o App.luau
# Compile a directory
rbx-tsx compile src/ -o out/
# Watch mode
rbx-tsx watch src/ -o out/
# Type check only (no emit)
rbx-tsx check src/

Input (Counter.tsx):

import React, { useState, useCallback } from "react";
interface CounterProps {
label: string;
initialCount?: number;
}
export default function Counter({ label, initialCount = 0 }: CounterProps) {
const [count, setCount] = useState(initialCount);
const increment = useCallback(() => {
setCount((c) => c + 1);
}, []);
return (
<div className="counter">
<h1>{label}</h1>
<span>Count: {count}</span>
<button onClick={increment}>+</button>
</div>
);
}

Output (Counter.luau):

local React = require(game:GetService("ReplicatedStorage").Packages.React)
local useState = React.useState
local useCallback = React.useCallback
type CounterProps = {
label: string,
initialCount: number?,
}
local function Counter(props: CounterProps)
local label = props.label
local initialCount = if props.initialCount ~= nil then props.initialCount else 0
local count, setCount = useState(initialCount)
local increment = useCallback(function()
setCount(function(c) return c + 1 end)
end, {})
return React.createElement("Frame", { [React.Tag] = "counter" }, {
H1 = React.createElement("TextLabel", { Text = label }),
Span = React.createElement("TextLabel", { Text = `Count: {count}` }),
Button = React.createElement("TextButton", {
[React.Event.Activated] = increment,
Text = "+",
}),
})
end
return Counter