Summary#
Where a component sits, how much room it gets and how it arrives are decisions about the page around it, so the caller writes them on the call:
[Route("/orders")] [AllowAnonymous]
component OrdersPage() {
int fresh = 3;
render {
Row(gap: 2, align: Align.Center) {
Text("Orders", grow: 1);
Badge($"{fresh} new", tone: Tone.Primary, ml: 2, animationKey: fresh);
Button("Refresh", onPress: Refresh, alignSelf: AlignSelf.End);
}
}
action Refresh() { fresh = fresh + 1; }
}Signature#
<Component>(<args>…, <placement prop>: <value>)
// m mx my mt mb ml mr · w h minW maxW minH maxH · grow shrink basis alignSelf order colSpan rowSpan gridArea
// animation animationDelay exitAnimation animationKey · labelThe values are the ones the same prop takes on an atom (style props): spacing steps, lengths, enum members, declared animations.
Description#
Which element do they land on?#
The component's ROOT — the element its render writes at the top level. A component whose root is another
component passes them on, so Badge(…) inside your own [Composable] component Status takes the m: written on
Status(…, m: 2). Each arm of a top-level if is the root while it shows.
Who wins?#
The caller. The value is written after the component's own on that element, and an inline value beats a variant's
class — so a Badge whose own recipe sets a width takes w: from the call.
Naming one — label:#
A component used twice on one screen needs two names, and it cannot know them — so the caller gives it one. label:
lands on the root as its accessible name (aria-label), which is also what Assert.Visible(…, within: "…") scopes
to in a test. It REPLACES a name the component gives its root itself; an element has one name.
[Composable]
component Panel(string title) {
render { Stack(gap: 2, p: 3, label: title) { Text(title, fontWeight: FontWeight.Medium); Slot; } }
}
[Route("/lines")] [AllowAnonymous]
component LinesPage() {
render {
Row(gap: 3) {
Panel("Open", label: "Open on next") { Text("4 slices"); }
Panel("Open", label: "Open on maint-4.2") { Text("1 slice"); }
}
}
}A component that declares a label parameter itself — the kit's Badge does — receives the argument instead, as
below.
What is refused?#
- A prop that paints —
bg,color,border,fontSize,rounded,shadow, … A component's look is its own vocabulary (Badge(…, tone: Tone.Success)), so twenty screens cannot drift apart; to paint around it, wrap it in aBoxand style theBox. The refusal names the component's own parameters. - A component with no one root element — two elements side by side at its top level, a
foreach(one root per row), the caller's own content (Slot) or a foreign control. The refusal says which, and writes the wrap.
Badge("Late", bg: Colors.Danger); // ✗ bg PAINTS — use Badge's own vocabulary:
Badge("Late", tone: Tone.Danger); // ✓
Box(bg: Colors.Muted, p: 2) { Badge("Late"); } // ✓ or paint a wrapper around itWhat if the component has a parameter with that name?#
Then it is that parameter, as a named argument is in C#: Chart(rows, h: 240) passes 240 to a Chart that declares
int h, and the chart uses it where it chooses. Only a name the component does not declare is a placement prop.
Why not a wrapper Box?#
A wrapper is an extra element, and it changes layout by being there: in a Row, the flex item is now the Box,
not the control, so grow: and alignSelf: on the control inside it no longer mean anything. Written on the call,
they land on the element the parent actually lays out.
See also#
- style props — every style prop and the values it takes
- component — declaring a component, its parameters and
[Composable] - animationKey — play an animation again when a value changes; a line that draws itself — replaying an animation when a value changes