Typesafe C# authoring for Unity UI Toolkit Stylesheets
Write your UI Toolkit styles in C# with full IntelliSense and compile-time safety, then generate standard .uss files automatically. Get the best of both worlds: the developer experience of C# and the runtime performance of USS.
Note
The code contained in this package is from my personal Unity project Automancer, while we are using it in production, that does not mean it is perfect or stable - this package should be considered experimental and the API may change without warning.
Unity UI Toolkit forces you to choose between:
| Approach | Type Safety | IntelliSense | Performance |
|---|---|---|---|
| Inline C# | ✅ | ✅ | ❌ Poor (per-element allocation) |
| USS files | ❌ | ❌ | ✅ High (native UI Toolkit) |
TypeUSS lets you author styles in C# and generates USS automatically:
// ❌ Before: No IntelliSense, typos compile fine, painful to maintain
.chat-container {
widht: 400px; /* Typo - no error until runtime */
background-color: rgba(0, 0, 0, 0.7);
}// âś… After: Full type safety, autocomplete, can share files with C# based component definitions
[GenerateUSS("UI/Generated/Chat.uss")]
public static class ChatStyles
{
public static readonly Selector Container = Sel.Class("chat-container");
public static readonly TypeStyle ContainerStyle = Container.Style(s => s
.Width(400) // Autocomplete works!
.BackgroundColor(new Color(0, 0, 0, 0.7f)));
}
// Usage - typesafe class names
container.AddToClassList(ChatStyles.Container); // No magic stringsGenerates standard USS files that Unity loads normally. Zero runtime overhead.
Copy the TypeUSS folder into your project's Assets/Scripts/ directory.
We recommend manual installation so you can easily add additional custom property wrappers as needed. TypeUSS doesn't wrap every USS property yet, just the most common ones. If you add new properties please consider contributing them!
using UnityEngine;
using TypeUSS;
[GenerateUSS("UI/Generated/Button.uss")]
public static class ButtonStyles
{
// Define selectors (class names)
public static readonly Selector Button = Sel.Class("btn");
// Define rules (selector + properties)
public static readonly TypeStyle ButtonBase = Button.Style(s => s
.Padding(8, 16)
.BorderRadius(4)
.BackgroundColor(new Color(0.3f, 0.3f, 0.3f))
.Color(Color.white));
// Hover state
public static readonly TypeStyle ButtonHover = Button.Hover().Style(s => s
.BackgroundColor(new Color(0.4f, 0.4f, 0.4f)));
}When the Unity project recompiles, TypeUSS generates the '.uss' file in the location specified in their [GenerateUSS] attribute. You can also manually regenerate via TypeUSS → Regenerate All USS in the menu bar.
/* Auto-generated by TypeUSS - Do not edit manually */
.btn {
padding: 8px 16px;
border-radius: 4px;
background-color: rgb(77, 77, 77);
color: rgb(255, 255, 255);
}
.btn:hover {
background-color: rgb(102, 102, 102);
}var button = new Button { text = "Click me" };
button.AddToClassList(ButtonStyles.Button); // Typesafe class name// Class selector: .my-class
Sel.Class("my-class")
// ID selector: #my-id
Sel.Id("my-id")
// Type selector: Button
Sel.Type<Button>()
Sel.Type("Button")
// Universal selector: *
Sel.All
// Escape hatch for complex selectors
Sel.Raw("#unity-text-input")Button.Hover() // .btn:hover
Button.Active() // .btn:active
Button.Focus() // .btn:focus
Button.Enabled() // .btn:enabled
Button.Disabled() // .btn:disabled
Button.Checked() // .btn:checked
// Chain them
Button.Hover().Focus() // .btn:hover:focus// Child combinator: .parent > .child
Parent > Child
// Descendant combinator: .ancestor .descendant
Ancestor.Descendant(Child)
// Adjacent sibling: .a + .b
A + B
// Combine without space (multiple classes): .btn.primary
Button.And(Primary)
Button.And(Sel.Class("primary"))
// Add a modifier class: .btn.focused
Button.AddClass("focused")TypeUSS wraps the most common USS properties with typesafe methods:
// Sizing with various length units
.Width(100) // 100px
.Width(Length.Percent(50)) // 50%
.Width(Length.Auto) // auto
.MarginLeft(Length.Auto) // auto (useful for push-right)
.Height(200)
.MinWidth(100)
.MaxHeight(500)
.FlexGrow(1)
.FlexDirection(FlexDirection.Row)
.JustifyContent(Justify.Center)
.AlignItems(Align.FlexStart)
.Padding(10) // all sides
.Padding(10, 20) // vertical, horizontal
.Padding(10, 20, 10, 20) // top, right, bottom, left
.Margin(10)
.Position(Position.Absolute)
.Top(0)
.Left(0)
.BackgroundColor(Color.black)
.Color(Color.white)
.BorderColor(Color.gray)
.BorderWidth(1)
.BorderRadius(4)
.Display(DisplayStyle.Flex)
.Visibility(Visibility.Hidden)
.Overflow(Overflow.Hidden)
.Opacity(0.5f)
// Typography
.FontSize(14)
.LetterSpacing(2)
.UnityTextAlign(TextAnchor.MiddleCenter)
.UnityFontStyleAndWeight(FontStyle.Bold)
.UnityFontDefinition(myFont)
.WhiteSpace(WhiteSpace.Normal)
// Background
.BackgroundImage(texture)
.BackgroundPosition(BackgroundPositionKeyword.Center, BackgroundPositionKeyword.Center)
.BackgroundSize(100, 100)
.BackgroundRepeat(Repeat.NoRepeat, Repeat.NoRepeat)
// Transform
.Rotate(45f)
.Scale(1.5f)
.TransformOrigin(Length.Percent(50), Length.Percent(50))Don't stop coding to add a new property wrapper. Use .Prop() for anything not yet wrapped:
public static readonly TypeStyle HeaderStyle = Header.Style(s => s
.Width(100) // Wrapped
.BackgroundColor(Color.black) // Wrapped
.Prop("-unity-font-style", "bold") // Escape hatch!
.Prop("transition-duration", "0.2s")); // Escape hatch!When you notice you're using the same .Prop() call repeatedly, that's a signal to add a proper wrapper method.
Target Unity's internal element structure using Sel.Raw():
public static readonly Selector Input = Sel.Class("my-input");
// Style the internal text input element
public static readonly TypeStyle InputInner = (Input > Sel.Raw("#unity-text-input")).Style(s => s
.BackgroundColor(Color.black)
.Color(Color.white)
.Padding(0, 5));TypeUSS automatically finds MonoBehaviours in the open scene with a StyleSheet[] field named styleSheets
and populates it with all generated stylesheets.
To automatically apply generated stylesheets to your UI, add a field to your UI controller:
public class UIManager : MonoBehaviour
{
[SerializeField] private UIDocument document;
[SerializeField] private StyleSheet[] styleSheets; // Auto-populated by TypeUSS
void OnEnable()
{
foreach (var sheet in styleSheets)
document.rootVisualElement.styleSheets.Add(sheet);
}
}TypeUSS works well with a React-inspired Props pattern for UI components. Co-locate styles at the top of each component file:
using System;
using UnityEngine;
using UnityEngine.UIElements;
using TypeUSS;
namespace MyGame.UI.Components
{
// ============================================================
// STYLES (co-located at top of file)
// ============================================================
[GenerateUSS("UI/Generated/MyComponent.uss")]
public static class MyComponentStyles
{
public static readonly Selector Root = Sel.Class("my-component");
public static readonly TypeStyle RootStyle = Root.Style(s => s
.FlexDirection(FlexDirection.Row)
.Padding(10));
public static readonly Selector Label = Sel.Class("my-component__label");
public static readonly TypeStyle LabelStyle = Label.Style(s => s
.FontSize(14)
.Color(Color.white));
// Modifier class for focused state
public static readonly TypeStyle LabelFocused = Label.And(Sel.Class("focused")).Style(s => s
.Color(Color.yellow));
}
// ============================================================
// PROPS (immutable configuration)
// ============================================================
public sealed record MyComponentProps(
string Text,
bool IsFocused = false,
Action OnClick = null
);
// ============================================================
// STATIC FUNCTIONAL COMPONENT
// ============================================================
public static class MyComponent
{
public static VisualElement Render(MyComponentProps props)
{
var root = new VisualElement();
root.name = "my-component";
root.AddToClassList(MyComponentStyles.Root);
var label = new Label(props.Text);
label.AddToClassList(MyComponentStyles.Label);
label.EnableInClassList("focused", props.IsFocused);
root.Add(label);
root.RegisterCallback<ClickEvent>(_ => props.OnClick?.Invoke());
return root;
}
public static void Update(VisualElement root, MyComponentProps props)
{
var label = root.Q<Label>();
if (label != null)
{
label.text = props.Text;
label.EnableInClassList("focused", props.IsFocused);
}
}
}
// ============================================================
// WRAPPER COMPONENT (for imperative usage or IUpdatableComponent)
// ============================================================
public sealed class MyComponentWrapper : VisualElement
{
private MyComponentProps _props;
public MyComponentWrapper(MyComponentProps props)
{
_props = props;
var content = MyComponent.Render(props);
Add(content);
}
public void UpdateProps(MyComponentProps props)
{
_props = props;
MyComponent.Update(this[0], props);
}
}
}| Principle | Description |
|---|---|
| Props are immutable | sealed record with all configuration |
| Styles at top | TypeUSS styles defined before component code |
| Render creates | Render(props) returns new VisualElement tree |
| Update mutates | Update(element, props) updates existing elements |
| BEM naming | component, component__element, component--modifier |
| Modifier classes | Dynamic state via EnableInClassList() |
TypeUSS was developed and tested with Unity 6.3 - it may work with earlier versions but I have not taken the time to verify compatibility.
MIT License - see LICENSE for details.
Contributions welcome! Please feel free to submit issues and pull requests.
Adding a new property wrapper:
- Add the method to
StyleBuilder.cs - Add any necessary enum mappings to
EnumExtensions - Submit a PR