Wiki 9Minecraft Wiki 9Minecraft

JSON UI Buttons and Toggles

Create interactive buttons, checkboxes, and radio tabs in Bedrock resource packs using JSON UI.

5Sections
4Code examples
3Known errors

Buttons and toggles are the primary interactive elements in Minecraft Bedrock's JSON UI system. You reach for buttons when you need to trigger a hardcoded game action or open a screen, and you use toggles when you need to maintain a persistent on/off state or switch between custom UI tabs purely within the resource pack.

How Interactive Controls Work

In Bedrock's JSON UI, an interactive element is not a single element. Instead, it is a compound control made of a parent element that handles input logic and several child elements that represent visual states. The game engine looks at the control names mapped inside the parent to decide which child to render at any given moment.

For both buttons and toggles, you rarely build the state logic from scratch. Minecraft's vanilla resource pack provides base templates inside the common and common_toggles namespaces. Inheriting from these templates handles sound effects, controller focus, and touch input automatically, leaving you to customize the textures, labels, and property bindings.

Building a Button Control

A raw button uses the button type and requires four state controls: default, hover, pressed, and locked. When the player interacts with the button, the UI engine hides the inactive state controls and only draws the one matching the current interaction state. You assign these by matching the child control names to the corresponding state properties on the parent button.

In practice, you should almost always inherit from common.button. With common.button, the default, hover, and pressed visuals are already wired up to standard stone-style button textures. You only need to define the $pressed_button_name variable to tell the game which hardcoded engine event to fire when clicked, and pass a label or custom graphic into the button_content child.

PropertyWhat it doesNotes
default_controlName of the child control shown at restDefaults to 'default' in common.button
hover_controlName of the child control shown on cursor hoverAlso shown when focused via controller
pressed_controlName of the child control shown while held downTriggers sound and visual offset
locked_controlName of the child control shown when disabledActive when button bindings evaluate enabled to false
$pressed_button_nameThe C++ engine event fired on releaseCannot invent custom events unless intercepted by Script API or server forms
RP/ui/pause_screen.json
{
  "namespace": "pause",

  "[email protected]": {
    "size": [ 120, 30 ],
    "$pressed_button_name": "button.menu_continue",
    "controls": [
      {
        "[email protected]_state_default": {}
      },
      {
        "[email protected]_state_hover": {}
      },
      {
        "[email protected]_state_pressed": {}
      },
      {
        "button_label": {
          "type": "label",
          "text": "Resume Game",
          "layer": 2
        }
      }
    ]
  }
}

Button Mappings and Input

Under the hood, a button knows how to respond to clicks because of its button_mappings array. A mapping connects a hardware input (like a mouse click or controller A button) to a specific UI event. While common.button sets this up for you using the $pressed_button_name variable, writing your own button mappings is useful if you want a button to react to right-clicks or specific keyboard keys.

Most mappings use the from_button_id and to_button_id fields alongside a mapping_type. A mapping_type of pressed means the action only triggers when the cursor is directly over the control. A mapping_type of global allows the button to listen for a key press anywhere on the screen, which is how the Escape key closes menus without requiring you to click the back button.

RP/ui/custom_mappings_example.json
{
  "namespace": "custom_ui",

  "raw_mapped_button": {
    "type": "button",
    "size": [ 50, 50 ],
    "default_control": "default",
    "hover_control": "hover",
    "pressed_control": "pressed",
    "button_mappings": [
      {
        "from_button_id": "button.menu_select",
        "to_button_id": "button.menu_exit",
        "mapping_type": "pressed"
      }
    ],
    "controls": [
      {
        "default": {
          "type": "image",
          "texture": "textures/ui/Black"
        }
      },
      {
        "hover": {
          "type": "image",
          "texture": "textures/ui/White"
        }
      },
      {
        "pressed": {
          "type": "image",
          "texture": "textures/ui/Gray"
        }
      }
    ]
  }
}

Building a Toggle Control

Unlike buttons, which rely on the C++ engine to do something when clicked, toggles maintain their own state entirely within the UI layer. A toggle control has six visual states instead of four: unchecked, checked, unchecked_hover, checked_hover, unchecked_locked, and checked_locked. The base template to inherit from is common_toggles.light_text_toggle or common_toggles.light_content_toggle.

Because you can read a toggle's state from other controls on the same screen using bindings, toggles are the primary tool for showing and hiding custom UI panels. To link a panel's visibility to a toggle, give the toggle a unique $toggle_name and bind the target panel's #visible property to the toggle's #toggle_state using a view binding.

RP/ui/hud_screen.json
{
  "namespace": "hud",

  "info_panel_toggle@common_toggles.light_text_toggle": {
    "size": [ 80, 20 ],
    "$toggle_name": "custom_info_toggle",
    "$button_text": "Show Info",
    "$toggle_view_binding_name": "info_toggle_state"
  },

  "secret_info_panel": {
    "type": "panel",
    "size": [ 100, 100 ],
    "bindings": [
      {
        "binding_type": "view",
        "source_control_name": "info_toggle_state",
        "source_property_name": "#toggle_state",
        "target_property_name": "#visible"
      }
    ],
    "controls": [
      {
        "info_text": {
          "type": "label",
          "text": "Toggled panel content!"
        }
      }
    ]
  }
}

Radio Toggles and Tab Navigation

Toggles can also work together in mutually exclusive groups, behaving like radio buttons or navigation tabs. When one toggle in a radio group is clicked, all other toggles sharing the same group automatically switch to their unchecked state. This is how vanilla settings screens switch between Video, Audio, and Controller sections.

To turn standard toggles into a radio group, set $radio_toggle_group to true on each toggle, ensure they all share the exact same $toggle_name, and assign each one a unique integer in $toggle_group_forced_index. You can also set $toggle_default_state to true on the first tab so that one panel is open by default when the screen loads.

VariableWhat it doesNotes
$toggle_nameIdentifier grouping toggles togetherMust be identical across all tabs in a radio group
$toggle_view_binding_nameInternal name exposed to other controlsReferenced in source_control_name of view bindings
$radio_toggle_groupEnables mutually exclusive behaviorBoolean; prevents unchecking the active tab by clicking it again
$toggle_group_forced_indexAssigns an ID to each radio toggleInteger starting at 0; required for radio groups to function
$toggle_default_stateSets whether the toggle starts checkedSet to true on index 0 of a radio group
RP/ui/custom_tabs.json
{
  "namespace": "custom_tabs",

  "tab_template@common_toggles.light_text_toggle": {
    "size": [ 60, 24 ],
    "$radio_toggle_group": true,
    "$toggle_name": "my_custom_tabs"
  },

  "tab_one@custom_tabs.tab_template": {
    "$button_text": "Tab 1",
    "$toggle_group_forced_index": 0,
    "$toggle_default_state": true,
    "$toggle_view_binding_name": "tab_one_view"
  },

  "tab_two@custom_tabs.tab_template": {
    "$button_text": "Tab 2",
    "$toggle_group_forced_index": 1,
    "$toggle_view_binding_name": "tab_two_view"
  }
}

Which versions this applies to

Applies to all modern Minecraft Bedrock versions (1.16.0 and later). JSON UI is a stable resource pack feature and does not require any experimental toggles.

When it does not work

What you seeWhyThe fix
A custom panel bound to a toggle never appears on screen when the toggle is clicked.The source_control_name in your panel's binding is pointing to the toggle's element name instead of the internal toggle control name.Define $toggle_view_binding_name inside your toggle element, and use that exact string as the source_control_name in your target panel's view binding.
Button displays as a blank area, or the text disappears as soon as you hover over it.When overriding the controls array of common.button directly, you wiped out the default, hover, and pressed state definitions provided by the template.Instead of replacing the controls array on common.button, pass your custom child control using the $button_content variable, or explicitly re-include the default, hover, and pressed state controls in your array.
Content Log displays error: [UI][error] Can't find control 'common_toggles.light_text_toggle'Unedited vanilla UI screens only load namespaces they actually use. If you add a toggle to a minimal screen like hud_screen.json, the common_toggles namespace might not be loaded in memory.Ensure your UI file or _ui_defs.json loads the required vanilla templates, or reference the full template path if modifying a screen that does not import common_toggles by default.

Reference, straight from the game files

Read out of the game itself, so the ids match the version you are building for.