These are some of the pain points I've lost time on with Unity's new UI system. Despite finally being released as "stable", the system and documentation are still pretty rough. There are a lot of pain points. Sometimes, important points are only explained in an obscure forum post. Maybe you can benefit from the legwork I've already done instead of rediscovering these things the hard way.

In particular, I've been digging deep into the Custom Components system. Despite a decent amount of documentation being available you can still end up banging your head against the wall at points if you don't know certain details of the system.

As of this writing I'm using the stable, LTS release line (Unity 2021.3.3) - not an alpha, beta, or "tech stream" release.

I'll continue to update this list as I go.

Differenences

First, a few mere differences. I'm neutral towards these. The system doesn't need to be just like the web, matching each and every idiosyncracy, but the differences are worth calling attention to because they take some getting used to.

-unity- prefixed properties

Instead of text-align, line-height, and vertical-align CSS properties to position text, there's a single combined USS property, -unity-text-align, that sets both vertical and horiztonal alignment at once. It has one of these values (from the properties reference)

upper-left | middle-left | lower-left | upper-center | middle-center | lower-center | upper-right | middle-right | lower-right

Bold And Italic Text

Another of these combined properties is -unity-font-style, instead of font-weight: bold or font-style: italic. One or both are set with the possible values: normal | italic | bold | bold-and-italic

CSS two more similar properties, text-transform and font-variant that Unity has no equivalent USS for. This is one of those idiosyncracies that didn't need to be duplicated just for the sake of it; I could never keep these four properties straight anyway!

Missing Features

No Media Queries

This one is hugly disappointing for me. This seems like it should've been a high priority item for Unity, considering how Unity is supposed to be about making it easy to publish across multiple platforms with one codebase (it's right in the name). It seems obvious that they'd want to be able to brag about the ability to target both mobile form factors and monitor/tv form factors completely declaratively, from one codebase.

It's not just about scaling elements up and down. I want to hide whole sections of UI and bring in new ones declaratively, based on screen size, just like responsive design on the web.

No Comma (Selector List) In UI Builder

You can create a selector list by manually editing your USS file, but can't do this in the editor (UI Builder). If you have e.g. .one, .two, .three { color: red }, UI Builder will show this as three separate classes in the editor, and will rewrite your file this way when you save. Trying to type a list into the "Add new selector...." box displays an error message about invalid characters.

No Rule Nesting (A La Sass)

CSS doesn't have this either - yet - but it should, and there's a CSS Nesting working draft (no implementations yet)

Sass/Less style nesting is extremely popular with web designers and has been of the most highly requested features for inclusion into the CSS standard.

If you poke around the forums you see some mentions of people getting Sass or PostCSS to generate USS, but adding this tooling yourself shouldn't be necessary.

Unity is re-inventing the wheel here. They ought to do it right, drawing on 30 years of collective web design knowledge, and include this out of the box.

No CSS Grid

Again, if making a totally new system without legacy support holding you back, why not aim higher?

CSS Grid has been widely supported since 2017. Even IE 10 - TEN - supported the older version of the standard, back in 2012. CSS Grid is not cutting edge, experimental web tech now. It's mundane. By only implementing flexbox but not grid, Unity has created a subpar system.

The predictable response is "Well, you can do such-and-such equivalent thing in flexbox by [....]". Yeah, and you can create grid systems with floats and clears and negative margins like the bad old days! It's not about what is technically possible, it's about ease of use.

Grids are everywhere in game UI: Inventories, spellbooks, achievements, level selects, shops, albums, bestiaries, etc, etc. Unity could have done so much better here.

The UGUI grid system is notorious for being one of the worst, most widely-mocked parts of Unity.

For example....

UI design in Unity recreates the feeling of trying to make a responsive, dynamic, web UI that is compatible with Firefox, Chrome, Safari, Edge, and that one ancient version of IE your biggest customer is legally required to use due to having military contracts.

....in a thread called "UI design in Unity makes me want to choke someone", no less.

Just look at the lengths you have to go to, overriding layout logic, to get a decent grid system under UGUI. You'd think Unity would want to finally redeem themselves!

At least there's a smaller grap to close now if you choose to write a whole grid system of your own on top of UI Toolkit's flexbox system, but you still shouldn't have to

No justify-self

UI Toolkit has the align-self property, of course, but no justify-self for parity. Originally, CSS flexbox didn't have this either, and MDN still has this mini-lecture in its docs about how it doesn't make sense, but it was indeed added to the spec later. I've used it from time to time.

No !important

Unlike media queries or grid layout, this is very minor. Ideally, you never use !important at all. It's an escape hatch. In your own code or code you control, you should fix your styling or element hierarchy instead of using !important, but I could see it potentially being a problem in edge cases when using third-party components that set inline styles.

Custom Component Gotchas

Init() of UXMLTraits gets called repeatedly

As this forum post explains, this behavior is by design. Init() gets called any time you change a property in the UI Builder inspector. If your rendering code involves appending child elements then they'll get duplicated every time you type a character. You need to either manually keep track of whether it's the first Init, building the skeleton as a separate step, or remove all child elements as the first step in your rendering process.

The original UXML custom attribute definition API (via UxmlTraits) was not designed to be invoked after initial element creation. This limitation is still present in the current UI Toolkit API. As such, the UI Builder has to be a little creative when it comes to letting you change attributes on an element in the UI Builder's Inspector. What the UI Builder does is it finds (via reflection) the Init() call on the current element and just calls it each time an attribute value has changed. This is why your Init() is called. It's by design. The fix is fairly simple: just check for evidence of a previous Init() call (your placeholder label already exists) and account for that (by not creating a new one).

Custom Properties Must Have get and set

The UI Toolkit system treats the existence of these as magic in order to discover your properties. Even if you declare them with the correct names via UXMLTraits, they won't be discovered without getter and setter declarations (the default { get; set; } is sufficient). Definitely not obvious!

From the same post:

UI Builder needs some way to read the current value of your custom UXML attribute. You could be doing anything inside your Init() with the value coming from UXML so there's no way for the Builder to know what it has been already set to. So, for the same root cause as above, the UI Builder relies on the existence of C# attributes (ie. { get; }) to be defined on your custom element that have the same name as the corresponding attribute (same name as in, uxml uses dashes "my-value" and corresponding C# attribute uses no dashes "myValue", case insensitive).

Programmatic Instantiation Doesn't Trigger Init

This makes sense when you think about it, but since I had started by getting UXMLTraits to work, and then moved on to instantiating components from code, I didn't think about it.

If you've built custom component "MyComponent" and you try to call new MyComponent() and Add() it as a child rather than inserting it into the UXML via <MyComponent />, Init() of UXMLTraits won't be called. This makes a kind of sense; UXMLTraits seems to be intended only for the declarative case, and you aren't actually setting anything in your UXML here.

However, this is a problem if, like me, you were only doing initial rendering in response to Init. Rather, you should do rendering both from your component's constructor and from Init; have a separate rendering method and call it from each path.

Programmatic Property Changes Don't Re-Trigger Init

For the same reasoning above, property changes don't and shouldn't re-trigger Init, as you're not changing a property declaration in your UXML. This might initially be non-obvious since property changes in UI Builder call Init over and over as mentioned, but changing properties in code has no effect.

You'll have to update or re-render the relevant portion of your UI as a side effect of the property's setter.

Other Gotchas