How HUD Modification Works
In Minecraft Bedrock, the in-game overlay is defined inside a file named hud_screen.json located in the resource pack's ui folder. When you create RP/ui/hud_screen.json in your own resource pack, the game does not completely overwrite the vanilla file if you structure it carefully. Instead, Bedrock merges your top-level JSON keys with the underlying vanilla definitions.
Every JSON UI file declares a namespace at the top. For the HUD, this namespace must be "hud". Inside this file, the main visual tree is rooted in an element called root_panel. Rather than redefining root_panel from scratch—which would erase the hotbar, crosshair, and hearts—you use the modifications array to inject your custom controls into the existing hierarchy.
This modification system works like surgical patching. You define your own self-contained UI elements in your file, and then instruct the game to insert a reference to your element inside root_panel. Because you are only appending to the child list of the panel, vanilla elements and controls injected by other resource packs remain intact.
Injecting a Custom Control into root_panel
To add a new element to the screen, you first define the control at the top level of your hud_screen.json file, and then add a modification instruction to root_panel. The modification instruction requires an array_name (which is always "controls" when adding child elements), an operation such as "insert_back" or "insert_front", and a value array containing references to your new controls.
Below is a complete, minimal RP/ui/hud_screen.json file that creates a small panel in the top-left corner containing a static text label, and injects it into the back of the HUD's root panel.
| Operation | What it does | Notes |
|---|---|---|
| insert_back | Appends your control to the end of the controls array. | Renders after earlier controls; safest choice for general overlays. |
| insert_front | Inserts your control at the very beginning of the controls array. | Useful if you want your element to sit behind vanilla HUD components. |
| insert_after | Places your control immediately after a specific named vanilla control. | Requires a "control_name" field to identify the target sibling. |
| insert_before | Places your control immediately before a specific named vanilla control. | Requires a "control_name" field to identify the target sibling. |
{
"namespace": "hud",
"custom_watermark_label": {
"type": "label",
"text": "My Add-on v1.0",
"color": [ 1.0, 1.0, 1.0 ],
"shadow": true
},
"custom_corner_panel": {
"type": "panel",
"size": [ 100, 20 ],
"anchor_from": "top_left",
"anchor_to": "top_left",
"offset": [ 10, 10 ],
"controls": [
{
"[email protected]_watermark_label": {}
}
]
},
"root_panel": {
"modifications": [
{
"array_name": "controls",
"operation": "insert_back",
"value": [
{
"[email protected]_corner_panel": {}
}
]
}
]
}
}Positioning, Anchoring, and Layering
Every visual element on the HUD needs to know where to sit on screens of wildly different resolutions and aspect ratios. JSON UI handles this through anchor_from, anchor_to, size, and offset. The anchor_from property defines the origin point on the parent container (in this case, root_panel, which spans the entire safe screen area), while anchor_to defines the pivot point on your own control that snaps to that origin.
For both anchor properties, the valid values are "top_left", "top_middle", "top_right", "left_middle", "center", "right_middle", "bottom_left", "bottom_middle", and "bottom_right". Always set both anchor_from and anchor_to Together; omitting one often leads to your control drifting off-center when the player resizes their window.
Sizes can be written in pixels as a two-number array like [ 100, 20 ], as percentages of the parent container like [ "50%", "10%" ], or set to "100%c" to shrink-wrap around the size of the element's children. The layer property (an integer) controls depth sorting: higher numbers draw on top of lower numbers, which helps if your HUD element overlaps the vanilla hotbar or chat window.
{
"namespace": "hud",
"custom_badge_icon": {
"type": "image",
"texture": "textures/ui/WarningGlyph",
"size": [ 16, 16 ],
"anchor_from": "top_right",
"anchor_to": "top_right",
"offset": [ -8, 8 ],
"layer": 10
},
"root_panel": {
"modifications": [
{
"array_name": "controls",
"operation": "insert_back",
"value": [
{
"[email protected]_badge_icon": {}
}
]
}
]
}
}Driving HUD Elements from Behavior Packs
JSON UI is strictly a presentation layer and cannot directly read arbitrary variables or custom properties from your Behavior Pack scripts. To make a custom HUD element react to gameplay—such as showing a mana bar, a cooldown timer, or a custom region name—you must bridge the gap using an existing vanilla HUD data channel.
The most reliable bridge is the title and actionbar system. When a Behavior Pack sends a command like /title @s actionbar or calls player.onScreenDisplay.setActionBar() via the Script API, the vanilla HUD receives that string through a hardcoded data binding named #hud_title_text_string. By building a custom control that binds to #hud_title_text_string and uses string manipulation inside UI bindings, you can intercept messages that start with a specific prefix and turn them into custom HUD updates.
When using this pattern, creators typically attach a visibility binding to the vanilla actionbar control so it hides itself whenever the text begins with the custom prefix, while their injected custom HUD control becomes visible only when that same prefix is present. This allows regular actionbar messages to function normally while routing prefixed data strings straight to your custom interface.
{
"namespace": "hud",
"bound_actionbar_readout": {
"type": "label",
"text": "#text",
"anchor_from": "bottom_right",
"anchor_to": "bottom_right",
"offset": [ -12, -12 ],
"bindings": [
{
"binding_name": "#hud_title_text_string",
"binding_name_override": "#text",
"binding_type": "global"
}
]
}
}Keeping custom definitions in a separate file
If you build a complex HUD with dozens of panels, bars, and icons, cramming everything into RP/ui/hud_screen.json quickly becomes hard to read. You can create a dedicated file—for example, RP/ui/my_custom_hud.json—with its own custom namespace, and reference its controls from hud_screen.json.
Whenever you create a completely new JSON file in the RP/ui/ folder that does not match a vanilla filename, you must register its path inside RP/ui/_ui_defs.json. The game reads _ui_defs.json on pack load to know which files to compile into the UI system.
{
"ui_defs": [
"ui/my_custom_hud.json"
]
}Which versions this applies to
Applies to all modern Bedrock versions (1.16.0 through 1.21+). JSON UI is a core resource pack feature and does not require any experimental toggles to function.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Your custom HUD appears, but the hotbar, hearts, and crosshair completely vanish. | You defined a "controls" array directly inside "root_panel" in your hud_screen.json instead of using the "modifications" array. Redefining "controls" overwrites the vanilla array. | Remove the "controls" key from "root_panel" and wrap your injected elements inside a "modifications" block using "operation": "insert_back". |
| Content Log shows an error like "Can't find control 'my_custom_hud@...'" or crashes when loading into a world. | You placed your custom controls in a separate JSON file inside RP/ui/, but forgot to list that file path inside RP/ui/_ui_defs.json, or misspelled the namespace before the @ symbol. | Add the path (relative to the resource pack root, e.g., "ui/my_file.json") to the "ui_defs" array in RP/ui/_ui_defs.json and verify the namespace matches. |
| The game loads without errors, but the custom label or image is nowhere to be seen on screen. | Your offset pushed the element off-screen, or a parent panel has a size of [ 0, 0 ] with clipping enabled. Remember that positive X offsets move right and positive Y offsets move down; if anchored to bottom_right, positive offsets push the control outside the screen bounds. | Use negative X and Y offsets when anchoring to the bottom or right edges of the screen, or temporarily set "anchor_from": "center" and "offset": [ 0, 0 ] to confirm the element renders. |
| A custom image renders as a solid magenta and black checkerboard square on the HUD. | The "texture" path in your image control is either misspelled, includes a file extension like .png when it shouldn't, or points to a file that doesn't exist. | Ensure the texture path starts with "textures/..." relative to the RP root and omit the file extension (for example, "textures/ui/my_icon"). |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
