Let your agent answer with an interface.
A model composes forms, tables, charts and status from a catalog of real components. Every message is checked before anything shows, and what is wrong goes back to the model in words it can fix. It draws in your theme, with any model, wherever your agent runs.
Composed by real models
Each surface below is what a model sent back for one sentence, on October 13, 2026, drawn as it arrived and never edited. In that run 6 models passed 54 of 54 tries, 53 of them on the first.
Asked of Claude Sonnet 5.5: “How did sales go this quarter? Revenue in USD by month: July: Desk 12400, Chair 8300, Lamp 3100. August: Desk 13900, Chair 9100, Lamp 2800. September: Desk 16200, Chair 9900, Lamp 3600. By channel over the quarter: Web 48200, Stores 21500, Partners 9600. Show charts and one sentence of summary.”
Sales, July to September
Revenue reached $79,300 for the quarter, growing 25% from July to September, with desks the top product and the web channel bringing in about 61% of sales.
$79,300
Desks 54% of revenue
$29,700
+25% on July
61%
$48,200
Revenue by month and product
- Desk
- Chair
- Lamp
| month | Desk | Chair | Lamp |
|---|---|---|---|
| July | 12400 | 8300 | 3100 |
| August | 13900 | 9100 | 2800 |
| September | 16200 | 9900 | 3600 |
Revenue by channel
- Web
- Stores
- Partners
Press a button in the surface to see what your agent receives.
{"version":"v0.9.1","createSurface":{"surfaceId":"quarter_sales","catalogId":"https://ui.mlola.com/a2ui/catalog/pro/v1","theme":{"name":"graphite","mode":"system"}}}
{"version":"v0.9.1","updateComponents":{"surfaceId":"quarter_sales","components":[{"id":"root","component":"Stack","children":["title","summary","stats","charts"]},{"id":"title","component":"Heading","level":2,"text":"Sales, July to September"},{"id":"summary","component":"Text","text":"Revenue reached $79,300 for the quarter, growing 25% from July to September, with desks the top product and the web channel bringing in about 61% of sales."},{"id":"stats","component":"Grid","columns":"3","children":["stat-total","stat-sept","stat-web"]},{"id":"stat-total","component":"Stat","label":"Quarter revenue","value":"$79,300","change":"Desks 54% of revenue"},{"id":"stat-sept","component":"Stat","label":"September revenue","value":"$29,700","change":"+25% on July","tone":"success","trend":[23800,25800,29700]},{"id":"stat-web","component":"Stat","label":"Web share of revenue","value":"61%","change":"$48,200"},{"id":"charts","component":"Grid","columns":"2","children":["by-month","by-channel"]},{"id":"by-month","component":"Card","children":["by-month-title","by-month-chart"]},{"id":"by-month-title","component":"Heading","level":3,"text":"Revenue by month and product"},{"id":"by-month-chart","component":"BarChart","label":"Revenue in US dollars by month, split into desks, chairs and lamps","description":"Every product grows or holds steady into September, except lamps dip in August.","data":{"path":"/months"},"x":"month","series":[{"key":"desk","label":"Desk"},{"key":"chair","label":"Chair"},{"key":"lamp","label":"Lamp"}],"stacked":true,"format":"currency","currency":"USD"},{"id":"by-channel","component":"Card","children":["by-channel-title","by-channel-chart"]},{"id":"by-channel-title","component":"Heading","level":3,"text":"Revenue by channel"},{"id":"by-channel-chart","component":"DonutChart","label":"Quarter revenue in US dollars by channel","data":{"path":"/channels"},"name":"channel","value":"revenue","centerLabel":"revenue","legend":"below","format":"currency","currency":"USD"}]}}
{"version":"v0.9.1","updateDataModel":{"surfaceId":"quarter_sales","path":"/","value":{"months":[{"month":"July","desk":12400,"chair":8300,"lamp":3100},{"month":"August","desk":13900,"chair":9100,"lamp":2800},{"month":"September","desk":16200,"chair":9900,"lamp":3600}],"channels":[{"channel":"Web","revenue":48200},{"channel":"Stores","revenue":21500},{"channel":"Partners","revenue":9600}]}}}Asked of Deepseek v4.1 Flash: “Show weekly sign-ups for the last 8 weeks (412, 438, 455, 470, 468, 502, 531, 547) against a target of 500 a week, and whether the trend is up or down.”
Weekly sign-ups, last 8 weeks
547
+16 on the week before
478
-22 against the 500 target
3 of 8
Weeks 6 to 8
The trend is up
Sign-ups against a target of 500 a week
- Sign-ups
- Target
| week | Sign-ups | Target |
|---|---|---|
| Week 1 | 412 | 500 |
| Week 2 | 438 | 500 |
| Week 3 | 455 | 500 |
| Week 4 | 470 | 500 |
| Week 5 | 468 | 500 |
| Week 6 | 502 | 500 |
| Week 7 | 531 | 500 |
| Week 8 | 547 | 500 |
| Week | Sign-ups | Target | Against target |
|---|---|---|---|
| Week 1 | 412 | 500 | -88 |
| Week 2 | 438 | 500 | -62 |
| Week 3 | 455 | 500 | -45 |
| Week 4 | 470 | 500 | -30 |
| Week 5 | 468 | 500 | -32 |
| Week 6 | 502 | 500 | 2 |
| Week 7 | 531 | 500 | 31 |
| Week 8 | 547 | 500 | 47 |
Press a button in the surface to see what your agent receives.
{"version":"v0.9.1","createSurface":{"surfaceId":"weekly-signups","catalogId":"https://ui.mlola.com/a2ui/catalog/pro/v1","theme":{"name":"graphite","mode":"system"}}}
{"version":"v0.9.1","updateComponents":{"surfaceId":"weekly-signups","components":[{"id":"root","component":"Stack","children":["title","stats","trend-note","chart-card","table"]},{"id":"title","component":"Heading","level":2,"text":"Weekly sign-ups, last 8 weeks"},{"id":"stats","component":"Grid","columns":"3","children":["stat-latest","stat-average","stat-target"]},{"id":"stat-latest","component":"Stat","label":"Sign-ups last week","value":"547","change":"+16 on the week before","tone":"success","trend":{"path":"/signups"}},{"id":"stat-average","component":"Stat","label":"Average a week","value":"478","change":"-22 against the 500 target","tone":"warning"},{"id":"stat-target","component":"Stat","label":"Weeks above the 500 target","value":"3 of 8","change":"Weeks 6 to 8","tone":"success"},{"id":"trend-note","component":"Alert","tone":"success","variant":"soft","title":"The trend is up","text":"Sign-ups rose in six of the last eight weeks and passed the 500 target in the last three: from 412 in week 1 to 547 in week 8, 33% higher."},{"id":"chart-card","component":"Card","variant":"elevated","children":["chart-title","chart"]},{"id":"chart-title","component":"Heading","level":3,"text":"Sign-ups against a target of 500 a week"},{"id":"chart","component":"LineChart","label":"Weekly sign-ups against a target of 500 a week","description":"Sign-ups climb from 412 to 547 and sit above the target from week 6 on.","data":{"path":"/weeks"},"x":"week","series":[{"key":"signups","label":"Sign-ups"},{"key":"target","label":"Target","dashed":true}],"dots":true,"area":false},{"id":"table","component":"Table","caption":"Sign-ups each week against the target of 500","columns":[{"key":"week","label":"Week"},{"key":"signups","label":"Sign-ups","align":"right"},{"key":"target","label":"Target","align":"right"},{"key":"diff","label":"Against target","align":"right"}],"rows":{"path":"/weeks"}}]}}
{"version":"v0.9.1","updateDataModel":{"surfaceId":"weekly-signups","path":"/signups","value":[412,438,455,470,468,502,531,547]}}
{"version":"v0.9.1","updateDataModel":{"surfaceId":"weekly-signups","path":"/weeks","value":[{"week":"Week 1","signups":412,"target":500,"diff":-88},{"week":"Week 2","signups":438,"target":500,"diff":-62},{"week":"Week 3","signups":455,"target":500,"diff":-45},{"week":"Week 4","signups":470,"target":500,"diff":-30},{"week":"Week 5","signups":468,"target":500,"diff":-32},{"week":"Week 6","signups":502,"target":500,"diff":2},{"week":"Week 7","signups":531,"target":500,"diff":31},{"week":"Week 8","signups":547,"target":500,"diff":47}]}}Asked of Claude Haiku 5.5: “Let me edit the prices of five products in a grid, then save them.”
Edit product prices
Press a button in the surface to see what your agent receives.
{"version":"v0.9.1","createSurface":{"surfaceId":"price-edit","catalogId":"https://ui.mlola.com/a2ui/catalog/pro/v1","theme":{"name":"graphite","mode":"system"}}}
{"version":"v0.9.1","updateComponents":{"surfaceId":"price-edit","components":[{"id":"root","component":"Stack","children":["title","grid","save"]},{"id":"title","component":"Heading","level":2,"text":"Edit product prices"},{"id":"grid","component":"DataGrid","label":"Product prices, editable in the Price column","columns":[{"key":"product","label":"Product","pinned":true},{"key":"sku","label":"SKU"},{"key":"price","label":"Price (USD)","type":"number","align":"end","editable":true}],"rows":{"path":"/products"}},{"id":"save","component":"Button","variant":"primary","text":"Save prices","action":{"event":{"name":"save_prices","context":{"products":{"path":"/products"}}}}}]}}
{"version":"v0.9.1","updateDataModel":{"surfaceId":"price-edit","path":"/products","value":[{"product":"Linen throw","sku":"LT-001","price":68},{"product":"Oak serving board","sku":"OS-014","price":42},{"product":"Stoneware mug, set of four","sku":"SM-220","price":54},{"product":"Beeswax candle, pack of three","sku":"BC-307","price":18},{"product":"Walnut salad bowl","sku":"WB-452","price":75}]}}Asked of GPT-6 Sol: “A form to book a table for tonight: name, number of guests, time, and any allergies.”
Request a table for tonight
Choose your preferred time. Your table is not confirmed until the restaurant responds.
Leave blank if none.
Press a button in the surface to see what your agent receives.
{"version":"v0.9.1","createSurface":{"surfaceId":"tonight-table-request","catalogId":"https://ui.mlola.com/a2ui/catalog/v1","theme":{"name":"graphite","mode":"system"}}}
{"version":"v0.9.1","updateComponents":{"surfaceId":"tonight-table-request","components":[{"id":"root","component":"Stack","children":["heading","intro","form"]},{"id":"heading","component":"Heading","level":2,"text":"Request a table for tonight"},{"id":"intro","component":"Text","text":"Choose your preferred time. Your table is not confirmed until the restaurant responds."},{"id":"form","component":"Card","children":["name","guests","time","allergies","request"]},{"id":"name","component":"Input","label":"Name","value":{"path":"/form/name"},"required":true},{"id":"guests","component":"Select","label":"Number of guests","options":[{"label":"1","value":"1"},{"label":"2","value":"2"},{"label":"3","value":"3"},{"label":"4","value":"4"},{"label":"5","value":"5"},{"label":"6","value":"6"},{"label":"7","value":"7"},{"label":"8","value":"8"},{"label":"9","value":"9"},{"label":"10","value":"10"}],"value":{"path":"/form/guests"},"placeholder":"Select number of guests","required":true},{"id":"time","component":"Select","label":"Preferred time tonight","options":[{"label":"5:00 pm","value":"17:00"},{"label":"5:30 pm","value":"17:30"},{"label":"6:00 pm","value":"18:00"},{"label":"6:30 pm","value":"18:30"},{"label":"7:00 pm","value":"19:00"},{"label":"7:30 pm","value":"19:30"},{"label":"8:00 pm","value":"20:00"},{"label":"8:30 pm","value":"20:30"},{"label":"9:00 pm","value":"21:00"},{"label":"9:30 pm","value":"21:30"}],"value":{"path":"/form/time"},"placeholder":"Select a time","required":true},{"id":"allergies","component":"Textarea","label":"Any allergies?","value":{"path":"/form/allergies"},"hint":"Leave blank if none.","rows":3},{"id":"request","component":"Button","text":"Request table","action":{"event":{"name":"request_table","context":{"form":{"path":"/form"}}}}}]}}
{"version":"v0.9.1","updateDataModel":{"surfaceId":"tonight-table-request","path":"/form","value":{"name":"","guests":"","time":"","allergies":""}}}Asked of Gemini 3.8 Flash: “Show the status of my three latest orders: one delivered, one on its way, one delayed. Let me contact support.”
Your recent orders
Order #8492
DelayedErgonomic Office Chair • Placed on Oct 24
Shipment delayed
Order #8501
On its wayWireless Mechanical Keyboard • Estimated delivery: Tomorrow by 8 PM
Order #8410
DeliveredUSB-C Dual Monitor Dock • Delivered Oct 26 at front door
Need help with an order?
Our customer support team is available 24/7 to resolve shipping delays or damaged items.
Press a button in the surface to see what your agent receives.
{"version":"v0.9.1","createSurface":{"surfaceId":"order-status","catalogId":"https://ui.mlola.com/a2ui/catalog/v1","theme":{"name":"graphite","mode":"system"}}}
{"version":"v0.9.1","updateComponents":{"surfaceId":"order-status","components":[{"id":"root","component":"Stack","children":["heading","orders","support"]},{"id":"heading","component":"Heading","level":2,"text":"Your recent orders"},{"id":"orders","component":"Stack","children":["order_delayed","order_transit","order_delivered"]},{"id":"order_delayed","component":"Card","children":["delayed_header","delayed_desc","delayed_alert"]},{"id":"delayed_header","component":"Row","children":["delayed_title","delayed_badge"]},{"id":"delayed_title","component":"Heading","level":3,"text":"Order #8492"},{"id":"delayed_badge","component":"Badge","tone":"danger","text":"Delayed"},{"id":"delayed_desc","component":"Text","text":"Ergonomic Office Chair • Placed on Oct 24"},{"id":"delayed_alert","component":"Alert","tone":"warning","variant":"soft","title":"Shipment delayed","text":"Held at regional sorting hub due to weather conditions. Estimated delivery updated to Nov 2."},{"id":"order_transit","component":"Card","children":["transit_header","transit_desc","transit_progress"]},{"id":"transit_header","component":"Row","children":["transit_title","transit_badge"]},{"id":"transit_title","component":"Heading","level":3,"text":"Order #8501"},{"id":"transit_badge","component":"Badge","tone":"info","text":"On its way"},{"id":"transit_desc","component":"Text","text":"Wireless Mechanical Keyboard • Estimated delivery: Tomorrow by 8 PM"},{"id":"transit_progress","component":"Progress","label":"Shipping progress","value":65,"max":100,"showLabel":true,"tone":"info"},{"id":"order_delivered","component":"Card","children":["delivered_header","delivered_desc"]},{"id":"delivered_header","component":"Row","children":["delivered_title","delivered_badge"]},{"id":"delivered_title","component":"Heading","level":3,"text":"Order #8410"},{"id":"delivered_badge","component":"Badge","tone":"success","text":"Delivered"},{"id":"delivered_desc","component":"Text","text":"USB-C Dual Monitor Dock • Delivered Oct 26 at front door"},{"id":"support","component":"Card","variant":"elevated","children":["support_title","support_text","support_actions"]},{"id":"support_title","component":"Heading","level":3,"text":"Need help with an order?"},{"id":"support_text","component":"Text","text":"Our customer support team is available 24/7 to resolve shipping delays or damaged items."},{"id":"support_actions","component":"Row","children":["contact_button"]},{"id":"contact_button","component":"Button","variant":"primary","text":"Contact support","action":{"event":{"name":"contact_support","context":{"recentOrderIds":["#8492","#8501","#8410"]}}}}]}}
{"version":"v0.9.1","updateDataModel":{"surfaceId":"order-status","path":"/","value":{}}}Asked of Claude Haiku 5.5: “Ask me three questions to set up notifications: email, push, and a quiet time.”
Set up notifications
Answer three questions. You can change these later.
1. Do you want email notifications?
Email notifications2. Do you want push notifications on this device?
Push notifications3. When should notifications stay quiet?
Press a button in the surface to see what your agent receives.
{"version":"v0.9.1","createSurface":{"surfaceId":"notification-settings","catalogId":"https://ui.mlola.com/a2ui/catalog/v1"}}
{"version":"v0.9.1","updateComponents":{"surfaceId":"notification-settings","components":[{"id":"root","component":"Stack","children":["heading","intro","email_q","email","push_q","push","quiet_q","quiet","actions"]},{"id":"heading","component":"Heading","level":2,"text":"Set up notifications"},{"id":"intro","component":"Text","text":"Answer three questions. You can change these later."},{"id":"email_q","component":"Text","text":"1. Do you want email notifications?"},{"id":"email","component":"Switch","label":"Email notifications","checked":{"path":"/form/email"}},{"id":"push_q","component":"Text","text":"2. Do you want push notifications on this device?"},{"id":"push","component":"Switch","label":"Push notifications","checked":{"path":"/form/push"}},{"id":"quiet_q","component":"Text","text":"3. When should notifications stay quiet?"},{"id":"quiet","component":"Select","label":"Quiet time","options":[{"label":"No quiet time","value":"none"},{"label":"21:00 to 07:00","value":"21:00"},{"label":"22:00 to 07:00","value":"22:00"},{"label":"23:00 to 08:00","value":"23:00"}],"value":{"path":"/form/quiet"}},{"id":"actions","component":"Row","children":["save"]},{"id":"save","component":"Button","variant":"primary","text":"Save notification settings","action":{"event":{"name":"save_notification_settings","context":{"form":{"path":"/form"}}}}}]}}
{"version":"v0.9.1","updateDataModel":{"surfaceId":"notification-settings","path":"/form","value":{"email":false,"push":false,"quiet":"22:00"}}}How it works
The messages are A2UI, an open format for interfaces an agent sends, so the same surface draws in your app and in a chat. Mlola gives the format a catalog, a check and the components.
The model is told the catalog
renderSystemPrompt writes the prompt from the catalog itself: every component, the props it takes and the values they allow, with worked examples. 25 components are free, 31 with Mlola Pro.
Every message is checked
The check holds what a schema cannot: one root, no loops, headings in order, numbers where a chart draws. A refused reply goes back to the model with its errors in words, and composeRender asks again.
It draws in your theme
Each component is accessible by construction and takes the theme you name, or the page's own. The model picks named values (a tone, a size) and never writes a color or a class.
Three ways to show what a model made
Each one works. They differ in what you build yourself, and in what happens when the model gets it wrong.
| Approach | When the model gets it wrong | Looks like your product | Accessible | What you build |
|---|---|---|---|---|
| The model writes HTML or code | It shows as written, or not at all. Safe only inside a sandbox. | While the prompt holds | When the model remembers | The sandbox |
| A JSON format of your own | Whatever your renderer does with it | Yes, your components | As far as your components are | The format, the prompt, the check and the renderer |
| Mlola Render | Nothing shows, and the model is told what to fix | Yes, in your theme | WCAG 2.2 AA, checked on every component in every theme | One call and one component |
Where it stops
- It draws only what the catalog holds: 25 components, 31 with Pro. A layout the catalog cannot say, it does not draw.
- Your own components cannot join the catalog: it holds Mlola's, each checked in every theme.
- A reply that stops in the middle of a message is refused, since it may have been cut off. It is never guessed at.
Runs where your agent runs
Your own server, any model
mlola-ui/render/ai asks a model for a surface, reads the reply as it streams and checks it. The docs show the call for chat completions, Anthropic and Gemini.
How to use it in your own server, any modelChatGPT and Claude
The MCP server's render_ui shows a surface in the conversation as an MCP App, and what a person presses comes back as their next message.
How to use it in chatgpt and claudeReact
Render Surface draws the free catalog; Render Pro adds charts, stats and a data grid. The AI Chat block draws them inside its answers.
How to use it in reactA page with no framework
@mlola-ui/behavior/render draws the same messages into plain HTML, from one script.
How to use it in a page with no framework
What it costs
Free
The catalog of 25 components, the check, the prompt and the loop, the renderers and the MCP tools. MIT, for commercial use too.
Start with the docsMlola Pro
Render Pro and the Pro catalog: charts, stats and a data grid a person sorts and edits. Part of the one-time Pro license.
See Pro pricing