Technology · Unity

USS is not CSS,
and the gaps are the things
you reach for first.

No gradients. No arcs. No masks. No keyframes. No positional selectors. What is actually missing from Unity UI Toolkit, what to use instead, and the rules that parse cleanly and style nothing at all.

By Marcin Firmuga·2026-08-11·updated 2026-09-01·9 min read·Technology

Every example in this guide came out of one project: a dense simulation game with roughly twenty screens, all of them built in C# against a single USS file, shipped as a public build.

I am writing it because the official documentation does not have a concept guide, and because most UI Toolkit questions I searched for while building had no accepted answer. What follows is not a tutorial. It is the list of things that cost me an evening each.

Version note. Everything here was observed on Unity 6000.5 with runtime UI Toolkit. Unity is still adding to USS, so treat this as a snapshot with a date on it rather than a permanent list.

What USS does not have

The pattern is consistent: USS covers layout and colour well, and stops at anything that paints a shape or reacts to position in a list.

What you wantStatusWhat to use instead
linear-gradient() Not available Bake it into a 256×1 texture and stretch it as a background image, or paint it with Painter2D
Arcs, rings, circles Not available Painter2D in generateVisualContent. A gradient ring means stroking it in short segments, because Painter2D strokes in one colour
clip-path, masks Not available Bake the shape into the alpha channel of the image itself
@keyframes Not available transition for simple cases, or swap classes on a schedule from C#
:last-child, :first-child, :nth-child Not available Negative margin on the container, or clear the last child's margin from C#
Grid layout, gap Not available Flexbox with margins on the children

The first four are inconvenient and obvious: you find out immediately, because nothing appears. The fifth is the dangerous one.

The rules that parse and do nothing

Positional selectors fail quietly, in the player, forever

A rule like .pack:last-child { margin-right: 0; } is accepted by the parser, dropped, and logged as Unknown pseudo class "last-child" in the player log at every launch. Nothing in the editor tells you. The rule simply never applies.

In the build I shipped, five such rules had been dead since the day they were written. I found them by reading the log of the compiled player, not from any test, because a test never reads a log.

If you want a row of items with no trailing gap, there are two honest options. Give the container a negative right margin equal to the child gap, so the last item's margin is absorbed. Or clear it from C# once the row is filled:

// USS has no positional selectors, so the last child is closed from here. var last = row.Children().LastOrDefault(); if (last != null) { last.style.marginRight = 0; }

The failure that deletes half your screen

This is the single most expensive UI Toolkit mistake I have made, and it has no error message at all.

A class named from C# that was never written into the stylesheet

AddToClassList("creator-column") compiles. It runs. The element takes default flex, which in a column usually means it shrinks to nothing. In my case an entire panel of readouts and notes was invisible on a screen I looked at every day, and 244 automated tests were green throughout.

Two defences. State flex-shrink: 0 on your panel class so a missing rule degrades into overflow rather than disappearance. And write a test that reads the stylesheet, collects every string passed to AddToClassList and EnableInClassList across your source, and fails when one has no rule.

// The guard, in one sentence: every class the interface names must be styled. var declared = ClassNamesIn(File.ReadAllText(stylesheetPath)); foreach (var used in ClassNamesUsedInSource()) { Assert.That(declared.Contains(used), Is.True, $"{used} is named from C# and absent from the stylesheet, so it takes " + "default flex and quietly collapses what it is on."); }

That guard has caught the same class of fault on this project more times than any other test I own, including twice while writing the article you are reading.

The runtime theme is styling your controls already

Sliders, toggles, text fields and dropdowns arrive pre-styled by Unity's runtime theme, in a light editor idiom. On a dark game screen they show up as pale grey slabs.

The instinct is to reach into the theme's own element names and override them. That instinct cost me four wrong fixes in a row.

Do not fight the theme's internals

Targeting .unity-toggle__checkmark to remove a white slab also removed the checkmark, so the control became a box that could not show its own state. Targeting unity-base-slider__dragger-border produced four attempts that each fixed the symptom somewhere else.

The reliable answer is to stop overriding and start drawing. A tick is a button, a drawn box and a filled block. It is fifteen lines, it cannot be broken by a theme update, and it looks like the rest of your game.

Watch for global caps as well. A rule like .unity-base-slider { max-width: 560px } anywhere in your sheet applies to every slider in the project. On a 1370 pixel row my sliders capped at 560 and the overlay marking their locked portion lined up with the groove only by coincidence.

Four things that look right and are not

A Button with a height does not centre its own label

It draws the text at the top left of the box. State -unity-text-align: middle-center on any button you give an explicit height to.

The runtime theme puts a margin on every Button

Four cards sized to fit a 1340 pixel page measured 1352 because each inherited a theme margin. The fourth wrapped to a row of its own, and it read as a deliberate three-across layout rather than as an overflow. State margin-left and margin-top explicitly on any styled Button.

Number formatting follows the machine, not the game

A raw $"{value:N2}" prints 20,00 on a machine with a Polish locale. This is not a UI Toolkit fault, but it surfaces in UI Toolkit strings more than anywhere else because that is where numbers meet the screen. Route every figure through one formatter that uses InvariantCulture. I have been caught by this three separate times on the same project.

A PanelSettings reference can vanish on a scene rebuild

Assigning document.panelSettings from editor tooling did not survive being written to disk for the second scene in a build run, every time, deterministically. The interface was constructed into no panel and drawn nowhere: the screen was the camera's clear colour and nothing else, which looks exactly like a hang. Assign serialized fields through SerializedObject in editor tooling, and read the serialized field back rather than the property you just set.

How to see any of this

None of the failures above throw. Most of them are invisible to a test suite that never opens a window. The only reliable method I have found is to render the interface to a texture and look at the picture.

A PlayMode test that loads the scene, points the panel at a RenderTexture, walks every screen and writes each frame to a PNG costs about eighty lines. One pass over the resulting contact sheet found four faults that 683 green tests had not: every styled button drawing its label at the top left, eleven tiles with no art because the files were never made, a difference bar scaled by the wrong value, and money printed as $20,00.

Assert two things about the frame so a picture cannot lie to you: that it is not one flat colour, because a render texture nobody drew into is indistinguishable from a dark screen that worked, and that nothing extends past the right-hand edge.

Is UI Toolkit worth it anyway?

For this project, yes, and I would choose it again. A dense interface built in C# against one stylesheet is far easier to keep consistent than the same interface assembled by hand in a scene, and every screen can be built by a function that a test can call.

But go in knowing the trade. The documentation is thin, a large share of forum questions have no answer, the theme styles your controls before you do, and the missing USS features are the ones you reach for on day one. None of that is fatal. All of it is easier when somebody has written the list down first.

Everything here came out of Scaling Laws, an AI company tycoon built in Unity and released as a free public build. The source is open, including the stylesheet and the guard tests described above.

The game page and the source

Questions people ask about this

Does USS support gradients?

No. Bake the gradient into a 256×1 texture and stretch it, or paint it with Painter2D. The texture is cheaper and is the right answer for a horizontal bar.

Does USS support :last-child?

No, and neither does any other positional pseudo-class. The rule parses, is dropped, and warns in the player log at every launch while styling nothing.

Why does my USS class do nothing?

Most often because it is named from C# and was never written into the stylesheet. The element takes default flex and collapses, silently. Write a test that compares the two lists.

Why is my button text not centred?

A Button with an explicit height does not centre its own label. Set -unity-text-align: middle-center.

UI Toolkit or uGUI in 2026?

UI Toolkit for a dense, data-heavy interface built in code, and it is Unity's forward path. uGUI is still simpler for a handful of straightforward screens.

MF

Marcin Firmuga

Solo developer · HCK_Labs · building in public

I write about what I actually shipped, with real numbers and real code, including the parts that did nothing. More: my story.