# skeleton

> A second render tree that stands in for a component while its first query has not yet arrived. It is written with the same grammar as `render`, it may not read the data it stands in for, and it gives way the moment the first result lands — never again on a refetch.

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

## Summary        {#summary}
A component that reads data has a moment before that data arrives. `skeleton { }` says what to draw in that moment:
the **shape** the content will take, at the size it will take, so the page is composed before it is filled.

It is an ordinary render tree — same grammar, same controls, same style props — held on the component beside
`render { }`. The runtime shows it while a query of that component has **never settled**, and swaps it for the real
tree the moment the first result lands.

Without one, a component whose read is in flight renders as **nothing**, and the page assembles itself in front of the
user as each piece arrives. That is not merely unpolished: content that appears late pushes what is already on screen,
so a person reading — or aiming at a button — has it move under them.

## Signature      {#signature}
```osy syntax
component Name(<params>) {
  live var <data> = …;          // the read the skeleton stands in for

  render   { … }                // the real tree
  skeleton { … }                // the stand-in — same grammar, no data reads
}
```

At most one `skeleton` block per component, and it is optional everywhere.

## Description    {#description}

### It fills a CHILD's window, not the page's   {#where-it-shows}
A routed page **awaits its own data before the first paint**, so by the time the page exists its queries have already
answered — a page-level skeleton would have nothing to cover. The window this block fills belongs to a **composed
child**: a child's read is deliberately not awaited, so the child paints and then fills. That is the ordinary case for
a kit control or any component you drop into a page.

So put `skeleton` on the component that **does the reading**, not on the page that contains it. A component with no
queries never shows a skeleton, because there is nothing to wait for.

One consequence worth knowing before you hit it: because a skeleton belongs to a composed child, that child needs
[[ui-composable|`[Composable]`]] when the page around it is public. Its own read stays gated either way — the
attribute is about being allowed to *render inside* an anonymous page, not about what it may read.

### It shows on the FIRST read, and never again   {#first-read-only}
The swap is driven by whether a query has ever **settled** — not by whether one is currently loading. Those differ
exactly once, and it matters:

| | |
|---|---|
| First read, nothing on screen yet | the skeleton shows |
| A refetch, with the previous rows still on screen | the skeleton does **not** show |

Replacing rows the user is already reading with grey placeholders is a regression, not a loading state. For the
*in-flight* feedback that a refetch or an action deserves, see [Pending](https://osysharp.com/reference/ui/pending/); for a read that came back **refused or
broken**, see [A failing query](https://osysharp.com/reference/ui/query-failure/) — that region reports the failure in place, and a skeleton would sit there forever
pretending it was still coming.

### A skeleton may not read the data it stands in for   {#no-data-reads}
Reading a `live var` from inside `skeleton { }` is a **compile error naming the member**. This is not a style rule —
it is the block's defining condition. A skeleton renders precisely *because* that read has not arrived, so anything it
reads off it is empty by construction, and the placeholder would silently render nothing.

**Params and plain `var` state are allowed, deliberately.** Both are present the moment the component mounts, and a
skeleton that knows how many rows to draw is a better skeleton than one guessing three.

The check covers the two ways data reaches a render tree: element **arguments** and `foreach` **sources**. A data read
buried in an `if` condition is not caught — it renders as a false branch rather than as anything harmful.

### Draw the shape, at the real size   {#geometry}
The bar to aim for is that **nothing moves when the data arrives**: the skeleton occupies the same box the loaded
content will. A placeholder that is the wrong height is worse than none, because it promises a layout and then breaks
it — the reflow it causes is the exact problem a skeleton exists to prevent.

In practice that means fixing the dimensions rather than letting a placeholder collapse: give each stand-in row the
height its real row will have, and the container the gap it will have.

A skeleton is **static by default**. If you want the shimmer, it is an ordinary [`animation`](https://osysharp.com/reference/ui/animation/) applied
with a style prop — the platform ships no privileged pulse, because the timing is a house-style decision.

### It survives an export   {#round-trip}
`skeleton { }` persists into the application model as a second render tree and is regenerated by the decompiler, so a
component exported and recompiled keeps it. Worth stating only because it did not always: the block was persisted and
read back by nothing for its whole first life, which is invisible in the output — the component still renders, just
with nothing on screen while its first read is in flight, which is the entire point of the block.

## Examples       {#examples}

### A child that holds its shape while its rows load   {#example-child}
```osy title="the ordinary case — the skeleton lives on the component that reads" test app=ui-skeleton
entity Order { [Required] string Reference; decimal Total; }

[Composable]                     // it is dropped into a public page; its own read stays gated
component RecentOrders() {
  live var orders = Order.OrderByDescending(o => o.Total).ToList();

  render {
    Stack(gap: 2) {
      foreach (var o in orders) { Text(o.Reference); }
    }
  }
  skeleton {
    // The same frame, with the row's real height — so nothing reflows when the rows arrive.
    Stack(gap: 2) {
      Box(h: "14px", w: "240px");
      Box(h: "14px", w: "240px");
      Box(h: "14px", w: "240px");
    }
  }
}

[Page("/orders")]
[Render(CSR)]
[AllowAnonymous]
component OrdersPage() {
  render { RecentOrders(); }
}
```

### Sizing the stand-in from a param   {#example-param}
A param is present at mount, so the caller can tell the skeleton how much to draw:

```osy syntax
component RecentOrders(int rows = 3) {
  live var orders = Order.OrderByDescending(o => o.Total).Take(rows).ToList();

  render   { foreach (var o in orders) { Text(o.Reference); } }
  skeleton { foreach (var i in placeholders) { Box(h: "14px", w: "240px"); } }   // `rows` and plain state are fine
}
```

### What the compiler refuses   {#example-refused}
```osy syntax
component RecentOrders() {
  live var orders = Order.ToList();

  render   { foreach (var o in orders) { Text(o.Reference); } }
  skeleton {
    foreach (var o in orders) { Box(h: "14px"); }   // ERROR: a `skeleton` block cannot read 'orders' —
  }                                                 // it renders precisely while that data is still loading
}
```

## See also       {#see-also}
- [Pending](https://osysharp.com/reference/ui/pending/) — the in-flight feedback for actions and refetches, which is the *other* waiting state.
- [A failing query](https://osysharp.com/reference/ui/query-failure/) — when the read comes back refused or broken, that region says so in place.
- [animation — looping motion with no destination state](https://osysharp.com/reference/ui/animation/) — `animation` blocks, if you want the stand-in to shimmer.
- [[Composable] — presentational components in public pages](https://osysharp.com/reference/ui/composable/) — why a composed child needs the attribute to render inside a public page.
- [component](https://osysharp.com/reference/ui/component/) — components, `render`, and the members a skeleton may read.
