How I built Utility USS

Posted on

Coming from the world of front end web development, I was pleasantly surprised to find that Unity’s UI Toolkit allows you to craft UI using XML and USS. USS is just CSS with a limited set of properties and some slight differences.

A few years ago, the web was completely taken by storm with the release of Tailwind CSS, a tool for designing and coding websites completely with utility classes. It’s a flexible CSS system that anyone can use, eliminating the learning curve when onboarding someone to a project. It feels backwards, but everything is handled in your html files instead of switching between CSS and HTML. It got popular, really popular.

Tailwind CSS growth
Tailwind CSS growth

I missed the simplicity of building UI this way, and being a CSS expert put me in a unique position to bring something similar to the world of Unity. Introducing Utility USS.

How I built it

The good thing about USS being near identical to CSS is that I was able to use web tooling to do the job. The ability to use SASS really helped speed up and simplify the development process.

SASS is a css extension language. It adds features like nesting, variables, arrays, and loops, and compiles to normal CSS. A lot of concepts SASS introduced have been brought over to the CSS spec. This helped turned hundreds of lines of code into dozens.

Challenges

A few challenges arose when building the library, thankfully nothing too difficult to handle.

USS class naming rules

CSS allows you to use characters in your class names that USS does not. Characters like : and / do not work inside USS files creating incompatibilities like:

.w-1/2 { width: 50%; }
.h-2.5 { height: 10px; }

To address this, I used words for fractions; for example, .w-1/2 becomes w-one-half. While this results in longer class names, it clearly communicates their function.

For half sizes I opted to remove them. Tailwind CSS only has 4 half sizes 0.5, 1.5, 2.5, and 3.5. By default, one unit in Tailwind CSS is equivalent to 4px. While I believe this omission isn’t significant, I may consider reintroducing them in the future.

Rounding out the differences

Another challenge was rounding out the differences between CSS and USS. Here’s some differences I encountered along the way.

  1. Label text does not wrap by default in USS, I changed this.
  2. In USS, flex column is the default direction; I left this unchanged.
  3. Properties like rotations and scales in USS utilize top-level functions, unlike CSS where they’re grouped under a single ‘transform’ property. I did the conversions here to make it work.
  4. There are many properties prefixed with -unity- that had to be mapped to CSS equivalents.
  5. Tailwind CSS uses rem units which do not exist in USS. Everything was converted to pixels.

Overly complex sass loops

Writing each class manually would be highly inefficient. Using SASS loops was an absolute necessity. However, in some cases, this resulted in complex loops that were challenging to navigate. Here’s an example:

$translateAmounts: (
    "0": 0px,
    "px": 1px,
    "1": 4px,
    "2": 8px,
    "3": 12px,
    "4": 16px,
    "5": 20px,
    "6": 24px,
    "7": 28px,
    "8": 32px,
    "9": 36px,
    "10": 40px,
    "11": 44px,
    "12": 48px,
    "14": 56px,
    "16": 64px,
    "20": 80px,
    "24": 96px,
    "28": 112px,
    "32": 128px,
    "36": 144px,
    "40": 160px,
    "44": 176px,
    "48": 192px,
    "52": 208px,
    "56": 224px,
    "60": 240px,
    "64": 256px,
    "72": 288px,
    "80": 320px,
    "96": 384px,
    "1-2": 50%,
    "1-3": 33.33333%,
    "2-3": 66.66667%,
    "1-4": 25%,
    "2-4": 50%,
    "3-4": 75%,
    "full": 100%
);

$wordAmounts: (
    "1-2": "one-half",
    "1-3": "one-third",
    "2-3": "two-thirds",
    "1-4": "one-quarter",
    "2-4": "one-half",
    "3-4": "three-quarters",
    "full": "full"
);

$generatedClassNames: (
);

@each $translateXName,
$translateXValue in $translateAmounts {
    $translateXName: if(map-has-key($wordAmounts, $translateXName), map-get($wordAmounts, $translateXName), $translateXName);

    @each $translateYName,
    $translateYValue in $translateAmounts {
        $translateYName: if(map-has-key($wordAmounts, $translateYName), map-get($wordAmounts, $translateYName), $translateYName);

        $className: "translate-#{$translateXName}-#{$translateYName}";

        @if(not map-has-key($generatedClassNames, $className)) {
            .#{$className} {
                translate: #{$translateXValue} #{$translateYValue};
            }

            $generatedClassNames: map-merge($generatedClassNames, ($className: ""));
        }
    }
}

Headed to the Unity Asset Store

My next objective is to make it available on the Unity Asset Store so users can download the package easily right from the Unity editor. Unfortunately, there’s a lengthy approval process; they estimate a wait time of 20-30 days before someone even reviews my submission. The package will be available for free. All the source code is open source, and contributions are welcome.