ElytraF5 & more · Code recipes
Complete examples

Integration recipes

These snippets show how to combine the API with a mod config, translations and a dedicated settings screen. Replace ExampleConfig and ExampleAdvancedScreen with your project’s own classes.

1 · Three options and a custom icon

A category containing a toggle, cycle choice, action button and custom icon.

private fun registerVisualSettings() {
    ElytraF5SettingsApi.registerCategory(
        SettingsCategory(
            id = "examplemod:visuals",
            title = Component.translatable("examplemod.settings.category.visuals"),
            icon = Identifier.fromNamespaceAndPath(
                "examplemod", "textures/gui/settings/visuals.png"
            ),
            options = listOf(
                BooleanSettingOption(
                    id = "particles",
                    title = Component.translatable("examplemod.settings.particles"),
                    description = Component.translatable("examplemod.settings.particles.description"),
                    currentValue = { ExampleConfig.particles },
                    onChanged = { value ->
                        ExampleConfig.particles = value
                        ExampleConfig.save()
                    }
                ),
                ChoiceSettingOption(
                    id = "quality",
                    title = Component.translatable("examplemod.settings.quality"),
                    description = Component.translatable("examplemod.settings.quality.description"),
                    choices = listOf(
                        SettingsChoice("low", Component.translatable("examplemod.quality.low")),
                        SettingsChoice("balanced", Component.translatable("examplemod.quality.balanced")),
                        SettingsChoice("high", Component.translatable("examplemod.quality.high"))
                    ),
                    currentChoice = { ExampleConfig.quality },
                    onChanged = { value ->
                        ExampleConfig.quality = value
                        ExampleConfig.save()
                    }
                ),
                ActionSettingOption(
                    id = "advanced",
                    title = Component.translatable("examplemod.settings.advanced"),
                    description = Component.translatable("examplemod.settings.advanced.description"),
                    buttonTitle = Component.translatable("examplemod.settings.open_advanced"),
                    action = { parent ->
                        Minecraft.getInstance().gui.setScreen(ExampleAdvancedScreen(parent))
                    }
                )
            )
        )
    )
}

Imports for this snippet: net.minecraft.network.chat.Component, net.minecraft.resources.Identifier, net.minecraft.client.Minecraft and net.turtle.ef5am.api.client.settings.*.

Java example: a boolean setting

The API is written in Kotlin. Java callbacks use Kotlin’s Function0 and Function1 interfaces. A change callback returns Unit.INSTANCE.

import java.util.List;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
import net.minecraft.network.chat.Component;
import net.turtle.ef5am.api.client.settings.*;

ElytraF5SettingsApi.registerCategory(new SettingsCategory(
    "examplemod:visuals",
    Component.translatable("examplemod.settings.category.visuals"),
    List.of(new BooleanSettingOption(
        "particles",
        Component.translatable("examplemod.settings.particles"),
        Component.translatable("examplemod.settings.particles.description"),
        (Function0<Boolean>) () -> ExampleConfig.isParticlesEnabled(),
        (Function1<Boolean, Unit>) value -> {
            ExampleConfig.setParticlesEnabled(value);
            ExampleConfig.save();
            return Unit.INSTANCE;
        }
    )),
    null
));

Compile with ElytraF5’s development jar and Kotlin libraries. The Kotlin API is most concise from Kotlin; Java integrations should verify generated signatures against the exact API version they target.

2 · Fabric client entrypoint

class ExampleModClient : ClientModInitializer {
    override fun onInitializeClient() {
        ExampleConfig.load()
        registerVisualSettings()
    }
}

Load your config before registration: ElytraF5 reads the value provider when the screen is built. Avoid disk I/O inside UI callbacks.

3 · Add translations

Create src/client/resources/assets/examplemod/lang/en_us.json and fr_fr.json (or the resource path configured by your project):

{
  "examplemod.settings.category.visuals": "Visuals",
  "examplemod.settings.particles": "Particles",
  "examplemod.settings.particles.description": "Shows custom particle effects.",
  "examplemod.settings.quality": "Quality",
  "examplemod.settings.quality.description": "Selects the detail level.",
  "examplemod.quality.low": "Low",
  "examplemod.quality.balanced": "Balanced",
  "examplemod.quality.high": "High",
  "examplemod.settings.advanced": "Advanced options",
  "examplemod.settings.advanced.description": "Opens advanced controls.",
  "examplemod.settings.open_advanced": "Configure"
}

Translation keys belong to your mod’s namespace. They are not added to ElytraF5’s language files.

4 · Child screen with a back path

class ExampleAdvancedScreen(private val parent: Screen) :
    Screen(Component.translatable("examplemod.settings.advanced.title")) {

    override fun onClose() {
        Minecraft.getInstance().gui.setScreen(parent)
    }
}

The supplied parent is the currently open ElytraF5 screen. Do not create a fresh settings screen when closing; restore the parent instance.

5 · Optional integration

If ElytraF5 is optional, declare it under suggests and check that it is loaded before running the integration bridge. Keep all ElytraF5 class references in a separate bridge class that is only called when the mod is present.

override fun onInitializeClient() {
    ExampleConfig.load()
    if (FabricLoader.getInstance().isModLoaded("ef5am")) {
        ExampleElytraF5Bridge.register()
    }
}

If the dependency is required, declare it under depends. Never import the client API into shared server code.

6 · Callback behavior

The API provides simple controls. It does not provide automatic persistence, sliders, nested categories or access to another mod’s settings.