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.
| Property | What it does | Notes |
|---|---|---|
| default_control | Name of the child control shown at rest | Defaults to 'default' in common.button |
| hover_control | Name of the child control shown on cursor hover | Also shown when focused via controller |
| pressed_control | Name of the child control shown while held down | Triggers sound and visual offset |
| locked_control | Name of the child control shown when disabled | Active when button bindings evaluate enabled to false |
| $pressed_button_name | The C++ engine event fired on release | Cannot invent custom events unless intercepted by Script API or server forms |
{
"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.
{
"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.
{
"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.
| Variable | What it does | Notes |
|---|---|---|
| $toggle_name | Identifier grouping toggles together | Must be identical across all tabs in a radio group |
| $toggle_view_binding_name | Internal name exposed to other controls | Referenced in source_control_name of view bindings |
| $radio_toggle_group | Enables mutually exclusive behavior | Boolean; prevents unchecking the active tab by clicking it again |
| $toggle_group_forced_index | Assigns an ID to each radio toggle | Integer starting at 0; required for radio groups to function |
| $toggle_default_state | Sets whether the toggle starts checked | Set to true on index 0 of a radio group |
{
"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 see | Why | The 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.
