Render Surface
Draws the interface an agent composed as A2UI messages, checked against the Mlola Render catalog and built from Mlola components.
Rendering Render Surface…
Installation
Copy the source into your project with the CLI. It lands under @/components/ui and is yours to edit.
npx mlola-ui add render-surfaceUsage
"use client";
import * as React from "react";
import { RenderSurface } from "@/components/ui/render-surface";
// What an agent sent: a surface, its components as a flat list, and the data its field is bound to.
const messages = [
{ version: "v0.9.1", createSurface: { surfaceId: "feedback", catalogId: "https://ui.mlola.com/a2ui/catalog/v1" } },
{
version: "v0.9.1",
updateComponents: {
surfaceId: "feedback",
components: [
{ id: "root", component: "Card", children: ["title", "note", "send"] },
{ id: "title", component: "Heading", level: 3, text: "How was your order?" },
{ id: "note", component: "Textarea", label: "Tell us more", value: { path: "/note" }, required: true },
{ id: "send", component: "Button", text: "Send feedback", action: { event: { name: "send_feedback", context: { note: { path: "/note" } } } } },
],
},
},
{ version: "v0.9.1", updateDataModel: { surfaceId: "feedback", path: "/note", value: "" } },
];
export default function Example() {
// The action goes back to the agent; here its context is shown instead.
const [sent, setSent] = React.useState("");
return (
<>
<RenderSurface messages={messages} onAction={(message) => setSent(String(message.action.context.note))} />
<p role="status">{sent ? `Sent to the agent: ${sent}` : ""}</p>
</>
);
}Styling comes from the engine stylesheet and the nearest theme. Set it once at the root:
<html data-theme="graphite" data-mode="light">HTML
The markup the example above renders, at rest. The interaction lives in the React component; from another framework, set the attributes below as the state changes.
<div class="ml-render">
<div class="ml-render-surface" data-surface="feedback" data-theme="graphite" data-mode="system" data-themed="">
<div class="ml-card" data-variant="default">
<div class="ml-card-content ml-stack">
<h3 class="ml-render-heading">How was your order?</h3>
<div class="ml-input-field">
<label class="ml-input-label" id="render-surface-1-feedback-note_40-label" for="render-surface-1-feedback-note_40">Tell us more<span class="ml-required-mark" aria-hidden="true">*</span></label>
<textarea class="ml-textarea" id="render-surface-1-feedback-note_40" rows="4" required="" maxLength="4000" style="--ml-textarea-max-rows:12"></textarea>
</div>
<button class="ml-button" type="button" data-variant="primary" data-size="md" id="render-surface-1-feedback-send_40"><span class="ml-button-label">Send feedback</span></button>
</div>
</div>
</div>
<p class="ml-visually-hidden" role="status"></p>
</div>
<p role="status"></p>Options
| Prop | Values | Default | Description |
|---|---|---|---|
theme | surfaceinherit | surface | "surface" draws in the theme the agent named; "inherit" keeps the theme of the page around it. |
Every prop with a fixed set of values, read from the exported TypeScript types with its doc comment. Open the Code tab above for the rest of the API.
Classes and attributes
A class names each element; data-* and aria-* attributes carry its variant and state. The values listed are the ones the stylesheet draws; the default needs no attribute.
| Class | Attributes |
|---|---|
ml-render | — |
ml-render-surface | data-themed |
ml-render-heading | — |
ml-render-text | — |
Accessibility and theming
- Semantic elements, accessible names and state relationships are part of the contract.
- Keyboard, pointer and touch paths are operable; focus stays visible in every theme.
- Light and dark modes pass WCAG AA contrast in all 5 canonical themes.
- Reduced motion and forced colors keep meaning and operation.
Dependencies
Installed alongside this component. No third-party UI runtime is ever added.