tokens

Tokens is a lexically-scoped dynamic lookup for design values: colors, lengths, font sizes, anything. define as much or as little as you want. the framework defines no schema and mandates no keys.

the shape

you describe a blob tree inside a Tokens.Scope call. Container and Text are the two most common blob types. Length is the s&box unit type for pixel sizes. Tokens.Scope pushes a dictionary on an ambient stack, evaluates the subtree expression, and pops it in a finally block.

private static readonly IReadOnlyDictionary<string, object> Dark = new Dictionary<string, object>
{
    ["Bg"]     = Color.FromBytes(0x11, 0x14, 0x1B, 255),
    ["Fg"]     = Color.FromBytes(0xEC, 0xED, 0xED, 255),
    ["Accent"] = Color.FromBytes(0x47, 0x8A, 0xD1, 255),
    ["Pad"]    = Px.Of(16),
};

protected override Container Build() => Tokens.Scope(Dark, () => new Container
{
    BackgroundColor = Tokens.Get<Color>("Bg"),
    Padding         = Tokens.Get<Length>("Pad"),
    Children        = { new Text("tokens demo") { FontColor = Tokens.Get<Color>("Fg") } },
});

Tokens.Scope(dict, () => subtree) returns whatever the body returns. lookups inside the body resolve against the stack top-down, so a nested Tokens.Scope overrides outer values for matching keys and falls through for missing ones.

Tokens.Get<T>("key") returns the value cast to T. it throws KeyNotFoundException if no active scope contains the key. it throws InvalidCastException if the stored value cannot be cast to T. Tokens.TryGet<T>(key, out value) is the non-throwing variant.

the dict type is IReadOnlyDictionary<string, object>. pass a plain Dictionary<string, object>, your own record that exposes one, or a static field. one entry or two hundred. your keys, your types.

Length accepts float implicitly in pixels, so writing 16f in any Length-typed property gives you 16 pixels. use Px.Of(16) when you need an explicit named Length value, for example as a dict entry. Px.Of returns a non-nullable Length, which is required when storing into object.

dynamic swapping

hold the dict in a field, swap the reference, call Rebuild(). resolution happens at every Build() call, so the next frame paints with the new values. Rebuild() is covered in a later guide.

IReadOnlyDictionary<string, object> _theme = Dark;

protected override Container Build() => Tokens.Scope(_theme, () => /* tree */);

void ToggleTheme()
{
    _theme = _theme == Dark ? Light : Dark;
    Rebuild();
}

this is the "dynamic" in dynamic-resource: the framework stores nothing across builds. swap the dict reference, ask for a redraw, done. no reactive layer, no observer wiring.

caveats

see also