Skip to content

User Interface

Lumina’s UI is built on RmlUi, a retained-mode UI library that reads like HTML and CSS. You author two kinds of file:

  • .rml, the document: the markup that says what elements exist.
  • .rcss, the stylesheet: the rules that say how they look and lay out.

Both are plain text, both live in your project’s content folder, and both are edited in the UI editor with a live preview.

There are two separate paths, and picking the wrong one is a common first mistake.

Screen-space UIWorld-space UI
What it isA document drawn flat over the viewport: menus, HUDs, dialogs.A document rasterized onto a quad that lives in the level: a console panel, a floating name plate.
How you use itWorld.UI from a script.The Widget Component on an entity. See World Widgets.
InputReceives mouse and keyboard through the viewport.Not interactive.

Each world owns its own screen-space UI context, created with the world and destroyed with it. Documents you load in one world never appear in another.

Five minutes from nothing to a menu on screen.

In the Content Browser, right-click a folder and choose New UI Widget. You get NewWidget.rml with a working skeleton. Double-click it to open the UI editor.

<rml>
<head>
<title>New Widget</title>
<style>
/* RmlUi has no HTML-like default stylesheet: EVERY element starts as
display:inline, so containers must be made block (or flex) or they
collapse and ignore width/padding/margin. */
div { display: block; box-sizing: border-box; }
/* Fill the view and center the panel. 'dp' scales with the display. */
body { width: 100%; height: 100%; display: flex;
align-items: center; justify-content: center;
color: #cdd6f4; font-size: 16dp; }
.panel { padding: 24dp; background-color: #1e1e2e;
border-width: 1dp; border-color: #45475a; border-radius: 8dp; }
</style>
</head>
<body>
<div class="panel">
<div>Hello from RmlUi.</div>
</div>
</body>
</rml>

The preview pane shows it immediately. You do not need to run the game to iterate on layout.

Replace the panel with a button and a status line, and bind both to data instead of hard-coding them:

<body data-model="menu">
<div class="panel">
<div class="button" data-event-click="Play()">Play</div>
<div class="status">{{ Status }}</div>
</div>
</body>

data-model names the view-model this subtree binds to, {{ Status }} interpolates a value from it, and data-event-click calls a method on it. This is data binding, and it is the way to drive UI in Lumina.

A script registers the view-model, then loads and shows the document.

using LuminaSharp;
using Lumina;
namespace GameScripts;
public sealed class MainMenu : EntityScript
{
[Property(Tooltip = "RML document shown on screen.")]
public string Document = "/Game/Content/UI/Menu.rml";
private sealed class MenuModel : ViewModel
{
private string _Status = "Click Play to begin.";
[Bind] public string Status { get => _Status; set => Set(ref _Status, value); }
[BindCommand] public void Play() => Status = "Starting...";
}
private MenuModel _Model = null!;
private UIDataModel? _Binding;
private UIDocument _Menu;
public override void OnReady()
{
_Model = new MenuModel();
_Binding = World.UI.AddModel("menu", _Model); // BEFORE LoadDocument
_Menu = World.UI.LoadDocument(Document);
_Menu.Show(); // documents load hidden
World.UI.EnableCursor(); // so the button is clickable
}
public override void OnDetach()
{
_Menu.Close();
_Binding?.Dispose();
World.UI.DisableCursor();
}
}

Add a C# Script component to any entity in the world, point it at Game.MainMenu, and press Play.

Documents are loaded by virtual path, the same paths the rest of the engine uses:

PathResolves to
/Game/Content/UI/Menu.rmlYour project’s content.
/Engine/Resources/Content/UI/...Engine content, including the bundled examples.
/MyPlugin/...A mounted plugin’s content.

Inside a document, href and src accept either an absolute virtual path or a path relative to the document’s own folder. Prefer absolute paths for anything shared between folders, and relative paths for a stylesheet that sits beside its document.

The engine ships working examples under Engine/Resources/Content/UI/Examples/, each with a matching script in Engine/Resources/Scripts/. Attach the script to an entity and press Play.

ExampleScriptShows
Menu.rmlLumina.Examples.MenuExampleCommands, computed values, toggling a class from a bound bool.
Composition/HudComposed.rmlLumina.Examples.HudExampleOne model driving three composed <template> widgets.
Composition/Settings.rmlLumina.Examples.SettingsExampleTwo-way form binding and a command with an argument.
Composition/Roster.rmlLumina.Examples.RosterExampleList binding with data-for.
Composition/Hud.rml(open in the editor)The same HUD with empty slots, to fill in with the composition designer.
  • RML Syntax, document structure, elements, templates, and the full data binding reference.
  • RCSS Styling, selectors, units, fonts, layout, and exactly which properties Lumina’s renderer supports.
  • The UI Editor, editing, previewing, and composing documents without leaving the editor.
  • World Widgets, putting a document on a surface in the level.
  • Driving UI from C#, the World.UI API: view-models, documents, elements, events, and cursor control.