Style (asset type)
When to use one
- You're branding a UI surface (game theme, editor theme, dialog theme) and want a named token set widgets pick up automatically.
- You want a swappable theme — light vs dark, normal vs accessibility contrast.
- You want designers to edit tokens (
color.primary,spacing.md) without touching widget code.
If you need per-widget overrides, those go inline on the widget, not
in a style. If you need shader-driven looks, that's a .shader +
.material.
Where it lives
- Source:
/zero/source/.../<name>.style/ - Identity:
<name>(the.stylesuffix strips). - Folder shape:
init.luau(orinit.lua) — returns the module carrying the token table. Required.README.md— instance documentation. Required..metadata— agent-editable tags + free-form fields. Required.
How to create one
asset.create("style", "<name>")
-- Creates: /zero/source/<name>.style/
-- init.luau (token scaffold: color / spacing / font)
-- README.md (instance README template)
-- `folder` places it in a subfolder of /source instead of the root:
asset.create("style", "<name>", { folder = "themes" })
How it operates
- Registration. Writing
init.luauinto a.style/folder registers the asset — it is listable, inspectable, and referencable by identity. - Token shape.
init.luaureturns a module exposingM.tokens, one nested table per token category:color.<name>/spacing.<name>/font.<name>are the conventional three; a style may declare any category it wants.- A value is a literal (a
"#rrggbb"colour string, a number, an array) or a"$otherToken"reference resolved during the cascade. An optionalM.stylesmaps a selector —<widgetType>,<widgetType>.<className>, or a bare.<className>— to a block of style properties, each value a literal or a$tokenreference.
- Cascade. The stylesheet resolves a widget's look in the order
type→type.class→.class→ inline props. Widgets select classes with theirclasses(orclass) prop. - Application. The UI theme surface takes the style's table:
Theme.register(name, tokens_and_styles)from@builtin::modules.zui.themeresolves every$tokenin Luau and pushes the flat result throughui.registerTheme. From thereui.setTheme(name)makes it the active theme, andui.useStyles(name)applies a registered theme's classes additively without changing the active one.ui.defineStyle/ui.defineStylesdefine individual classes directly. - Hot reload. Editing
init.luaureloads the asset; re-register the style for the change to reach the live stylesheet.
Discovery
asset.list("style")— every registered style.asset.inspect("<name>")—tokenCategories(the top-level keys of the style'sM.tokensliteral, read from its source text), source, and this type README.ls /zero/source/<name>.stylelists the files the folder holds.styleRef:getInitScript()— the entry script's raw text.
Authoring conventions
- Write colours the way the theme system reads them —
"#rrggbb"/"#rrggbbaa"hex orrgba(r,g,b,a)strings. - Name tokens semantically (
color.primary,color.danger,spacing.gutter), not by visual value (color.blue,spacing.eight). Semantic names survive theme swaps. - Keep token names stable. Widgets reference tokens by name — renaming a token forces every consumer to update.
- Document
light/darkvariants under separate.style/folders with paired names (game.style,gameDark.style) rather than switching values within one style.
Common pitfalls
- Unknown
$tokenreference. A"$name"with no matching token fails the cascade —Theme.registerreturnsfalse, "token 'a': unknown token: $name"and the style never reaches the stylesheet. Read the error rather than the render. - Token reference cycles.
$a→$b→$ais reported astoken 'a': cycle detected: a → b → aat register time. - Missing tokens. A widget asking for
color.warnwhen the style doesn't define it falls back to the active theme. Inspect the style to confirm the token surface.
Related types
.component—UiPaneland other widget components consume styles..material/.shader— for 3D surface looks (different cascade system)..package— to ship styles + components together as a theme package.
In the Inspector
Selecting a style opens its Tokens section, from the theme styleRef:loadTheme() returns: a swatch per colour token, its spacing values, and a table of its font tokens (family and size). A style written as YAML style classes (a styles: map of class name to properties, applied with ui.useStyles) shows how many classes it declares and, for each class, its colour properties as swatches and its other properties as values. A style whose init.luau does not load a tokens table says why.