Skip to main content
The UI system is built on three component types that work together:
  • ScreenPanel — root container that renders child PanelComponent objects as a screen-space HUD.
  • WorldPanel — root container that renders child PanelComponent objects into 3D world space.
  • PanelComponent — abstract base class for your own Razor UI components. Must be a descendant of a ScreenPanel or WorldPanel.

ScreenPanel

ScreenPanel renders any attached PanelComponent children to the screen. It acts as the root of your HUD hierarchy. The CameraComponent automatically composites all active ScreenPanel objects on top of the scene in ZIndex order.
ZIndex
int
default:"100"
Rendering order relative to other ScreenPanel instances. Higher values render on top.
Scale
float
default:"1.0"
Manual scale override for all child panels. Only takes effect when AutoScreenScale is false. Range 0–5.
AutoScreenScale
bool
default:"true"
When true, the panel scales automatically according to the ScaleStrategy. Overrides Scale.
ScaleStrategy
AutoScale
Automatic scaling mode. ConsistentHeight scales so that the panel always matches a 1080p baseline. FollowDesktopScaling uses the OS desktop scale. Only visible when AutoScreenScale is true.
Opacity
float
default:"1.0"
Overall opacity of the entire panel hierarchy. Range 0–1.
TargetCamera
CameraComponent
If set, this ScreenPanel is only rendered by the specified camera. Leave null to render on the main camera.

WorldPanel

WorldPanel renders child PanelComponent objects as a flat panel placed in 3D world space, like a name tag or in-world screen. It inherits from Renderer so it participates in the standard render pipeline.
PanelSize
Vector2
default:"512, 512"
The width and height of the panel surface in UI units.
RenderScale
float
default:"1.0"
A scale multiplier applied to the panel’s world-space size without changing its UI-space resolution.
LookAtCamera
bool
default:"false"
When true, the panel automatically rotates to face the main camera each frame — useful for billboard-style name tags.
HorizontalAlign
HAlignment
default:"Center"
Horizontal alignment of the panel surface relative to the GameObject’s origin. Options: Left, Center, Right.
VerticalAlign
VAlignment
default:"Center"
Vertical alignment of the panel surface relative to the GameObject’s origin. Options: Top, Center, Bottom.
InteractionRange
float
default:"1000"
Maximum distance at which a player can interact with this world panel using mouse input.

PanelComponent

PanelComponent is the abstract base class for all UI leaf components. You subclass it (usually paired with a .razor file) to define the panel’s markup and update logic.

Key members

Panel
Panel
The underlying Panel object. Can be null if the component is disabled or has not yet been enabled.

Methods

MethodDescription
BuildHash()Override to return a hash of the data that drives this panel. The framework calls this each frame; when the hash changes, the panel re-renders. Return 0 to always re-render.
StateHasChanged()Explicitly request a re-render on the next frame. Call this when you mutate state outside of BuildHash.
AddClass(string)Adds a CSS class to the underlying panel.
RemoveClass(string)Removes a CSS class from the underlying panel.
SetClass(string, bool)Adds or removes a CSS class based on a boolean.
HasClass(string)Returns true if the panel currently has the given class.
BindClass(string, Func<bool>)Binds a CSS class to a function evaluated each frame.

Lifecycle callbacks

MethodDescription
OnTreeFirstBuilt()Called once after the Razor tree is built for the first time.
OnTreeBuilt()Called every time the Razor tree is rebuilt.
Never cache Panel across frames without null-checking. The panel is destroyed when the component is disabled and recreated when it is re-enabled.

Example: HUD health bar

The example below shows a minimal ScreenPanel hierarchy with a custom PanelComponent that displays a health value. Scene hierarchy:
HudObject
  └── ScreenPanel        ← root, renders the HUD
  └── HealthBarPanel     ← your PanelComponent subclass
HealthBarPanel.cs:
using Sandbox;

public sealed class HealthBarPanel : PanelComponent
{
    [Property] public float Health { get; set; } = 100f;
    [Property] public float MaxHealth { get; set; } = 100f;

    // Returning a hash of Health causes a re-render only when it changes.
    protected override int BuildHash()
        => HashCode.Combine( Health, MaxHealth );
}
HealthBarPanel.razor:
@inherits PanelComponent

<root class="health-bar-root">
    <div class="health-bar" style="width: @(Health / MaxHealth * 100)%"></div>
    <label>@Health / @MaxHealth</label>
</root>
HealthBarPanel.scss:
.health-bar-root {
    position: absolute;
    bottom: 40px;
    left: 50%;
    transform: translateX(-50%);
    width: 300px;
    height: 20px;
    background: rgba(0, 0, 0, 0.5);

    .health-bar {
        height: 100%;
        background: green;
        transition: width 0.2s;
    }
}
Use BuildHash() rather than calling StateHasChanged() every frame. The hash comparison is cheap and avoids rebuilding the Razor tree unnecessarily.

Build docs developers (and LLMs) love