the model

your UI is a tree

every goo UI is a tree of blobs. a blob is a small C# value that describes a node. nesting blobs gives you structure. the root is whatever you return from Build().

public class MyHud : GooPanel<Container>
{
    protected override Container Build() => new Container
    {
        FlexDirection = FlexDirection.Column,
        Padding = 16,
        Children = { new Text("Hello World") },
    };
}

three things to notice:

  1. GooPanel<Container> is a Sandbox.PanelComponent. drop it on a GameObject like any other component.
  2. Build() returns a Container. that's your root blob.
  3. children sit inside Children = { ... } in the same initializer.

blobs

there are ten blob types:

all ten are readonly record struct types. cheap to allocate, cheap to compare. you don't keep references to them. you describe the tree you want, goo mutates the engine Panel tree to match.

the Shapes class (Goo.Shapes) is a separate named surface: a static factory that returns composite subtrees for common shapes like rings and discs. it is not a blob type itself, but it produces blobs you use like any other. see shapes for the full rundown.

why not razor?

razor in s&box pairs a .razor template with a .razor.scss stylesheet. the template is markup, the code-behind is C#, the styling is a sibling file. three languages, three syntaxes, three file mouths to feed for one component.

goo drops the template and the stylesheet. the tree is C#. the styles are C# init-only properties on the same record. you read top-to-bottom in one language.

other things you get:

composition by extraction

want a reusable card? write a function that returns a Container. see composition for the pattern in depth.

structural diff

the reconciler matches blobs by type and optionally Key. see build method for what that comparison covers per blob.

if you didn't understand any of this, that's ok. work through the hands-on articles in order and circle back to this page later.

see also