Proa UI

Composition

How to compose proa_ui components with children, fragments, and stable data attributes.

Open Markdown

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.

Search

Type at least 2 characters