Setup
Add the TabbyLib maven repository and the dependency for your loader to your build.gradle. Pick your Minecraft version and the code adapts.
repositories {
maven { url = "https://avie.cc/maven" }
}
dependencies {
// Fabric (on 1.21.1: modImplementation)
implementation "me.avie29.tabbylib:tabbylib:1.0.0+26.3"
// NeoForge
implementation "me.avie29.tabbylib:tabbylib-neoforge:1.0.0+26.3"
// Forge (on 1.20.1: implementation fg.deobf("me.avie29.tabbylib:tabbylib-forge:1.0.0+1.20.1"))
implementation "me.avie29.tabbylib:tabbylib-forge:1.0.0+26.3"
}
Also add TabbyLib as a dependency of your mod, so players get a message when it is missing:
// fabric.mod.json
"depends": {
"tabbylib": ">=1.0.0"
}
# neoforge.mods.toml (NeoForge) / mods.toml (Forge: mandatory=true instead of type)
[[dependencies.my_mod]]
modId = "tabbylib"
type = "required"
versionRange = "[1.0.0,)"
ordering = "AFTER"
side = "CLIENT"
Your first config
Every setting is an Option object. Create them as constants and put them into a
TabbyConfig. build() registers the config with TabbyLib and loads the file
config/<modid>.json – it is created when it does not exist yet.
public final class MyConfig {
public static final BooleanOption SHOW_HUD = BooleanOption.builder("showHud", true).build();
public static final IntOption SIZE = IntOption.builder("size", 10).slider(1, 50, 1).build();
public static final ColorOption COLOR = ColorOption.builder("color", 0xFFFFFFFF)
.alpha()
.dependsOn(SHOW_HUD)
.build();
private static TabbyConfig config;
// Call this in your client initializer (Fabric) / mod constructor (NeoForge, Forge)
public static void init() {
config = TabbyConfig.builder("my-mod")
.category("general", category -> category
.add(SHOW_HUD)
.group("look", group -> group.add(SIZE, COLOR)))
.build();
}
public static TabbyConfig get() {
return config;
}
}
Read values with get(). When you change a value in code, save the config afterwards:
if (MyConfig.SHOW_HUD.get()) {
drawHud(MyConfig.SIZE.get(), MyConfig.COLOR.get());
}
MyConfig.SHOW_HUD.set(false);
MyConfig.get().save();
getPending()).
Only "Save" moves it into get(). Use getPending() for previews that should react right away.
Option types
| Option | In the screen | Example |
|---|---|---|
BooleanOption |
On / off button | BooleanOption.builder("enabled", true) |
IntOption |
Slider (slider) or text field (range) |
IntOption.builder("size", 10).slider(1, 50, 1) |
DoubleOption |
Slider or text field for decimals | DoubleOption.builder("scale", 1.0).slider(0.5, 3.0, 0.25) |
StringOption |
Text field, optionally validated | StringOption.builder("prefix", "Day").maxLength(32) |
EnumOption |
Button that cycles through the values (right click goes back) | EnumOption.builder("mode", Mode.SIMPLE) |
ColorOption |
Hex field and color picker, with alpha() also transparency |
ColorOption.builder("color", 0xFFFF5555) |
KeyBindOption |
Key binding, in sync with Minecraft's controls | KeyBindOption.builder("toggleKey", myKeyMapping) |
StringListOption |
List with add, remove and reorder | StringListOption.builder("players", List.of()) |
HudPositionOption |
Drag and drop editor with snapping | see HUD |
Every builder shares these extra settings:
BooleanOption.builder("glow", false)
.dependsOn(SHOW_HUD) // greyed out while SHOW_HUD is off
.enabledWhen(() -> SIZE.getPending() > 5) // or any other condition
.visibleWhen(() -> MODE.getPending() == Mode.ADVANCED) // hidden instead of greyed out
.requiresRestart() // shows a hint and a message after saving
.onChange(value -> reloadRenderer()) // runs after the value was saved
.binding(() -> Settings.glow, value -> Settings.glow = value) // use an existing field
.name(Component.literal("Glow")) // instead of the translation key
.tooltip(Component.literal("Makes it shine"))
.hidden() // saved, but not shown in the screen
.build();
Categories, groups, texts and buttons
Every category becomes a tab. Groups are collapsible sections inside a
category. You can also add lines of text (label) and buttons that run code (ActionEntry).
TabbyConfig.builder("my-mod")
.category("general", category -> category
.label(Component.translatable("config.my-mod.general.intro"))
.add(SHOW_HUD, SIZE)
.group("colors", group -> group
.description(Component.literal("Everything about colors"))
.collapsed() // closed when the screen opens
.add(COLOR, BACKGROUND)))
.category("advanced", category -> category
.add(DEBUG)
.add(ActionEntry.of(
Component.literal("Cache"),
Component.literal("Clear"),
() -> MyMod.clearCache())))
.build();
Translations
TabbyLib looks up all names in your mod's language files (assets/<modid>/lang/en_us.json). Tooltips and enum names are optional.
{
"config.my-mod.showHud": "Show HUD",
"config.my-mod.showHud.tooltip": "Shows or hides the whole HUD.",
"config.my-mod.mode.simple": "Simple",
"config.my-mod.mode.advanced": "Advanced",
"config.my-mod.category.general": "General",
"config.my-mod.group.colors": "Colors"
}
my_mod there but
my-mod on Fabric, set .translationId("my-mod") – then all loaders share the same language files.
HUD elements
A HudPositionOption stores where a HUD element is. The position is attached to one of nine
anchors of the screen and stays there when the window size or GUI scale changes. In the screen it opens an
editor where players drag the element around – with snapping to the edges, the center and a grid.
public static final HudPositionOption POSITION = HudPositionOption.builder("position",
HudPosition.of(HudPosition.CENTER, HudPosition.END, 0, -50), // bottom center, 50 px above the bottom
MyHud::preview).build();
// Drawing the HUD
if (!TabbyLibApi.isHudEditorOpen()) { // the editor draws its own preview
HudPosition position = MyConfig.POSITION.get();
int x = position.x(screenWidth, width);
int y = position.y(screenHeight, height);
draw(graphics, x, y);
}
// Preview for the editor (uses the unsaved values)
public static HudPreview preview() {
return new HudPreview() {
public int width() { return currentWidth(); }
public int height() { return currentHeight(); }
public void render(GuiGraphicsExtractor graphics, int x, int y) { draw(graphics, x, y); }
};
}
// Open the editor directly, e.g. from a command
TabbyLibApi.openHudEditor(MyConfig.POSITION);
On 1.21.1 and 1.20.1 the graphics class is called GuiGraphics instead of GuiGraphicsExtractor.
Advanced
Opening the config screen
TabbyLibApi.openScreen("my-mod"); // e.g. from your own command
Screen screen = TabbyLibApi.createScreen(parent, "my-mod");
// Mod Menu (Fabric)
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> TabbyLibApi.createScreen(parent, "my-mod");
}
On NeoForge and Forge, TabbyLib connects the "Config" button of the mod list to your config automatically.
File name, display and saving
TabbyConfig.builder("my-mod")
.fileName("mymod") // keeps an existing config/mymod.json
.name(Component.literal("My Mod")) // instead of the name from the mod metadata
.icon(Identifier.fromNamespaceAndPath("my-mod", "textures/gui/icon.png"))
.onSave(() -> MyMod.reload()) // runs after the file was written
...
Migrating old config files
If you used your own config file before, you can convert it while loading. Return true and the file is written again afterwards.
.migration(json -> {
if (!json.has("oldName")) {
return false;
}
json.add("newName", json.remove("oldName"));
return true;
})
Available versions
| Minecraft | Fabric | NeoForge | Forge |
|---|---|---|---|
| 26.1 · 26.1.1 · 26.1.2 · 26.2 · 26.3 | tabbylib | tabbylib-neoforge | tabbylib-forge |
| 1.21.1 | tabbylib | tabbylib-neoforge | tabbylib-forge |
| 1.20.1 | – | – | tabbylib-forge |
The version is always 1.0.0+<Minecraft version>, for example me.avie29.tabbylib:tabbylib-neoforge:1.0.0+1.21.1. All versions have the same features and API.