Summary#
A plain Slot places the caller's content once. Slot(item) renders it once per item, with a different value
each time — so one component can serve a list of anything.
Signature#
// in the component — pass the item
Slot(<value>);
// at the call site — receive it
SomeComponent(items: xs) { x => … }Description#
Some components are about a collection: a picker, a list, a table. The component knows how to lay the collection
out; only the app knows what one row should say. Slot(item) is what connects those.
The component passes the item:
[Composable] component Picker<T>(T[] options) {
render {
Stack {
foreach (var o in options) { Slot(o); }
}
}
}The caller supplies a template, naming the value it receives:
Picker(options: customers) { c =>
Row { Text(c.Name); Hint(c.Region); }
}That block is a template, not content: it runs once per Slot(o), and c is a different customer each time.
The template still sees everything around it. Only the named value comes from the component; the rest of the expression resolves where you wrote it, so a page's own state and the item can appear together:
string search = "";
Picker(options: customers) { c =>
Text(c.Name, bold: c.Name == search); // `c` from Picker, `search` from the page
}A plain Slot is unchanged. A component that just wraps its caller's content — a card, a panel, a dialog —
writes Slot; exactly as before. You only need the argument when the same content has to render more than once with
different values.
A component may use both. Slot(current) for the closed state of a dropdown and Slot(o) inside its list both
render the same template, with different values — which is usually what you want, since the selected row should look
like the rows it was chosen from.
A repeated slot needs a template, and the compiler says so#
A fill is placed, not copied. The caller builds its content once and the component moves it to wherever the
Slot is — so a Slot reached once per row can only ever end up holding it in the last row, and every earlier
row renders empty. Both halves of that mismatch are compile errors rather than a blank page:
[Composable] component Rows<T>(T[] rows) {
render { foreach (var r in rows) { Stack { Slot("cell"); } } }
}
Rows(rows: xs) { slot cell { Text("hi"); } }
// ✗ 'Rows' renders `Slot("cell")` inside a `foreach`, so this fill would be placed once per row …The fix is two-sided, and so is the message: the caller writes a template, and the component gives that template
a datum to render with. A template is only ever invoked by Slot(<name>, <datum>), so adding r => alone swaps a
last-row-only page for an empty one — which is refused too, naming the component's half.
[Composable] component Rows<T>(T[] rows) {
render { foreach (var r in rows) { Stack { Slot("cell", r); } } } // ← hand each invocation its row
}
Rows(rows: xs) { slot cell { r => Text(r.Name); } } // ← receive itTwo mutually exclusive if arms are not repetition. A component that writes the same Slot in a wide arm and a
narrow one renders one of them at a time, so plain content is correct there and is accepted.
If a caller passes plain content to a component that expects a template, nothing renders at that slot — the same as any unfilled slot. Slots are optional by design.
Examples#
A picker that owns the list and lets its caller own the row:
entity Customer { [MaxLength(100)] string Name; }
[Composable] component Picker<T>(T[] options) {
render {
Stack {
foreach (var o in options) { Slot(o); }
}
}
}
component Home() {
live var rows = Customer.ToList();
render {
Picker(options: rows) { c =>
Text(c.Name);
}
}
}A named per-row cell — the shape the refusals above steer to:
entity Customer { [MaxLength(100)] string Name; }
[Composable] component Rows<T>(T[] rows) {
render {
Stack {
foreach (var r in rows) { Row { Slot("cell", r); } }
}
}
}
component Home() {
live var cs = Customer.ToList();
render {
Rows(rows: cs) { slot cell { c => Text(c.Name); } }
}
}See also#
- component — declaring a component and its parameters
- [Composable] — presentational components in public pages — marking a presentational component so any page can render it