Skip to content

Your First Screen

ssbaxys edited this page Oct 26, 2026 · 1 revision

Build your first screen and learn what a UI is made of.

On this page: Your first screen · Core concepts · Example

Your first screen

import io.github.ssbaxys.lumina.forge.LuminaScreen;      // NeoForge: io.github.ssbaxys.lumina.neoforge.LuminaScreen
import io.github.ssbaxys.lumina.layout.Dim;
import io.github.ssbaxys.lumina.layout.StackLayout;
import io.github.ssbaxys.lumina.node.UiRoot;
import io.github.ssbaxys.lumina.reactive.Signal;
import io.github.ssbaxys.lumina.style.ModernTheme;
import io.github.ssbaxys.lumina.widget.*;

public final class HelloScreen {
    public static void open() {
        Signal<Integer> clicks = Signal.of(0);
        Signal<Boolean> music = Signal.of(true);
        Signal<Float> volume = Signal.of(60f);

        Minecraft.getInstance().setScreen(new LuminaScreen(Component.literal("Hello"), screen -> {
            Panel window = Panel.column().gap(10).width(Dim.px(260)).add(
                    new Label("Lumina UI").scale(1.5f),
                    Label.bound(clicks.map(c -> "Clicks: " + c)),
                    new Checkbox("Music", music),
                    new Slider(volume, 0, 100),
                    new Button("Press me", () -> clicks.update(c -> c + 1)),
                    new Button("Close", screen::onClose));
            // full-screen layer with the window in the middle
            Panel root = Panel.box(StackLayout.centered()).fill().add(window);
            return new UiRoot(root, ModernTheme.dark());
        }));
    }
}
  • LuminaScreen is a regular Minecraft Screen that hosts a UiRoot. All input goes to the UI, and whatever the UI does not handle (Esc, for example) falls through to the normal screen logic.
  • The factory receives the screen itself, handy for a Close button (screen::onClose).
  • .parent(previousScreen) sets the screen to return to on close; .pauseGame(true) pauses a singleplayer game.
  • The root's content is placed in the top-left corner at its own size. Call .fill() to stretch it over the whole screen, or wrap a window in Panel.box(StackLayout.centered()).fill() to center it, as above.

A small demo of the library itself opens with /lumina demo. The full showcase is the luminatest test mod (press K or run /luminatest); its sources are in testmod-common/.

Core concepts

Class Role
UiElement A tree node: children, layout, style, states (hover, pressed, focused, disabled, checked), events, animations.
Widget<S> Base of every widget: a type-safe fluent API (.width(...), .style(...), .on(...), .tooltip(...)) where each call returns the widget itself.
UiRoot The tree root: theme, animator, focus and Tab navigation, popup layer (menus, tooltips), accessibility, gamepad, inspector.
Theme Styles per widget class plus tokens (accent, surface, corner radius, animation speed).
Signal<T> An observable value: widgets bind to it and update themselves.
Canvas Drawing interface for your own widgets: rounded rectangles, shadows, gradients, text, textures, items, entities.

Common Widget methods:

new Button("OK")
        .id("ok")                          // later: root.content().findById("ok")
        .size(120, 20)                     // or .width(Dim.px(120)).height(Dim.percent(0.5f)), .fill()
        .minSize(80, 20).maxSize(200, 40)
        .grow(1).shrink(0)                 // share of free space in flex
        .margin(Insets.all(4))
        .alignSelf(Align.END)
        .absolute(10, 10)                  // out of layout flow, from the parent's top-left corner
        .styleClass("button primary")      // space-separated theme classes
        .style(s -> s.set(Styles.RADIUS, Radii.all(12)))
        .tooltip("Hint")
        .enabled(false).visible(true)
        .on(PointerEvent.ENTER, e -> {})
        .with(b -> b.setFocusable(true));  // any setup without breaking the chain

Example

Signal<Integer> clicks = Signal.of(0);

Panel window = Panel.column().gap(10).width(Dim.px(260)).add(
        new Label("Lumina UI").scale(1.5f),
        Label.bound(clicks.map(c -> "Clicks: " + c)),
        new Button("Click me", () -> clicks.update(c -> c + 1))
                .style(s -> s.set(Styles.RADIUS, Radii.all(12))));

Minecraft.getInstance().setScreen(new LuminaScreen(Component.literal("Example"),
        screen -> new UiRoot(window)));

The full feature showcase is the luminatest test mod (see README): the K key, /luminatest or the "Lumina" button in the menu. A small demo of the library itself: /lumina demo.


← Installation · English home · Layout →

Clone this wiki locally