This might have been the most productive single day on the project. We went from “GUIs are built in Studio and manually wired up” to “GUIs are fully code-driven with a CSS-like styling system” in one sitting.

Why?

Roblox’s built-in GUI workflow is: open Studio, drag frames around in the explorer, set properties one by one in the Properties panel, then write scripts to wire everything up. It’s fine for simple UIs, but it means your UI layout lives in the place file, not in your code. You can’t version control it cleanly with Rojo (instance properties are binary), you can’t generate UI dynamically, and every new HUD element means going back to Studio to drag more frames around.

We wanted something closer to how web developers build UIs: describe the structure and styling in code, and let the system handle the rendering.

What we built (in 10 phases)

  1. Core infrastructure — A GUI.create() function that builds Instance trees from declarative tables
  2. CSS-like styling — Named style sheets that map property names to Roblox properties
  3. ModifiersUICorner, UIStroke, UIPadding applied through style declarations
  4. Grid layout — Auto-arranging children via UIGridLayout and UIListLayout
  5. Responsive breakpoints — Style overrides that kick in at different screen aspect ratios
  6. Z-index layering — Explicit layer ordering so HUD elements don’t fight each other
  7. Pseudo-classes:hover, :active, :disabled states, just like CSS
  8. Declarative actionsonClick, onHover handlers defined inline with the style
  9. Runtime style updates — Change styles on the fly and the GUI updates in place
  10. Migration — Ported every existing GUI from Studio-authored to code-driven

The result looks something like this:

GUI.create("Frame", {
    style = { size = {200, 50}, backgroundColor = Color3.new(0.2, 0.2, 0.2) },
    hover = { backgroundColor = Color3.new(0.3, 0.3, 0.3) },
    children = {
        GUI.create("TextLabel", {
            style = { text = "Start Game", textColor = Color3.new(1, 1, 1) }
        })
    }
})

No dragging in Studio. No manual property setting. Version-controllable, diffable, and dynamically generatable.

The migration

Converting every existing GUI was the real test. Each one went from “a tree of Instances in the place file + a script to animate them” to “a single GUI.create() call with a style table.” The Scoreboard, the satisfaction HUD, the MessageTicker, the Dispenser counter — all of them.

Some were straightforward translations. Others exposed gaps in the system — we hadn’t accounted for UIAspectRatioConstraint in the style system until the Scoreboard migration needed it. Each migration made the system more complete.

We capped the day by building a modular HUD layout system — a manager that handles positioning and layering of all HUD elements. Individual assets register their HUD widget and the layout system decides where on screen it goes. This means adding a new HUD element is one line of registration, not a Studio layout session.

Total output: a full GUI framework, responsive and styled, with every game UI migrated and a layout manager on top. Not bad for a day’s work.