How to create simple UI in s&box

The UI system in s&box is powerful, and easy to use. This guide gives you an introduction to using it.

How to create simple UI in s&box
UI for "Boomer", a s&box game by Facepunch.

s&box is praised for having an easy-to-use and versatile UI system. A combination of HTML, SCSS, and C# make it easy to create and style user interfaces. This guide will give an introduction to creating a simple UI in s&box.

RootPanel

RootPanel is your introduction to your user interface. It should act as the parent to all other panels. You can create a RootPanel by simply deriving the RootPanel class in your C# class, as seen below.

using Sandbox.UI;

namespace ExampleUI;

public class MyHud : RootPanel 
{
    // fill me up!
}
MyHud.cs

Initializing this class will create the RootPanel.

using Sandbox;

namespace ExampleUI;

public class MyGame : Game 
{
    public MyGame() 
    {
        if ( IsClient ) 
        {
            _ = new MyHud();
        }
    }
}
MyGame.cs

In the above example, we ensure we are only creating MyHud on the client. This means MyHud and all children are client-side only. You can reference the client and pawn using Local.Client and Local.Pawn.

Element Flow

When you create multiple elements in s&box, how is the order determined in how they are displayed? The answer is simple: flexbox.

Flexbox is module for CSS that provides a simple way to organize layouts. Every single panel in s&box follows default flex rules. Certain properties allow you to control how flex displays its children, such as flex-direction, flex-grow, and flex-wrap.

One popular method of learning flexbox we recommend before diving too deep into s&box UI is flexbox froggy.

Panel Inspector

You can see the panel we just created by using the Panel Inspector view in the s&box editor.

Ensure you have the Panels view open in the editor.

In the Panels view, click the dropdown and select ExampleUI.MyHud.

Once selected, you can click on MyHud and see the outline for our RootPanel. You should see a white box outlining your game screen. This view will become more useful as we add more panels to our game.

Now that we have created a RootPanel, we should probably put some panels in it.

Static UI (HTML)

Using HTML templates is the easiest way to create UI quickly. It has lots of advantages:

  • Hotloading works fantastically
  • Easy to populate complex layouts quickly
  • Less verbose than its C# equivalent

To use a template, you can simply add the [UseTemplate] attribute to your C# class, and then create a .html file of the same name. Using our example from above, we would modify the code slightly:

using Sandbox.UI;

namespace ExampleUI;

[UseTemplate]
public class MyHud : RootPanel 
{
    // fill me up!
}
MyHud.cs

Then, create a file named MyHud.html:

<!-- A template always needs root-level <div> tags to represent its panel. -->
<div>
    <label class="title-label">My Sample Game</label>
</div>
MyHud.html
Look at the top-left corner of this image... if you want.

If you start or restart your game, you should now see text in the top-left corner of your screen that says "My Sample Game". But it doesn't look very good yet. We are going to add style to it using scss.

If you can't find your html and scss files, check the dropdown under MyHud.cs.

Styling

Often, developers name their stylesheets the same as their panels and html templates, but it's not a requirement. For the sake of simplicity, we are going to create a .scss file called MyHud.scss:

MyHud {
  .title-label {
    font-size: 72px;
    color: white;
    text-stroke: 4px black;
  }
}
MyHud.scss

Next, we have to link this stylesheet to our template. The easiest way to do this is in our template. Add this line to the top of MyHud.html:

<link rel="stylesheet" href="MyHud.scss" />
MyHud.html

If you already have your game open, adding the <link> and saving the .html file should hotload the style for you. The "My Sample Game" text should now be huge!

My Sample Game is now much easier to read!

Dynamic UI (C#)

Using C# to create our UI is also a great option. Instead of using a template, let's start with a barebones MyHud.cs again. This time, though, we will start filling up the class.

using Sandbox.UI;
using Sandbox.UI.Construct;

namespace ExampleUI;

public class MyHud : RootPanel 
{
    public Label TitleLabel { get; set; }

    public MyHud()
    {
        StyleSheet.Load( "MyHud.scss" );
    
        TitleLabel = Add.Label( "My Sample Game", "title-label" );
    }
}
MyHud.cs

We can load a stylesheet manually, or we can set the styles of the components manually. While this is slower, more verbose, and often not recommended, it can be useful if you need to update a style based your game's state or other conditions.

TitleLabel = Add.Label( "My Sample Game", "title-label" );
TitleLabel.Style.FontColor = Color.Red;
TitleLabel.Style.FontSize = 72;

Hotloading

You may notice that changing these styles is not hotloading like it might when changing our .html and .scss files. Since MyHud is created once when the game starts, it won't run the constructor for MyHud again, thus requiring a restart to see the changes.

You can override the OnHotloaded() method to ensure something is run after a hotload, but keep in mind anything you do in this method will never actually be called when your game is played.

Tick

Every UI element created in C# can override the Tick() method to constantly check certain conditions based on your game's state. Consider the following example:

using Sandbox;
using Sandbox.UI;
using Sandbox.UI.Construct;

namespace ExampleUI;

public class MyHud : RootPanel 
{
    public Label TitleLabel { get; set; }

    public MyHud()
    {
        StyleSheet.Load( "MyHud.scss" );
    
        TitleLabel = Add.Label( "My Sample Game", "title-label" );
    }
    
    public override void Tick()
    {
        var client = Local.Client;
        if ( client is null ) {
             return;
        }
        
        // Set the TitleLabel's text to the client's name.
        TitleLabel.Text = client.Name;
    }
}
MyHud.cs

Since we created the UI client-side, we can use Local.Client to check the client running the code. We then set the label's text to the client's name.

My username is Trundler.

While setting a label to our client's name is a very simple example, you can see how useful this pattern could be. You could update a client's score based on a networked variable in your game class, populate a scoreboard using a list of all players.

Hybrid

Hybrid UI is the ideal situation for most use cases. It uses a combination of templates, C#, and stylesheets to create a well-oiled user interface machine.

Start with a template, and for any elements that need to be changed or updated, you can bind that element to a property in your C# class.

<link rel="stylesheet" href="MyHud.scss" />

<div>
    <label @ref="TitleLabel" class="title-label">My Sample Game</label>
</div>
The @ref attribute allows you to bind between your code and template.
using Sandbox;
using Sandbox.UI;
using Sandbox.UI.Construct;

namespace ExampleUI;

public class MyHud : RootPanel 
{
    public Label TitleLabel { get; set; }

    public MyHud()
    {
        // we don't need to init TitleLabel anymore, as it's created in the template
    }
    
    public override void Tick()
    {
        var client = Local.Client;
        if ( client is null ) {
             return;
        }
        
        // Set the TitleLabel's text to the client's name.
        TitleLabel.Text = client.Name;
    }
}
MyHud.cs

Check out the s&box wiki page on UI Layout Binding for more information. You can bind individual property, call events, and more with the various binding functionality s&box provides!

HudEntity

One last thing to cover is HudEntity. It is an entity that wraps your RootPanel (or any panel). You can reference it for use in RPCs, but it hasn't found much proper use in s&box development yet. Since it is an entity, you should initialize slightly differently. Check out the code below for an example:

using Sandbox;

namespace ExampleUI;

public class MyGame : Game
{
    public MyHud MyHud { get; set; }
    
    public MyGame()
    {
        // Instantiate server-side instead, since it's an entity.
        if ( IsServer )
        {
            MyHud = new MyHud();
        }
    }
}
MyGame.cs
using Sandbox.UI;

namespace ExampleUI;

public class MyHud : HudEntity<RootPanel>
{
    public MyHud()
    {
        if ( !IsClient )
            return;
            
        // do something
    }
}
MyHud.cs

Conclusion

Creating user interfaces in s&box is fast, easy, and fun! After following this tutorial, you should have everything you need to start writing basic user interfaces in s&box.