Composition
Compound components as Rust builders: ordered children, composed parts, and render closures.
1Card::new().child(CardHeader::new())A card is a Card holding a CardHeader holding a CardTitle. Each part is its own builder, and you nest them in layout order. Three patterns cover every component, and telling them apart is most of learning the API.
Every component is a builder
Components are builders implementing RenderOnce. Props are methods; children are ordered .child(..) calls, and the order is the layout order:
1Button::new("save")
2 .child("Save") // ordered children: icon first,
3 .variant(Variant::Primary) // then the label text
4 .on_press(|_, _, _| { /* save */ })Parts are components too
A named part is its own builder, nested the same way. The parent keeps the padding and the geometry; the parts carry only their own text styling:
1Card::new()
2 .child(CardHeader::new().child(CardTitle::new().child("Invoice")))
3 .child(CardContent::new().child("Due in 14 days"))
4 .child(CardFooter::new().child(Button::new("pay").child("Pay")))Composed parts draw only where composed
A part that renders conditionally behaves the same here. A modal draws its close X only if you compose one, so omitting it is how you get a modal without one —Modal has no boolean for it. Popover takes show_close_button instead. Each component documents its own rule.1// The X is drawn only where it is composed.
2Modal::new()
3 .id("confirm")
4 .is_open(open)
5 .title("Delete project")
6 .child(ModalCloseTrigger::new())Closures receive computed values
Where a part needs a value the parent computes — the sort direction of a column header, the active page of a pagination link — the builder hands that value to a closure:
1// The builder already computes the sort direction,
2// so it hands it to a closure instead of asking for a part.
3Table::new(vec!["Name".into(), "Size".into()])
4 .indicator(|direction| match direction {
5 SortDirection::Ascending => chevron_up(),
6 SortDirection::Descending => chevron_down(),
7 })The others are Pagination::link(|page, is_active|), InputOTP::slot(|index, Option<char>|), Slider::thumb(|index, value|), Dropdown::item_content(|key, state|) with the item key and its interaction state, and DateField/TimeField's segment(|segment, text|).
Which one a component uses
Each component page lists its parts and slots under the Button API reference, generated from the same source the library is built from. When a part takes a value the parent computes, it appears there as a render closure rather than as a nested builder.
State is the other half of this: see State for which components hold their own value and which hand you an entity.