# Layout.ScrollHeight and Layout.ScrollWidth

> `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`.

<!-- id: ui-scroll-extent · area: ui · stability: stable · html: https://osysharp.com/reference/ui/scroll-extent/ -->

## Summary        {#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      {#signature}

```osy syntax
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    {#description}

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

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   {#tracking}

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   {#nullable}

`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:

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

## Examples       {#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:

```osy test app=ui-scroll-extent
[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   {#notes}
- **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   {#see-also}
- [Layout.TextWidth](https://osysharp.com/reference/ui/text-measurement/) — `Layout.TextWidth`, for how wide a string will paint
- [layout primitives](https://osysharp.com/reference/ui/layout/) — `gap`, `align` and `justify`
- [style props](https://osysharp.com/reference/ui/styling/) — `overflowY`, `h`, and why a definite size on a flex child holds
