Proa UI
Composition
How to compose proa_ui components with children, fragments, and stable data attributes.
proa_ui components are ordinary Rust values. Compose them inside html_sync! by placing component structs in { ... } slots. Most components use a C = () generic for children, so a child can be text, another component, or an html_sync! fragment.
Simple Text Children
use crate::components::{Button, ButtonColor};
html_sync! {
{Button {
color: Some(ButtonColor::Primary),
children: Some("Save changes"),
..Button::default()
}}
}
Use plain string children when the component only needs text.
Structured Children
Use html_sync! fragments when a component has nested parts:
use proa_macros::html_sync;
use crate::components::{
Button, ButtonVariant, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
};
html_sync! {
{Card {
class: Some("max-w-md"),
children: Some(html_sync! {
{CardHeader {
children: Some(html_sync! {
{CardTitle { children: Some("Billing"), ..Default::default() }}
{CardDescription {
children: Some("Manage plan and invoice settings."),
..Default::default()
}}
}),
..Default::default()
}}
{CardContent {
children: Some("Your current plan renews on the next billing cycle."),
..Default::default()
}}
{CardFooter {
children: Some(html_sync! {
{Button {
variant: Some(ButtonVariant::Outline),
children: Some("Update plan"),
..Button::default()
}}
}),
..Default::default()
}}
}),
}}
}
This keeps all rendering in the synchronous path. Reach for async composition only when the render tree must await data.
Data Attributes
Every component root should expose a stable data-* hook:
<span data-badge data-variant="default">New</span>
<div data-card>...</div>
<button data-button data-variant="solid">Save</button>
Compound components expose part-specific hooks such as data-card-header, data-card-title, data-scroll-area-viewport, or data-dropdown-item. Use those hooks for tests, CSS, and lightweight client behavior.
Multipart components additionally publish one canonical anatomy across static, delegated-runtime, rsjs, Markdown, and generated render targets:
<div data-scope="tabs" data-part="root">
<div data-scope="tabs" data-part="list">
<button data-scope="tabs" data-part="trigger">Account</button>
</div>
<div data-scope="tabs" data-part="content">...</div>
</div>
data-scope and data-part are the portable contract. Component-specific
attributes remain useful behavior hooks, but must describe the same parts
rather than create a second anatomy.
Target those attributes in tests and integration CSS. Do not build selectors out of generated class strings: classes are an implementation detail and change with the recipe, while part names stay stable once published.
Writing your own
A component is any type implementing WebRenderSync, so your own compose alongside the library's with no registration step. See Styling and variants for the shape components follow.