# Your first menu

> Build a working mod menu with a toggle, a slider and a Done button, register it, and open it from the Options screen.

URL: https://xichil.dev/docs/xis-mvui/v1/getting-started/first-menu/ · Updated: 2026-11-06 · Applies to: Fabric, Quilt, Legacy Fabric, Forge, NeoForge, MC 1.7.10 - 26.3

In five steps you build a menu with a toggle, a slider and a Done button, and open it from the game's
Options screen.

## Prerequisites

- The API on your compile class path and the `xis_mvui` dependency declared ([Installation](https://xichil.dev/docs/xis-mvui/v1/getting-started/installation/index.md)).

## Steps

### 1. Keep the values in your own class

The library never stores your settings. Widgets read them from your code and write changes back, so first
make a place for the values:

```java title="MyConfig.java"
package com.example.mymod;

public final class MyConfig {
    public boolean sounds = true;
    public int volume = 80;

    public void save() {
        // Write the values to disk; see the "Save and load settings" guide.
    }
}
```

### 2. Describe the menu

A menu is a [ScreenFactory](https://xichil.dev/javadoc/xis-mvui/v1/dev/xichil/mvui/ScreenFactory.html). Its `create` method builds the first
screen each time the player opens the menu:

```java title="MyMenu.java"
package com.example.mymod;

import dev.xichil.mvui.Button;
import dev.xichil.mvui.MenuScreen;
import dev.xichil.mvui.Screen;
import dev.xichil.mvui.ScreenFactory;
import dev.xichil.mvui.Slider;
import dev.xichil.mvui.Text;
import dev.xichil.mvui.Toggle;
import dev.xichil.mvui.UiHost;
import dev.xichil.mvui.UiTexts;

public final class MyMenu implements ScreenFactory {
    private final MyConfig config;

    public MyMenu(MyConfig config) {
        this.config = config;
    }

    @Override
    public Text buttonLabel() {
        return Text.translatable("mymod.menu", "My Mod...");   // the Options screen button
    }

    @Override
    public Screen create(UiHost host) {
        return MenuScreen.builder("main")
                .title(Text.translatable("mymod.title", "My Mod"))
                .add(Toggle.builder("sounds")
                        .label(Text.translatable("mymod.sounds", "Sounds"))
                        .value(() -> config.sounds)          // read on every refresh
                        .onChange(v -> config.sounds = v)    // you store the new value
                        .build())
                .add(Slider.builder("volume")
                        .label(Text.translatable("mymod.volume", "Volume"))
                        .range(0, 100).step(5)
                        .value(() -> config.volume)
                        .onChange(v -> config.volume = (int) v)
                        .enabled(() -> config.sounds)       // greyed out while sounds are off
                        .build())
                .footer(Button.builder("done")
                        .label(UiTexts.DONE)
                        .onPress(() -> {
                            config.save();
                            host.back();                    // close the menu
                        })
                        .build())
                .build();
    }
}
```

What each part does:

- `MenuScreen.builder("main")` starts a screen with the id `main`. Ids are never shown; tests use them.
- `.title(…)` is the text at the top. `Text.translatable(key, fallback)` shows the translation of `key` if your
  lang file has one, or the English fallback if not, so the menu works before you write any lang file.
- `.add(…)` appends a row to the scrolling list. `.footer(…)` adds a button to the fixed row at the bottom.
- `value(() -> …)` and `onChange(v -> …)` connect the widget to your field. After every click the screen
  re-reads every supplier, so the slider greys out as soon as the toggle turns sounds off.
- `host.back()` closes the screen and returns to wherever the player opened the menu from.

### 3. Register it

Call [MultiversionUi.register](https://xichil.dev/javadoc/xis-mvui/v1/dev/xichil/mvui/MultiversionUi.html) once, on the client, while the game
starts:

```java title="MyModClient.java"
MultiversionUi.register("mymod", Text.translatable("mymod.name", "My Mod"), new MyMenu(config));
```

The first argument is your mod id exactly as in your mod metadata. The second is your mod's name as the
"Mod menus" list shows it when several mods have menus.

Where the call goes depends on the loader:

#### Fabric, Quilt, Legacy Fabric

In your `client` entrypoint:

```java title="MyModClient.java"
package com.example.mymod;

import dev.xichil.mvui.MultiversionUi;
import dev.xichil.mvui.Text;
import net.fabricmc.api.ClientModInitializer;

public final class MyModClient implements ClientModInitializer {
    @Override
    public void onInitializeClient() {
        MultiversionUi.register("mymod", Text.translatable("mymod.name", "My Mod"), new MyMenu(new MyConfig()));
    }
}
```

```json title="fabric.mod.json"
{
  "entrypoints": {
    "client": ["com.example.mymod.MyModClient"]
  }
}
```

#### Forge, NeoForge

In your `@Mod` class's constructor. On Forge and NeoForge 1.13.2 and newer this also makes the menu your mod's
Config screen in the mod list, because Forge attributes it to the mod whose constructor is running.

On Forge 1.7.10 to 1.12.2, also set `guiFactory` in the annotation to get the mod list's Config button:
`MultiversionUi.CPW_FORGE_GUI_FACTORY` on 1.7.10 and `MultiversionUi.LEGACY_FORGE_GUI_FACTORY` on 1.8 to 1.12.2.
[Register your menu](https://xichil.dev/docs/xis-mvui/v1/guides/registering/index.md) has the code for each era, and how to keep the call off
dedicated servers.

### 4. Open it in the game

Start the client and open **Options**. There is a new button labelled with your `buttonLabel()`,
"My Mod...". When more than one mod registers a menu, the button reads **Mod menus...** and opens a list with
one button per mod instead.

### 5. Add translations (optional)

Add your keys to your mod's lang file. From 1.13 that is `assets/mymod/lang/en_us.json`:

```json title="assets/mymod/lang/en_us.json"
{
  "mymod.menu": "My Mod...",
  "mymod.name": "My Mod",
  "mymod.title": "My Mod",
  "mymod.sounds": "Sounds",
  "mymod.volume": "Volume"
}
```

`UiTexts.DONE` uses vanilla's own `gui.done` key, so it is translated already. See
[Translate your menu](https://xichil.dev/docs/xis-mvui/v1/guides/translations/index.md) for 1.12.2 and older.

## Next steps

- [State and refresh](https://xichil.dev/docs/xis-mvui/v1/concepts/state-and-refresh/index.md): the one idea that explains how every widget behaves.
- [Which widget to use](https://xichil.dev/docs/xis-mvui/v1/widgets/index.md): all sixteen widgets at a glance.
- [Save and load settings](https://xichil.dev/docs/xis-mvui/v1/guides/saving-config/index.md): write `MyConfig` to disk.
- [A settings menu](https://xichil.dev/docs/xis-mvui/v1/examples/settings-menu/index.md): a bigger, complete example.
