Skip to main content

Overview

The Luau output format generates a .luau module file that exports a factory function for creating Roblox StyleSheet instances. This is the default and recommended output format for rbx-css.

When to use Luau output

  • Default choice: Best for most use cases
  • Runtime creation: When you need to create StyleSheets programmatically at runtime
  • Type safety: Works seamlessly with Luau type checking
  • Minification: Supports minified output for production
  • Rojo integration: Works perfectly with Rojo workflows
  • Version control: Plain text format is easy to diff and review

Generated structure

The generated Luau module follows this structure:
-- Auto-generated by rbx-css
-- Source: styles.css

local function createStyleSheet()
    local sheet = Instance.new("StyleSheet")
    sheet.Name = "StyleSheet"

    -- Tokens from :root
    sheet:SetAttribute("primary", Color3.fromHex("#335fff"))
    sheet:SetAttribute("radius", UDim.new(0, 8))

    -- Style rules
    do
        local rule = Instance.new("StyleRule")
        rule.Selector = ".card"
        rule.Parent = sheet
        rule:SetProperties({
            BackgroundColor3 = "$primary",
        })
    end

    return sheet
end

return createStyleSheet

Module components

Factory function

The module exports a createStyleSheet() function that returns a fully configured StyleSheet instance:
local createStyleSheet = require(path.to.module)
local sheet = createStyleSheet()

Root StyleSheet

Creates the root StyleSheet instance and sets its name:
local sheet = Instance.new("StyleSheet")
sheet.Name = "StyleSheet"

Design tokens

CSS custom properties from :root become StyleSheet attributes:
-- Tokens from :root
sheet:SetAttribute("primary", Color3.fromHex("#335fff"))
sheet:SetAttribute("radius-md", UDim.new(0, 8))
sheet:SetAttribute("spacing", UDim.new(0, 16))

Style rules

Each CSS rule becomes a StyleRule instance with proper scoping:
-- Rule: .card
do
    local rule = Instance.new("StyleRule")
    rule.Selector = ".card"
    rule.Parent = sheet
    rule:SetProperties({
        BackgroundColor3 = "$primary",
        Size = UDim2.new(1, 0, 0, 100),
    })
end
Rules with a single property use SetProperty() for efficiency:
rule:SetProperty("BackgroundColor3", Color3.fromRGB(255, 255, 255))

Pseudo-instance rules

CSS properties that map to Roblox pseudo-instances (like border-radiusUICorner) generate separate rules:
-- Rule: .card::UICorner
do
    local rule = Instance.new("StyleRule")
    rule.Selector = ".card::UICorner"
    rule.Parent = sheet
    rule:SetProperty("CornerRadius", "$radius-md")
end

Theme support

When your CSS includes themes, the module generates theme StyleSheets and a helper function:
-- Themes
local themes = {}

do
    local theme = Instance.new("StyleSheet")
    theme.Name = "StyleSheet_dark"
    theme:SetAttribute("bg", Color3.fromHex("#1a1a2e"))
    theme:SetAttribute("text", Color3.fromHex("#ffffff"))
    themes["dark"] = theme
end

-- Theme derive
local themeDerive = Instance.new("StyleDerive")
themeDerive.Parent = sheet

-- Theme switcher
local function setTheme(themeName: string)
    themeDerive.StyleSheet = themes[themeName]
end

Example output

Given this CSS input:
:root {
  --primary: #335fff;
  --radius: 8px;
}

.card {
  background-color: var(--primary);
  border-radius: var(--radius);
  padding: 16px;
}

.card:hover {
  background-color: #4470ff;
}
The Luau output:
/home/daytona/workspace/source/src/codegen/luau.ts
-- Auto-generated by rbx-css

local function createStyleSheet()
    local sheet = Instance.new("StyleSheet")
    sheet.Name = "StyleSheet"

    -- Tokens from :root
    sheet:SetAttribute("primary", Color3.fromHex("#335fff"))
    sheet:SetAttribute("radius", UDim.new(0, 8))

    -- Rule: .card
    do
        local rule = Instance.new("StyleRule")
        rule.Selector = ".card"
        rule.Parent = sheet
        rule:SetProperty("BackgroundColor3", "$primary")
    end

    -- Rule: .card::UICorner
    do
        local rule = Instance.new("StyleRule")
        rule.Selector = ".card::UICorner"
        rule.Parent = sheet
        rule:SetProperty("CornerRadius", "$radius")
    end

    -- Rule: .card::UIPadding
    do
        local rule = Instance.new("StyleRule")
        rule.Selector = ".card::UIPadding"
        rule.Parent = sheet
        rule:SetProperties({
            PaddingTop = UDim.new(0, 16),
            PaddingBottom = UDim.new(0, 16),
            PaddingLeft = UDim.new(0, 16),
            PaddingRight = UDim.new(0, 16),
        })
    end

    -- Rule: .card:Hover
    do
        local rule = Instance.new("StyleRule")
        rule.Selector = ".card:Hover"
        rule.Parent = sheet
        rule:SetProperty("BackgroundColor3", Color3.fromHex("#4470ff"))
    end

    return sheet
end

return createStyleSheet

Value serialization

The Luau codegen serializes CSS values to Roblox types:

Color3

Color3.fromRGB(255, 0, 153)
Color3.fromHex("#335fff")

UDim and UDim2

UDim.new(0, 8)           -- 8px
UDim.new(0.5, 0)         -- 50%
UDim2.new(1, 0, 0, 100)  -- width: 100%, height: 100px

Numbers

16                -- font-size: 16px
math.huge         -- max-height: infinite
-math.huge        -- min-height: -infinite

Booleans

true              -- visibility: visible
false             -- display: none

Enums

Enum.TextXAlignment.Center
Enum.FillDirection.Vertical
Enum.FontWeight.Bold

Fonts

Font.new("rbxasset://fonts/families/GothamSSm.json")
Font.new("rbxasset://fonts/families/Roboto.json", Enum.FontWeight.Bold)
Font.new("rbxasset://fonts/families/Merriweather.json", Enum.FontWeight.Regular, Enum.FontStyle.Italic)

ColorSequence

Two-stop gradients use the shorthand constructor:
ColorSequence.new(
    Color3.fromRGB(255, 0, 153),
    Color3.fromRGB(255, 204, 0)
)
Multi-stop gradients use keypoints:
ColorSequence.new({
    ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 0, 0)),
    ColorSequenceKeypoint.new(0.5, Color3.fromRGB(0, 255, 0)),
    ColorSequenceKeypoint.new(1, Color3.fromRGB(0, 0, 255))
})

Token references

"$primary"        -- var(--primary)
"$radius-md"      -- var(--radius-md)

CLI usage

Output to file

rbx-css compile styles.css -o StyleSheet.luau

Output to stdout

rbx-css compile styles.css --format luau

Minified output

rbx-css compile styles.css -o StyleSheet.luau --minify
Minified output removes comments and indentation:
local function createStyleSheet()
local sheet = Instance.new("StyleSheet")
sheet.Name = "StyleSheet"
sheet:SetAttribute("primary", Color3.fromHex("#335fff"))
do
local rule = Instance.new("StyleRule")
rule.Selector = ".card"
rule.Parent = sheet
rule:SetProperty("BackgroundColor3", "$primary")
end
return sheet
end
return createStyleSheet

Custom StyleSheet name

rbx-css compile styles.css -o StyleSheet.luau --name "AppStyles"

Programmatic API

import { compile, generateLuau } from "rbx-css";

const result = compile(
  [{ filename: "styles.css", content: cssContent }],
  { name: "MyStyleSheet", warnLevel: "all", strict: false }
);

const luau = generateLuau(result.ir, {
  minify: false,
  sourceFile: "styles.css"
});

console.log(luau);

Source file tracking

When using the CLI with -o, the generated file includes a source comment:
-- Auto-generated by rbx-css
-- Source: styles.css
With the programmatic API, pass the sourceFile option:
generateLuau(result.ir, {
  minify: false,
  sourceFile: "my-styles.css"
});

Build docs developers (and LLMs) love