LuauCSS: StyleSheets that you author like CSS

,

LuauCSS: StyleSheets that you author like CSS :nail_polish:

Intro

Roblox StyleSheets are great!! Wiring them up by hand is… a lot of Instance.news. I’m used to CSS so this makes my head spin when looking at it. Lets take this amalgamation of some samples from the docs site!

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local coreSheet = ReplicatedStorage:FindFirstChild("CoreSheet")

-- Create a tokens style sheet
local tokensSheet = Instance.new("StyleSheet")
tokensSheet.Name = "Tokens"
tokensSheet.Parent = ReplicatedStorage

-- Set tokens (attributes) on tokens sheet
tokensSheet:SetAttribute("LightGray", Color3.new(0.9, 0.9, 0.9))
tokensSheet:SetAttribute("DarkGray", Color3.new(0.2, 0.2, 0.2))

-- Create theme style sheets
local lightThemeSheet = Instance.new("StyleSheet")
lightThemeSheet.Name = "LightTheme"
lightThemeSheet:SetAttribute("Background", "$LightGray")
lightThemeSheet.Parent = ReplicatedStorage
local darkThemeSheet = Instance.new("StyleSheet")
darkThemeSheet.Name = "DarkTheme"
darkThemeSheet:SetAttribute("Background", "$DarkGray")
darkThemeSheet.Parent = ReplicatedStorage

-- Set theme sheets to derive from tokens sheet
lightThemeSheet:SetDerives({ tokensSheet })
darkThemeSheet:SetDerives({ tokensSheet })

local themeDerive = Instance.new("StyleDerive")
themeDerive.Parent = coreSheet
themeDerive.StyleSheet = lightThemeSheet

-- Function to dynamically change the derived theme for the core sheet
local function changeTheme()
	if themeDerive.StyleSheet == lightThemeSheet then
		themeDerive.StyleSheet = darkThemeSheet
	elseif themeDerive.StyleSheet == darkThemeSheet then
		themeDerive.StyleSheet = lightThemeSheet
	end
end

-- Add rules
local rule = Instance.new("StyleRule")
rule.Parent = coreSheet

-- Class selector
rule.Selector = "TextButton"

-- Set rule properties
rule:SetProperties({
	["BackgroundColor3"] = Color3.fromHex("335FFF"),
	["TextColor3"] = Color3.fromHex("E1E1E1"),
	["Size"] = UDim2.new(0.15, 0, 0, 40),
	["BorderSizePixel"] = 0,
})

-- And a pseudoelement
local cornerRule = Instance.new("StyleRule")
cornerRule.Parent = coreSheet

-- UI component selector
cornerRule.Selector = "Frame::UICorner"

-- Set rule property
cornerRule:SetProperty("CornerRadius", UDim.new(0, 20))

-- And transitions!
tokenSheet:SetAttribute("LinearTransition", TweenInfo.new(1, Enum.EasingStyle.Linear)) 
tokenSheet:SetAttribute("QuadTransition", TweenInfo.new(0.25, Enum.EasingStyle.Quad))

-- Make the rule
local frameRule = Instance.new("StyleRule")
frameRule.Parent = coreSheet
rule.Selector = "Frame"

-- Set transitions for multiple properties at once
frameRule:SetPropertyTransitions({
	["BackgroundColor3"] = "$LinearTransition",
	["BackgroundTransparency"] = "$QuadTransition",
})

LuauCSS is a simple ModuleScript that turns tokens, rules, gradients, and hover states into something a bit more readable thanks to some delicious syntax sugar in Luau :face_savoring_food:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LuauCSS = require(ReplicatedStorage:WaitForChild("LuauCSS"))

local Styling = LuauCSS.new {
	Tokens = {
		LightGray = Color3.new(0.9, 0.9, 0.9),
		DarkGray = Color3.new(0.2, 0.2, 0.2),
		LinearTransition = TweenInfo.new(1, Enum.EasingStyle.Linear),
		QuadTransition = TweenInfo.new(0.25, Enum.EasingStyle.Quad),
	},
	Themes = {
		LightTheme = { Background = "$LightGray" },
		DarkTheme  = { Background = "$DarkGray" },
	},
	Default = "LightTheme",
}

Styling
	"TextButton" {
		BackgroundColor3 = Color3.fromHex("335FFF"),
		TextColor3 = Color3.fromHex("E1E1E1"),
		Size = UDim2.new(0.15, 0, 0, 40),
		BorderSizePixel = 0,
	}
	"Frame::UICorner" {
		CornerRadius = UDim.new(0, 20),
	}
	"Frame" {
		Transition = {
			BackgroundColor3 = "$LinearTransition",
			BackgroundTransparency = "$QuadTransition",
		},
	}

local coreSheet = Styling:GetStyleSheet()
coreSheet.Name = "CoreSheet"
coreSheet.Parent = ReplicatedStorage

local currentTheme = "LightTheme"

local function changeTheme()
	currentTheme = if currentTheme == "LightTheme" then "DarkTheme" else "LightTheme"
	Styling:useTheme(currentTheme)
end

Get it:

  • :shopping_cart: Creator Store – automatically updated from github! thanks rojo :happy4:
  • :package: GitHub – copy the single script into your project.

Setup

  1. Grab LuauCSS from the Creator Store or copy LuauCSS.lua from GitHub into ReplicatedStorage as a ModuleScript
  2. Create your styling instance with tokens and/or themes
  3. Set up your rules — .Button, .Button:Hover, .Card::UIGradient, etc.
  4. Parent the sheet to ReplicatedStorage, then link with a StyleLink
local LuauCSS = require(ReplicatedStorage.LuauCSS)

local Styling = LuauCSS.new {
    Tokens = {
        Primary = Color3.fromHex("FF0099"),
        Accent  = Color3.fromHex("0093F0"),
    },
}

Styling
    ".Button" {
        BackgroundColor3 = "$Primary",
        Transition = { Default = TweenInfo.new(0.15) },
    }
    ".Button:Hover"  { BackgroundColor3 = "$Accent" }
    ".Button::UICorner" { CornerRadius = UDim.new(0, 8) }

local sheet = Styling:GetStyleSheet()
sheet.Parent = ReplicatedStorage

local link = Instance.new("StyleLink")
link.StyleSheet = sheet
link.Parent = screenGui

That’s it! Tag your instances and press Play, or use a great plugin like Flipbook or UILabs to preview scripted UI without having to do that :sparkles:

For more detailed API documentation please visit the github repo and read the readme :slight_smile:


Before / Afters :framed_picture:

Please appreciate these animations I spent too much time putting together that are probably objectively less useful then just showing the code snippets but like i said i spent too much time on them.





Also there’s something useful, i took tailwind’s default theme (theme.css) and turned it in to a fairly readable stylesheet (uploaded as an example).

I had my good friend cursor whip up something to create the header for this devforum post as well, thats an example too! I’ll work on other examples at some point.


What it doesn’t do (yet…) :construction:

  • :cross_mark: StyleDerive.Priority / multiple derives — one derive per instance
  • :cross_mark: Auto StyleLink — you still link the sheet yourself
  • :white_check_mark: StyleRule.Priority works fine via Priority = N in a rule

Feedback :speech_balloon:

Feature requests welcome in the comments! Bug reports too though the script is pretty simple. And you can open up a PR on the github repo if i set it up right. And if i didn’t set it up right file an issue if i set THAT up right.

Happy styling ~ Bitwise (@BitwiseAndrea)

11 Likes

pretty pretty cool, pretty unique as well in a way


, not sure about this tho, but amazing package

1 Like

wdym? StyleRule | Documentation - Roblox Creator Hub

1 Like