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

Reference / UI

Slot (child content)

Slot; — the point where a component renders the children its caller passed it

A `Slot` marks where a component renders the content block its caller wrapped around it. Writing `Card { Text("hi"); }` passes `Text("hi")` as Card's children; Card renders them wherever it writes `Slot`. The passed content evaluates in the CALLER's scope, so a wrapper component (a card, a panel, a dialog) can frame arbitrary content without knowing what it is.

stable6 examples compiled by CIuiauthoringcomposition

Summary#

A Slot is the point where a component renders the children its caller passed it — the content projection mechanism (React's children, Vue's <slot>). A component that writes Slot in its render tree is a wrapper: its caller supplies a content block, and the wrapper decides where that content lands.

[Composable] component Card() {
  render { Box { Slot; } }        // whatever the caller wraps in a Card lands here, inside the box
}

Calling Card { Text("hi"); } renders Text("hi") inside Card's box. The card frames the content; it never needs to know what the content is.

Signature#

Slot;              // the default slot — renders the caller's children block here
Slot("header");    // a NAMED slot — renders the caller's `slot header { … }` fill here
Slot(id: field);   // …and NAME what lands here, so the wrapper's own caption can point at it

A caller passes the default content by writing a content block after a component call, and fills a named slot with slot <name> { … }:

Card {             // the block below is Card's default children
  slot header {     // fills Card's Slot("header")
    Text("Title");
  }
  Text("body");     // untagged → Card's default Slot
}

Description#

A component call takes an optional trailing { … } children block. Those child nodes are handed to the called component as its slot content. Wherever that component writes Slot, the caller's children render in place.

The projected content evaluates in the caller's scope, not the wrapper's. This is what makes wrappers reusable: the content can read the caller's own state, props, and loop variables — the wrapper only positions it.

[Composable] component Panel() {
  render { Box { Slot; } }
}

[Composable] component Greeting(string who) {
  render {
    Panel { Text(who); }         // `who` is Greeting's prop — resolved in Greeting's scope, not Panel's
  }
}

A wrapper that writes Slot but whose caller passes no children renders nothing at that position (never an error). A component that never writes Slot simply ignores any children a caller passes.

Named slots#

A wrapper with more than one insertion point gives each a name: Slot("header"), Slot("footer"). A caller fills a named slot with a slot <name> { … } block; untagged children still fill the default Slot.

[Composable] component Card() {
  render {
    Box {
      Slot("header");    // the header region
      Slot;              // the default region
    }
  }
}

[Page("/")] [AllowAnonymous] [Render(CSR)]
component Home() {
  render {
    Card {
      slot header { Text("Title"); }   // → Card's Slot("header")
      Text("body");                    // → Card's default Slot
    }
  }
}

The slot name is checked at compile time: filling a slot the wrapper doesn't declare (a typo, or a slot that doesn't exist) is a compile error, so a mis-named fill is caught before it ships. An editor completes the available slot names from the wrapper you're calling. A named slot the caller doesn't fill renders nothing (it's optional).

Slots render identically whether a route is delivered server-side (pre-rendered HTML) or client-side, so a page built from wrappers hydrates without a flash.

Naming what lands in a slot#

A wrapper renders the caption and the caller renders the control, on two sides of a boundary. So a form component looks perfectly labelled and is labelled for exactly one audience: people who can see the layout.

Slot(id: <handle>) closes that in the one direction that is well-defined — the wrapper declares the handle and claims the content it is filled with. The caller writes nothing:

[Composable] component Field(string label) {
  render {
    Stack(gap: 1) {
      Text(label, labelFor: field);   // a real <label>, and it names…
      Slot(id: field);                // …whatever the caller puts here
    }
  }
}

[Page("/signup")] [AllowAnonymous] component SignUp() {
  string name = "";
  render { Field("Your name") { Input(value: name); } }
}

The caption is written once, at the call site, and the pair is a real <label for> — so clicking the words focuses the field, which on a form of small controls is most of the hit area. Writing label: "Your name" on the Input instead gives an accessible name and neither of those: the caption is then written twice, with nothing keeping the two in step.

The fill must be a single element. for= names one element; with two roots there is no answer, and the first one takes the name. Where a slot legitimately holds several things, name the control directly with label:.

Examples#

A reusable card wrapper framing page-specific content:

component Card() {
  render { Box { Slot; } }
}

[Page("/welcome")]
[Render(SSR)]
component Welcome() {
  render {
    Card {
      Text("Welcome");
    }
  }
}

A wrapper with a named region plus its default content — the caller fills the named slot by name (a mis-typed name would be a compile error) and leaves the rest for the default slot:

component Panel() {
  render {
    Box {
      Slot("header");
      Slot;
    }
  }
}

[Page("/article")]
[Render(SSR)]
component Article() {
  render {
    Panel {
      slot header { Text("Title"); }
      Text("Body");
    }
  }
}

See also#

Related

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…

layout primitives

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

routes and pages

How a component becomes a page: it declares a route with `[Page("/catalog/{slug}")]`, and navigating to a matching path…