Content & Children
Beverly components are designed to be composed through the shared UiElement
tree. text(...), button(), input(...), checkbox(), alert(...),
toast(...), card(), list_item(...), form(...), radio(...), table(...),
tabs(...), modal(...), and custom component adapters can all be placed in
the same children collection.
The fundamental primitive for composition is:
#![allow(unused)]
fn main() {
.children(...)
}
A component can accept one child or many children.
#![allow(unused)]
fn main() {
app()
.children([text("Hello")])
.run()
}
Or:
#![allow(unused)]
fn main() {
app()
.children([
text("Hello"),
text("World"),
])
.run()
}
This simple rule provides a consistent way to build interfaces from small pieces.
Everything can be a child. Everything can be composed.
One Child or Many
children() accepts either a single value or a collection of values.
A single child is useful when the relationship is straightforward:
#![allow(unused)]
fn main() {
button()
.text("Save")
.children([ButtonChild::icon("save"), ButtonChild::text("Save")])
}
Multiple children allow a component to become a container:
#![allow(unused)]
fn main() {
button()
.children([
ButtonChild::icon("folder"),
ButtonChild::text("Open project"),
])
}
The API stays the same regardless of how many children a component contains.
There is no need to learn separate APIs for one child, two children, or a list of children.
Composition Over Special Cases
Beverly components are designed to be building blocks rather than complete screens.
A component can contain another component.
That component can contain more components.
Application
↓
Page
↓
Card
↓
Header
↓
Icon + Text + Button
The same composition model works at every level.
#![allow(unused)]
fn main() {
app()
.children([
text("Report").children([text("Quarterly financial report")]),
button().text("Open"),
input("Search").label("Search reports"),
checkbox().label("Include archived").checked(true),
card().children([text("Summary")]),
list_item("Recent activity").subtitle("Just now"),
form(Form::post("/save")).children([input("Name")]),
table(TableConfig::default()),
])
.run()
}
This makes components reusable without requiring the framework to predict every possible combination developers might need.
Build the primitive once. Compose it everywhere.
Named Sections
Card, Container (column() and row()), Form, Modal, Alert, Toast, Tabs, and
Sidebar implement .header(...), .body(...), and .footer(...) as real
composition sections. They map to top, middle, and bottom, regardless of
builder-call order. Each accepts one UiElement or an iterable; repeated calls
append to the selected section. In a sized panel, the body grows into the
available height so the footer stays below it.
For Card, Container, Form, and Tabs, ordinary .children(...) content becomes
part of the body when sections are used. Tabs apply sections within each tab
zone; Sidebar keeps its navigation controls in the body. Modal retains its
dialog title and close control. Alert and Toast retain their built-in message,
icon, and dismissal behavior alongside custom content.
Navbar and the standalone Footer use horizontal left/center/right regions instead. Table headers describe columns, not a generic panel header. Buttons, fields, typography, and other leaf components do not have vertical sections; compose them inside a sectioned container instead.
Some components have a meaningful internal structure.
A Card, for example, may have:
- Header
- Body
- Footer
For these components, Beverly can provide named sections alongside general children() composition.
#![allow(unused)]
fn main() {
card().children([
text("Account"),
text("Account information"),
button().text("Save"),
])
}
Named sections make the intent of the interface immediately obvious.
They also provide components with a way to express their own semantic structure without requiring developers to manually construct the underlying layout.
Sections Can Still Be Composed
Named sections follow the same content rules.
A section can receive a single value:
#![allow(unused)]
fn main() {
card()
.header(
text("Settings")
)
}
Or multiple values:
#![allow(unused)]
fn main() {
card()
.header([
icon("settings"),
text("Settings"),
button().text("Edit"),
])
}
The section itself becomes a composition boundary.
Card
├── Header
│ ├── Icon
│ ├── Text
│ └── Button
│
├── Body
│ └── Content
│
└── Footer
└── Actions
This allows a component to provide useful semantic structure without sacrificing the flexibility of general composition.
children() Is the Universal Escape Hatch
Named sections are useful when a component has a well-defined structure.
But developers should never be trapped by them.
children() remains the general composition primitive.
For a simple container:
#![allow(unused)]
fn main() {
container()
.children([
text("First"),
text("Second"),
text("Third"),
])
}
For a more structured component:
#![allow(unused)]
fn main() {
card()
.header(text("Title"))
.children([
chart(data),
table(rows),
])
.footer(button().text("Close"))
}
The component can provide convenient named sections while still allowing arbitrary content where appropriate.
This keeps the API expressive without creating a separate abstraction for every possible layout.
Content Is Data
Beverly treats component content as values that can be passed, returned, stored, and composed.
That means a component does not need to know where its content came from.
Content might be:
- Static text
- Another component
- A list of components
- Data-driven content
- Conditional content
- Application state
- AI-generated content
The component simply receives content and presents it according to its role.
#![allow(unused)]
fn main() {
card()
.header(text(user.name()))
.children([
text(user.email()),
badge(user.status()),
])
}
The View decides what should be presented.
The component decides how that content is presented.
The Model remains responsible for the underlying application data.
Consistent Composition
The same mental model should work throughout a Beverly application.
#![allow(unused)]
fn main() {
container()
.children([
navbar(),
card()
.header(text("Users"))
.children([
user_table(),
])
.footer([
button().text("Previous"),
button().text("Next"),
]),
])
}
Developers don’t need to memorize a different composition system for every component.
They learn one fundamental concept:
Component
↓
children()
↓
Child or Children
Named sections simply add semantic structure where it is useful.
Why This Matters for AI
A consistent composition model is particularly valuable when code is generated by LLMs.
The model does not need to invent a new layout abstraction for every component.
It learns a small vocabulary:
component()
children(...)
header(...)
body(...)
footer(...)
That makes generated code easier to predict and easier for humans to review.
A developer looking at:
#![allow(unused)]
fn main() {
card()
.header(...)
.children(...)
.footer(...)
}
can immediately understand the structure.
The code itself becomes a description of the interface.
Composition should be obvious from the code.
The Beverly Composition Rule
Beverly keeps content composition deliberately simple:
Use children() for general composition.
Use named sections when a component has meaningful semantic regions.
Accept one child or many children.
Keep components composable.
Don’t create a new abstraction when ordinary composition already solves the problem.
The result is a UI system where small primitives can become complex interfaces without the API becoming complex.
Small primitives. Simple composition. Unlimited combinations.