🤔 Unity, huh, how?

🤔

Styling elements in UI Toolkit

This guide describes how to create USS selectors to style complex elements.

USS is a query system similar to CSS that allows you to construct rules to constrain styling to specific elements.

Steps taken when styling an element

Additionally

Create your element

We actually need to see an element to see how to style it. Create an element using the UI Builder, UXML, or via code.

Avoid inline styles

Style overrides in the UI Builder or UXML's style property should be avoided.

Inline styles override all other styling and must be specified every time an element is created. These styles are generally only appropriate when setting dynamic behaviour in code.

Use BEM when choosing class names

Block Element Modifier (BEM) is a naming convention that is also used by UI Toolkit1. You can remain consistent with the built-in controls, and keep consistency with others by using it too.

Example

For our styling example we will be styling a Slider. The example will use one from the UI Toolkit Samples WindowUI ToolkitSamples.

UI Toolkit Samples - Slider

Learn USS selectors.

See USS selectors.

Learn to use the UI Toolkit Debugger.

See UI Toolkit Debugger

Construct a USS rule

Use selectors to target your element

In your USS, reconstruct the work you have done in the debugger.
Often, you should anchor the styling to the root of the control, using the type, its name, or class. Take note of the preexisting Matching Selectors already present on your element for inspiration.

UI Toolkit Debugger - Matching selectors

A preexisting selector that has been used by UI Toolkit for our element.

Use properties to style your selector's matches

Once you've created a selector, use USS properties to style the elements it matches. Learning USS properties and syntax is beyond the scope of this guide, but note it has great similarity with CSS if you ever get lost with a certain style.

information

The USS property data types page is a specification applying to the USS common properties page.
Apply the listed syntax rules to formulate a valid property.

Example

.unity-base-slider__drag-container {
  /* Make the parent "drag container" align its "dragger" child in the center of the cross-axis */
  justify-content: center;
}

.unity-base-slider--horizontal .unity-base-slider__tracker {
  /* The "tracker" background can stretch across the slider (left 0 to right 0), 
     and not contribute to layout by using absolute. */
  position: absolute;
  height: 10px;
  top: auto;
  left: 0;
  right: 0;
  /* Set the radius to match the "dragger" */
  border-radius: 5px;
}

.unity-base-slider--horizontal .unity-base-slider__dragger {
  /* Make the "dragger" taller and remove previous alignment now it's centered */
  margin-top: 0;
  width: 16px;
}

.unity-base-slider--horizontal .unity-base-slider__dragger-border {
  /* Adjust the "dragger-border" to accomodate the new size of the "dragger". It's already absolute. */
  height: 14px;
  width: 20px;
  margin-left: -2px;
}

Or, targeting the specific slider by name/* Targeting the specific slider by name: */
#the-uxml-slider .unity-base-slider__drag-container {
  /* Make the parent "drag container" align its "dragger" child in the center of the cross-axis */
  justify-content: center;
}

#the-uxml-slider.unity-base-slider--horizontal .unity-base-slider__tracker {
  /* The "tracker" background can stretch across the slider (left 0 to right 0), 
     and not contribute to layout by using absolute. */
  position: absolute;
  height: 10px;
  top: auto;
  left: 0;
  right: 0;
  /* Set the radius to match the "dragger" */
  border-radius: 5px;
}

#the-uxml-slider.unity-base-slider--horizontal .unity-base-slider__dragger {
  /* Make the "dragger" taller and remove previous alignment now it's centered */
  margin-top: 0;
  width: 16px;
}

#the-uxml-slider.unity-base-slider--horizontal .unity-base-slider__dragger-border {
  /* Adjust the "dragger-border" to accomodate the new size of the "dragger". It's already absolute. */
  height: 14px;
  width: 20px;
  margin-left: -2px;
}
Making a thicker slider via USS.

Thicker Slider

Thicker slider created by the above USS.

Styling notes

Take a peek at the C# source code

It's generally good practice to take a look at the Unity C# reference source code to see how something works. I keep a local copy to browse as I work.

In my example, I've been working with a Slider, searching for it in the UIElements namespace you can find the file ModulesUIElementsCoreControlsSlider.cs.

Taking a look at how it's constructed, it uses a base class to do most of the work, but we can see it adds a class to the element, and uses properties to create some nested elements, adding classes to them too.

/// <summary>
/// Creates a new instance of a Slider.
/// </summary>
/// <param name="label">The string representing the label that will appear beside the field.</param>
/// <param name="start">The minimum value that the slider encodes.</param>
/// <param name="end">The maximum value that the slider encodes.</param>
/// <param name="direction">The direction of the slider (Horizontal or Vertical).</param>
/// <param name="pageSize">A generic page size used to change the value when clicking in the slider.</param>
public Slider(string label, float start = 0, float end = kDefaultHighValue, SliderDirection direction = SliderDirection.Horizontal, float pageSize = kDefaultPageSize)
    : base(label, start, end, direction, pageSize)
{
    AddToClassList(ussClassName);
    labelElement.AddToClassList(labelUssClassName);
    visualInput.AddToClassList(inputUssClassName);
}
One of the constructors for a Slider

These classes are public and static, so if you're ever needing them in C# you can access them directly from the class!

/// <summary>
/// USS class name of elements of this type.
/// </summary>
public new static readonly string ussClassName = "unity-slider";
/// <summary>
/// USS class name of labels in elements of this type.
/// </summary>
public new static readonly string labelUssClassName = ussClassName + "__label";
/// <summary>
/// USS class name of input elements in elements of this type.
/// </summary>
public new static readonly string inputUssClassName = ussClassName + "__input";
The classes applied to Slider as specified in C#.

You can investigate further, look at the base class BaseSlider, follow along to find out what elements it creates and what classes it adds. As most of UI Toolkit is in C#, and doesn't rely on UnityEngine.Object, most of the work is done in constructors and should be very intuitive!

Inline styles

Inline styles are indicated in the debugger. As mentioned previously, inline styles cannot be overridden by USS. In these cases you will have to override the style in code, know you can poke around the source code to see why and how something has been done.

Complex styles

Sometimes you might find a style isn't one of the common USS properties, and is instead implemented by CustomStyleProperty.
A great example of this is CurveField, where the curve color is driven by a CustomStyleProperty<Color> called --unity-curve-color, your USS can use this property2.

  1. Best practices for USS.

  2. To override a custom style in code, find what member the custom style is written to (m_CurveColor in this case), and write to it in a CustomStyleResolvedEvent callback.