Proa UI
Interactivity
Native controls, data-attribute behavior, action bindings, and rsjs controlled components.
proa_ui starts with server-rendered markup. Components add interactivity in three layers: native browser behavior, stable data attributes for small client controllers, and rsjs controlled components when the UI needs reactive state.
Native First
Prefer native semantics when they solve the problem:
- Buttons render real
<button>elements withtype,disabled, and loading state support. - Form controls render real inputs, textareas, selects, checkboxes, and radio groups.
- Progress renders
role="progressbar"with ARIA values. - Menus and dialogs render role and ARIA attributes where the component requires them.
Native behavior keeps the baseline HTML useful before any client script runs.
Data Attribute Controllers
Interactive primitives expose data hooks for client behavior:
<button data-dialog-trigger>Open</button>
<div data-dialog-content>...</div>
<div data-dropdown-content data-side="bottom" data-align="start">...</div>
Those attributes let small controllers attach behavior without requiring a runtime virtual DOM. Keep selectors stable and component-specific. Do not rely on Tailwind classes as JavaScript selectors.
Action Bindings
Some components accept ActionDataStatic handlers and emit data attributes for the action runtime. Button supports onclick, onblur, and onfocus.
ActionDataStatic comes from proa_actions, which Proa UI does not re-export. Add it alongside the installed crate:
proa_actions = { git = "https://github.com/Proa-Labs/proa.git" }
use proa_actions::ActionDataStatic;
use crate::components::{Button, ButtonType};
const SAVE_ACTION: ActionDataStatic = ActionDataStatic::new("save");
Button {
button_type: ButtonType::Submit,
children: Some("Submit"),
onclick: Some(SAVE_ACTION),
..Button::default()
}
The rendered element carries the action identifier in a data-* attribute, which keeps the server-rendered HTML explicit and inspectable.
Reactive Attributes
Components can accept ReactiveAttr<T> when a value may be static or bound to client state. Button uses this for disabled and loading:
use crate::components::Button;
Button {
disabled: true.into(),
loading: false.into(),
children: Some("Processing"),
..Button::default()
}
Static values render directly. Bound values also emit binding attributes so client code can update the element.
Controlled Components
The rsjs feature, enabled by default, exposes controlled wrappers such as:
ControlledInputControlledCheckboxControlledRadioGroupControlledSelectControlledTabsControlledCalendar
Use controlled components when the browser needs to update state without a full page round trip. Use the plain server-rendered components when static HTML or normal form submission is enough.
Hydration Boundary
Keep client behavior close to the component that needs it:
- Prefer a small controller for disclosure, menu, tooltip, and dialog behavior.
- Prefer
rsjscontrolled components for stateful form UI. - Prefer server-rendered links and forms for navigation and submission.
- Avoid adding client state to presentation-only components such as
Badge,Card,Separator,Kbd, orSkeleton.