Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / UI

Layout.TextWidth

Layout.TextWidth(text) / Layout.TextWidth(text, fontSize) — how wide that string will paint, in pixels (nullable)

Answers how wide a string will actually paint, measured against the font the surrounding container paints with. Null wherever there is no font to measure against — the server, and any environment without text metrics — so the app supplies its own estimate with ?? and owns the fallback.

stable1 example compiled by CIuilayoutmeasurement

Summary#

Layout.TextWidth tells you how wide a string will paint, before it is painted:

render {
  // How much room does the longest label need? Reserve exactly that, and no more.
  Axis(gutter: Layout.TextWidth(longestLabel) ?? 40.0);
}

Almost every sizing question a UI asks has a layout answer — a flex child fills its share, an SVG viewBox maps a nominal drawing onto whatever box it lands in, and neither needs a number in advance. Text is the exception. Whether two axis labels collide, whether a centred readout will be clipped, and how wide a gutter a value label needs are all decided before the box those things live in exists, and flexbox has no opinion to consult.

Signature#

FormMeasures at
Layout.TextWidth(text)the size the container already paints at
Layout.TextWidth(text, fontSize)fontSize pixels, in the container's family and weight

Returns a nullable double. text must be a string.

Description#

The size argument is optional, and omitting it is the safe form#

The number is only as good as the font it was measured with, and a measurement taken against the wrong font is not an error — it is a plausible number that lays out slightly wrong. So the platform does not take the font from you. It reads the computed style of the container your component renders into: family, weight, style, and — unless you say otherwise — size.

Pass a size only when the text will genuinely be drawn at a size the container is not using (a heading you are about to render, a canvas label). Family and weight still come from the container even then, because those are the parts an author could not reliably supply anyway.

Why is the measurement null? — no font on the server#

There is no font on a server, so a server-rendered page measures nothing. Rather than invent a number — which would paint a layout built on a font nobody is using — the read answers null, and your ?? decides:

double gutter = Layout.TextWidth(longest) ?? 40.0;

This is the same shape layout primitives's container reads use, and for the same reason: a size has no honest reading of "not measured", so zero would be a lie that lays out. The estimate lives in your app, where you can see it.

The first client render corrects it, and so does the moment a web font finishes loading — until then the browser can only measure the fallback face, so the answer is re-taken and anything that read it re-renders. Nothing is wired by the app.

Can I measure text in an action? — render only#

In a render block only. An action or a function body may run on the server and runs independently of any paint, so it has no container and no font to measure against — that is a compile error, not a runtime surprise. Read it in render and pass the number in.

What it costs#

Nothing on a page that never calls it, and no layout pass on a page that does: the measurement reads font metrics directly rather than laying out a hidden element, and repeated measurements of the same string in the same font are served from a cache. An axis measuring a dozen labels on every resize is a normal thing to write.

Examples#

Reserve exactly the gutter the longest tick label needs — and thin the labels out when they would collide:

string Longest(string[] labels) {
  var best = "";
  foreach (var l in labels) { if (l.Length > best.Length) { best = l; } }
  return best;
}

// How many categories to skip so the widest label fits its slot. At least 1 — a step of 0 would show nothing.
int Step(double widest, double slot) {
  var needed = (int)(widest / slot) + 1;
  return needed < 1 ? 1 : needed;
}

[Page("/chart")]
[Render(CSR)]
[AllowAnonymous]
component MiniAxis() {
  string[] labels = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];

  render {
    // The gutter is exactly what the longest label needs — measured, not guessed at 40.
    var gutter = Layout.TextWidth(Longest(labels)) ?? 40.0;
    // Each category gets an equal slice of the plot. If the widest label does not fit its slice, show every Nth.
    var slot = ((Layout.Width ?? 600) - gutter) / labels.Length;
    var step = Step(Layout.TextWidth(Longest(labels)) ?? 40.0, slot);

    Row(gap: 1) {
      Box(w: gutter + "px") { Text("value"); }
      Stack {
        foreach (var i in Enumerable.Range(0, labels.Length)) {
          if (i % step == 0) { Text(labels[i]); }
        }
      }
    }
  }
}

The ?? 40.0 is not defensive clutter — it is what paints on the server and in the instant before the browser has measured, so choose a number that looks right rather than a zero.

See also#

  • layout primitives — the layout primitives, and the container reads (Layout.Width / Layout.Height) this is shaped after
  • Canvas — drawing text yourself, where you also choose the size it is drawn at
  • keys — the other primitive that answers a question about the live page

Related

layout primitives

The built-in layout primitives and how they arrange children. `Stack` stacks children in a column, `Row` lays them in a…

keys

`keys:` declares that an element owns a set of keys: it becomes focusable, those keys stop scrolling the page, and…

Canvas

A drawing surface, and the verbs that paint on it. Put a `Canvas` in a render block, call `Draw.*` from an `on frame`…