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

Reference / UI

Layout.ScrollHeight and Layout.ScrollWidth

Layout.ScrollHeight / Layout.ScrollWidth — how tall or wide the content is, as opposed to the box it is shown in (int?)

`Layout.ScrollHeight` is how tall a container's CONTENT is; `Layout.Height` is how tall the container is. The difference is what "is there more here than fits?" means, so `Layout.ScrollHeight > Layout.Height` is a scrollbar-present test, a "more below" hint, or a shadow that appears only when a list overflows. Both are `int?` and both are null on the server, which has no layout — write your own fallback with `?? n`.

stable1 example compiled by CIuilayoutmeasurement

Summary#

Layout.Height answers how big is this box. Layout.ScrollHeight answers how big is what is inside it. When the content fits, they are equal; when it does not, the extent is larger — and that difference is the only way to ask whether a container is actually scrollable.

Both are render-only reads of the container the component is placed into, and both are int?: a server has no layout, so SSR answers null rather than inventing a number.

Signature#

int? h  = Layout.ScrollHeight;   // how tall the content is
int? w  = Layout.ScrollWidth;    // how wide the content is

Written without parentheses — they are measured values, not operations, the same reading that makes Math.PI parenless.

Description#

Which box does it measure? — the container, not the root#

The same element Layout.Width / Layout.Height measure: the box this component was placed into, not the component's own root. So a component that reads the extent reports on its container, which is what makes a reusable "scroll hint" component possible — drop it inside any pane and it describes that pane.

That has one practical consequence worth knowing before you write it: reading the extent on a page answers a true but useless number, because a page root grows to fit its content and the two readings are equal for ever. The extent is interesting exactly where the box is constrained.

When does it update? — on CONTENT, not on size#

Like every Layout.* read, this one subscribes the slot that read it — you write no wiring and the value updates.

What it updates ON is not what the size reads update on, and it is the reason this exists as its own primitive rather than as an option on the others. The visible box changes when the element is resized. The extent changes when the content changes — a row appended to a list inside a fixed-height pane grows the extent while the pane's own box never moves at all. Both are handled; you do not have to know which one fired.

Why is it null? — nothing has been measured yet#

null means not measured — on the server, or before the container exists. It is not a fallback the platform chose: zero would divide, and any invented number would lay out at the wrong scale. Write the fallback yourself, where your app can see it:

// in a render block:
if ((Layout.ScrollHeight ?? 0) > (Layout.Height ?? 0)) { Text("more below"); }

Examples#

A "more below" hint that appears only when the list actually overflows — the canonical use, and one that cannot be written from the box alone:

[AllowAnonymous]
component ScrollHint() {
  render {
    // Read IN the render block — `Layout.*` measures a rendered container, so a member initializer has nothing to
    // measure and the compiler refuses it there.
    Stack {
      if ((Layout.ScrollHeight ?? 0) > (Layout.Height ?? 0)) {
        Text("more below ↓", fontSize: 12);
      }
    }
  }
}

[Page("/inbox")]
[AllowAnonymous]
component Inbox() {
  int rows = 20;

  render {
    Stack(h: 200, overflowY: Overflow.Auto) {
      ScrollHint();
      foreach (var i in Enumerable.Range(0, rows)) {
        Text($"message {i}");
      }
    }
  }
}

Limits — a render position only#

  • Render position only. The answer comes from measuring a rendered container, so it belongs in a render block — an action or a function body has nothing on screen to measure, and the compiler says so rather than letting it fail at run time.
  • Reading an extent makes the platform measure the container, which forces layout. That cost is paid only by a component that asks for it — a page that never mentions Layout.Scroll* observes nothing.
  • The extent includes content clipped by overflow, which is the whole point; it does not include margins outside the padding box.

See also#

Related

Layout.TextWidth

Answers how wide a string will actually paint, measured against the font the surrounding container paints with. Null…

layout primitives

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

style props

Inside a `variants` block, each `Name = value` is a style prop from a fixed vocabulary the renderer maps to CSS — paint…

The reactivity & lifecycle model

How an Osy# component comes alive and stays in sync: declarations are live value bindings, `on mount`/`on unmount` are…