Skip to content
Mlola UI
  • Introduction
  • Theming
  • Quality
  • Versions
  • Coding agents
  • Mlola Render
  • AI integration
  • All components
  • Migrate from shadcn/ui
  • Workbench
  • Icons
  • AccordionA collapsible content panel supporting single and multiple open modes
  • Activity HeatmapActivity by day in GitHub's calendar grid, with quantile shading, a keyboard grid and hover detail
  • AlertA status banner with variants, dismiss action, and optional action slot
  • App ShellA resizable, foldable sidebar with sections and items that becomes a drawer on small screens
  • AvatarUser avatar with image, initials fallback, and status indicator
  • BadgeA small status or label badge with color variants, dot indicator, and removal
  • Bar ChartBars per category, grouped or stacked, standing up or lying across, with rounded ends and keyboard reading
  • Block EditorA block editor: slash commands, Markdown shortcuts, to-dos, callouts, drag to reorder, a selection bubble and AI that streams into the page
  • BotAn expressive companion whose body morphs between shapes on springs and whose face carries the assistant's state
  • BreadcrumbNavigation breadcrumb trail with custom separators and responsive collapse
  • ButtonA versatile button component with multiple variants, sizes, and states
  • CalendarA month grid for one date, a range or several, with min and max, blocked days, two-month view and full keyboard paging
  • CanvasAn infinite surface to pan and zoom, with a dotted grid, zoom controls, floating panels and collaborators' cursors
  • Canvas ToolbarA floating tool palette with single-key shortcuts, roving focus and room for undo and redo
  • CardA compound card with header, content, and footer slots in four surface variants
  • CarouselOne slide at a time with previous/next controls and a roving dot group
  • ChartThe shared chart frame: scales, nice ticks, monotone curves, stacking, legend, tooltip and an accessible data table
  • CheckboxAn accessible checkbox with label, description, error, and indeterminate state
  • CitationNumbered inline sources with hover previews and a matching source list
  • Code BlockHighlighted code with a filename, line numbers, marked lines and copy; its palette is Mlola's contrast-solved text roles
  • Color PickerA color field with a saturation area, hue and opacity, hex, rgb or oklch, an eyedropper, presets and a contrast check
  • ComboboxA text field that filters a long list as you type, with grouping, descriptions and creating what is missing
  • Command MenuThe ⌘K palette: ranked commands with shortcuts, keyboard navigation and matched letters bolded
  • Commit HistoryA git log with a branch graph, refs, CI checks, authors and relative times
  • Confidence TextAn answer that marks where the model is unsure, with the confidence spoken to screen readers
  • Context CardsThe passages a model retrieved before answering, with source, location, relevance and the query's words marked
  • Context MenuA right-click menu built for what was clicked, with shortcuts, typeahead, long-press on touch and Shift+F10
  • Context MeterHow full the context window is and on what, warning before answers get cut short
  • ConversationA message log that follows new content and offers a way back when the reader scrolls up
  • Copy ButtonCopies a value to the clipboard and confirms it, with the outcome spoken to screen readers and a refusal explained
  • Data GridA spreadsheet-grade table: virtualized rows, pinned and resizable columns, sorting, cell ranges, inline editing, copy and paste, totals
  • Date PickerA date field that opens a calendar, for one date or a range with presets, shown in the reader's format and stored as ISO
  • DialA value chosen by turning a knob, with a press on the ring to jump, a hold to turn by finger, Escape to undo a turn and the keys of a slider
  • Diff ViewUnified or split file diffs from before/after text or a patch, with in-line change marks and folded unchanged lines
  • Donut ChartParts of a whole as a ring whose center reads the slice you point at
  • Dropdown MenuA keyboard-navigable dropdown menu with icons, shortcuts, and danger items
  • DropzoneDrop, choose or paste files; checked for type, size and count before they are handed over
  • Editor TabsOpen files across an editor: unsaved dots, git status, preview tabs and names that disambiguate twins
  • Empty StateWhat a screen says when there is nothing to show yet, and the way forward
  • Eval GridTest cases against model and prompt variants: pass rates, scores, latency and cost, disagreements, and each answer diffed against the expected one
  • File ChangesAn agent's changed files as one summary line that opens into each file's status and size of change
  • File TreeA project's files as a keyboard-first tree with folder guides, file-type badges and git status
  • Filter BarFilters as editable sentences, "Status is In progress", with an applyFilters helper
  • Funnel ChartStages of a funnel with step conversion and drop-off between each
  • GanttA roadmap timeline: grouped bars you drag and resize, milestones, dependency arrows, today and three zoom levels
  • Hover CardA preview card that opens when the pointer rests on a link or name, and on keyboard focus
  • InputText input with variants, sizes, label, hint, and error states
  • KanbanColumns of cards that move by drag or keyboard, with room-making drops, edge scrolling, folding columns and limits
  • KbdKeys and platform-aware shortcuts: mod+k reads ⌘K on a Mac and Ctrl K elsewhere, spoken as words
  • Line ChartLines and areas over a sequence, stacked or overlaid, with a crosshair, keyboard reading and dashed targets
  • MessageOne conversation turn with actions, feedback, answer versions and a facts line
  • MinimapThe whole canvas in miniature, with the view outlined; press or drag to move there
  • ModalAccessible dialog overlay with focus trap, escape handling, and scroll lock
  • Model CompareThe same prompt streamed to several models side by side, blind by default, with time to first token, speed and a vote
  • Model PickerChoose a model by speed, reasoning depth and context window, built on Select
  • Node GraphA node editor for agent workflows: drag nodes, connect ports, refuse loops and watch a run flow through the edges
  • Number InputA number field formatted at rest in the reader's locale, with steppers you can hold, arrows, bounds and precision
  • OrbThe assistant's presence: idle, listening, thinking and speaking, colored by the theme and driven by live audio
  • OTP InputA one-time code as separate boxes that type, delete, paste and autofill like one field
  • PaginationPage navigation with prev/next, page numbers, and ellipsis for large ranges
  • Password InputA password field with a show and hide toggle and, when choosing a new password, a meter that says how strong it is and what would make it stronger
  • PopoverA non-modal layer anchored to its trigger that flips and shifts to stay on screen
  • Priority IconA work item priority as signal bars, with a filled alert for urgent
  • ProgressHorizontal and circular progress indicators with variants and indeterminate mode
  • Prompt InputA growing composer that sends on Enter, never mid-IME, with attachments, tools and a stop button
  • Prompt PlaygroundA system prompt and a template whose variables become fields, with model settings, saved versions and a run history
  • Property PickerA small trigger that opens a searchable menu to set a property: status, priority, assignee or labels, one or many
  • Radar ChartSeveral series compared across the same qualities on a spider web, with highlighting and a tooltip
  • Radio GroupA native radio group with vertical and horizontal orientations
  • RatingStar rating with exact fractional fill and optional review count
  • Render ProDraws the interface an agent composed from the Mlola Render Pro catalog: every free component, plus charts and a data grid a person can edit
  • Render SurfaceDraws the interface an agent composed as A2UI messages, checked against the Mlola Render catalog and built from Mlola components
  • ResizableTwo panes and a keyboard-accessible handle between them
  • Rich Text EditorA modern rich text editor: headings, marks, lists, quotes, code and links, with Markdown shortcuts as you type and paste cleaned to what it owns
  • Scatter ChartPoints or bubbles on two measures, with series, keyboard stepping and a tooltip
  • SchedulerA day, three-day and week calendar where events are drawn, moved and resized by pointer or keyboard, with overlaps laid out side by side
  • Segmented ControlSingle-choice control that swaps between a small set of options
  • SelectCustom searchable dropdown with option groups, keyboard navigation, and clearing
  • Selection ActionsSelect text and AI actions appear above it: rewrite, explain, shorten
  • SheetA slide-over panel from any screen edge with header, body, and footer sections
  • SkeletonShimmering loading placeholders for text, avatars, and cards
  • SliderAn accessible single-value slider with keyboard support and pointer dragging
  • Source ControlBranch sync state, staged and unstaged changes, and a commit box whose message a model can stream in
  • SparklineA tiny axis-free trend line for stat cards, tables and lists
  • SpinnerWork in progress whose end is unknown, drawn in the color of the text around it and named for screen readers when it stands alone
  • StatOne number with what it measures, how it moved and a small trend line, for the top of a dashboard
  • Status IconA work item status as one glyph: backlog, todo, in progress, in review, done and canceled, readable without color
  • StepperProgress through a flow of steps, horizontal or vertical, clickable, condensing to "Step 2 of 4" when narrow
  • Streaming TextText that arrives in pieces, revealed at a steady reading rate that catches up on bursts, with fresh runs fading in
  • Suggested EditAn AI rewrite shown as word-level tracked changes, each accepted or rejected on its own or all at once
  • SuggestionsPrompt starters and follow-ups as a wrapping or swipeable row
  • TableA compositional data table with sortable columns, striped rows, density sizes, and footer totals
  • TabsTabbed interface with underline, pills, and enclosed variants
  • Tag InputMany short values in one field: add with Enter or a comma, paste lists, remove with Backspace, with suggestions and validation
  • Task ListAn agent's plan as it runs: status per step, nested subtasks, progress announced as it changes
  • TerminalCommand output with ANSI colors mapped to theme roles, exit status, follow-on-output scrolling and an optional input with history
  • TextareaMulti-line text that grows as it is written, with a counter near its limit
  • ThinkingA model's reasoning with a live timer that folds into “Thought for 8s”, plus shimmer, dots and orbit indicators
  • Time PickerA time field of typeable spinbuttons in 12- or 24-hour time, with a quick list; the value is always HH:MM
  • TimelineA vertical event timeline with status dots, timestamps, and custom icon nodes
  • ToastToast notification system with an imperative API, auto-dismiss, and stacking
  • ToggleiOS-style switch and button-style toggle with controlled and uncontrolled modes
  • Tool CallA model's tool use as an expandable card, with input, output, timing and human approval
  • TooltipHover and focus tooltip with four placements, delay, and arrow
  • TourA guided product tour that spotlights parts of the page step by step, with keyboard control and a focus trap
  • Trace ViewerOne agent run span by span: a tree on a timeline with the slow and failing steps marked, totals for time, tokens and cost, and each span's input and output
  • TranscriptA call or meeting transcript that follows playback word by word, with speakers, search and a summary
  • TreemapNested rectangles sized by value, laid out as squares as far as possible
  • Virtual ListA long list that renders only what is on screen, measures rows of any height and loads more near the end
  • Voice WaveMirrored audio bars for microphone input or the assistant's voice, fed by a live meter
  • WhiteboardShapes, arrows, freehand, text and sticky notes on an infinite canvas, with selection, resize, color, undo and presence
GitHubv1.14.3Studio

Mlola Render

Sometimes an agent should show interface instead of writing code for it: a form inside a chat, a status card in an agent's own app, a table of results a tool just fetched. With Mlola Render it describes the surface as A2UI messages, Mlola checks them against its catalog, and the surface is drawn with Mlola components in your theme.

How it works

The agent picks components and named values (tone, variant, size) from the catalog and never writes a color, a size or a class, so what it composes is accessible and on brand by construction. Before anything renders, the check holds it to the catalog and to what makes a surface usable, and answers in A2UI's own error shape so the agent can fix its messages itself.

The catalog is generated from the components: a value a component does not offer cannot be in it. Its A2UI catalog (JSON Schema) is served at https://ui.mlola.com/a2ui/catalog/v1, the catalogId every surface names. What passes is drawn by Render Surface in React, by @mlola-ui/behavior/render on a page without a framework, or inside a chat by the render_ui MCP App, all three from the same host, so they mean the same by every message.

For an agent

Both MCP servers, npx mlola-ui mcp and https://ui.mlola.com/mcp, offer two tools. An agent reads the catalog once, composes, and checks before it sends:

Terminal
get_render_catalog              # once: components, props, values, examples
check_render { "messages": [...] }   # before sending; fix every error, check again

A form whose values are bound to the data model and come back with the button's action. This is one of the catalog's examples, and it passes the check:

A sign-up form
[
  {"version":"v0.9.1","createSurface":{"surfaceId":"signup","catalogId":"https://ui.mlola.com/a2ui/catalog/v1","theme":{"name":"graphite","mode":"system"}}},
  {"version":"v0.9.1","updateComponents":{"surfaceId":"signup","components":[
    {"id":"root","component":"Card","children":["title","form"]},
    {"id":"title","component":"Heading","level":2,"text":"Create your workspace"},
    {"id":"form","component":"Stack","children":["name","email","plan","actions"]},
    {"id":"name","component":"Input","label":"Workspace name","value":{"path":"/form/name"},"required":true},
    {"id":"email","component":"Input","type":"email","label":"Work email","value":{"path":"/form/email"},"required":true},
    {"id":"plan","component":"RadioGroup","label":"Plan","options":[{"label":"Free","value":"free"},{"label":"Team","value":"team"}],"value":{"path":"/form/plan"}},
    {"id":"actions","component":"Row","children":["submit"]},
    {"id":"submit","component":"Button","variant":"primary","text":"Create workspace","action":{"event":{"name":"create_workspace","context":{"form":{"path":"/form"}}}}}
  ]}},
  {"version":"v0.9.1","updateDataModel":{"surfaceId":"signup","path":"/form","value":{"name":"","email":"","plan":"free"}}}
]

And this is what it draws, with Render Surface:

Create your workspace

Plan

Fill it in and press the button to see what the agent receives.

In a chat that shows MCP Apps, render_ui draws the same surface in the conversation, and the person's press comes back to the agent as their next message.

Data and actions

Fields bind to the surface's data model with {"path": "/form/email"}, and updateDataModel gives them their starting values. A container's children can be a template that repeats one component for each item of an array: {"componentId": "row", "path": "/items"}, where relative paths like {"path": "name"} read the item.

When a person fills in the example's form and presses its button, the agent receives an A2UI action with the button's event name and its context, each binding replaced by its value at that moment:

JSON
{
  "version": "v0.9.1",
  "action": {
    "name": "create_workspace",
    "surfaceId": "signup",
    "sourceComponentId": "submit",
    "timestamp": "2026-10-09T09:30:00Z",
    "context": {
      "form": {
        "name": "Atlas Studio",
        "email": "lena@company.com",
        "plan": "free"
      }
    }
  }
}

The context is what a person typed. Check it on the server, as you would a form post, before the agent acts on it.

What the check says

A first try, with a name borrowed from A2UI's basic catalog, a color, a field with no label and a wrong case:

JSON
[
  {"version":"v0.9.1","createSurface":{"surfaceId":"feedback","catalogId":"https://ui.mlola.com/a2ui/catalog/v1","theme":{"name":"graphite","primaryColor":"#2563eb"}}},
  {"version":"v0.9.1","updateComponents":{"surfaceId":"feedback","components":[
    {"id":"root","component":"Column","children":["title","note","send"]},
    {"id":"title","component":"Heading","level":2,"text":"How was your order?"},
    {"id":"note","component":"Textarea","placeholder":"Tell us more"},
    {"id":"send","component":"Button","variant":"Primary","text":"Send","action":{"event":{"name":"send_feedback","context":{"note":{"path":"/note"}}}}}
  ]}}
]
Terminal
4 errors: nothing renders until they are fixed.
error /0/createSurface/theme/primaryColor [feedback]: Colors, type and shape come from the theme; pick one by name instead of setting "primaryColor": "graphite", "atelier", "machined", "aerogel", "nordic".
error /1/updateComponents/components/0/component [feedback]: "Column" is not in the Mlola catalog; use "Stack".
error /1/updateComponents/components/2 [feedback]: Textarea needs "label": the field's name.
error /1/updateComponents/components/3/variant [feedback]: Button "variant" is "primary", not "Primary": values are case-sensitive.

Beyond the schema it holds one root with real children and no loops, a name a screen reader reads on every field and control, headings that do not skip a level, values inside their range and among their options, data paths that cannot reach an object's prototype, and actions that send named events, never code. Every error is VALIDATION_FAILED with a JSON Pointer to what needs fixing. What agents bring from the basic catalog (Column, Image, a Button's child, weight) is answered with what to use instead, and the catalog's guide lists it up front.

For a developer

In React, add the component with npx mlola-ui add render-surface. Pass the same array with more messages at its end as they arrive; nothing that fails the check is drawn, and what was refused reaches onError as A2UI's error message, ready to send back to the agent.

TSX
import { RenderSurface } from "@/components/render-surface";

<RenderSurface
  messages={messagesFromTheAgent}
  streaming={stillArriving}
  onAction={(message) => sendToTheAgent(message)}
  onError={(refused) => sendToTheAgent(refused)}
/>

Without a framework, the same surfaces draw as the components' markup, with the runtime's tabs, select, slider and switch attached:

HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mlola-ui/engine@1/generated/mlola.css" />
<div id="agent-ui"></div>
<script type="module">
  import { mountRender } from "https://cdn.jsdelivr.net/npm/@mlola-ui/behavior@1/src/render/dom.js";
  import rules from "https://cdn.jsdelivr.net/npm/@mlola-ui/behavior@1/src/render/rules.js";

  const view = mountRender(document.querySelector("#agent-ui"), { rules, onAction: sendToTheAgent, onError: sendToTheAgent });
  view.receive(messagesFromTheAgent);
</script>

The same check runs from a terminal, where it exits with 1 on an error so it can gate CI, and in code, where a model's output passes through your server. It uses no Node or browser API, so it runs in a server, an edge function or a page, and mlola-ui/render and mlola-ui/render/rules carry their TypeScript types.

Terminal
npx mlola-ui render check ui.json         # an array, one message, or JSON Lines
npx mlola-ui render check ui.json --json  # the result as data
npx mlola-ui render catalog               # the guide an agent reads
npx mlola-ui render catalog --schema      # the A2UI catalog
TSX
import { validateRender } from "mlola-ui/render";
import rules from "mlola-ui/render/rules" with { type: "json" };

const result = validateRender(messagesFromTheModel, rules);
if (!result.valid) {
  // Each error is { code, surfaceId, path, message }: hand them back to the model.
  return retryWith(result.errors);
}

The details, from streaming to required fields and the limits the check holds, are in docs/render.md.

With your own model

Mlola never calls a model. Your server calls yours, with your key, and mlola-ui/render/ai is what goes around that call: what to tell the model, how to read what it answers, and what to answer when the check refuses it. Nothing a person types and nothing a model writes passes through Mlola.

TSX
import { composeRender } from "mlola-ui/render/ai";
import rules from "mlola-ui/render/rules" with { type: "json" };

const { valid, messages, result, turns } = await composeRender({
  rules,
  request: "A form to book a table for tonight",
  generate, // your function: ({ system, messages }) => the model's reply as text
});

if (valid) sendToTheClient(messages); // draw them with Render Surface or mountRender
else log(result.errors); // the model did not get there in three tries

composeRender asks the model, reads its reply, checks it, and hands a refused reply back with the errors as the next turn, three times unless you set attempts. When the person presses a button, continue from turns with A2UI's action as their next turn, and the model answers with the next surface:

TSX
const next = await composeRender({
  rules,
  request: [...turns, { role: "user", content: JSON.stringify(action) }],
  generate,
});

The one function you write

generate takes the system prompt and the turns, and answers with the model's text. Name the model you use where these say MODEL; each reads its key from the environment, where your server keeps it. All three are run by the library's tests against a stand-in for the service.

A server that speaks chat completions, which most hosted and local models do:

Chat completions
async function generate({ system, messages }) {
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: { "content-type": "application/json", authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
    body: JSON.stringify({ model: MODEL, messages: [{ role: "system", content: system }, ...messages] }),
  });
  if (!response.ok) throw new Error(`The model's server answered ${response.status}.`);
  const reply = await response.json();
  return reply.choices?.[0]?.message?.content ?? "";
}

Anthropic's Messages API:

Anthropic
async function generate({ system, messages }) {
  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: { "content-type": "application/json", "x-api-key": process.env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01" },
    body: JSON.stringify({ model: MODEL, max_tokens: 8192, system, messages }),
  });
  if (!response.ok) throw new Error(`The model's server answered ${response.status}.`);
  const reply = await response.json();
  return (reply.content ?? []).map((block) => (block.type === "text" ? block.text : "")).join("");
}

Google's Gemini API:

Gemini
async function generate({ system, messages }) {
  const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent`, {
    method: "POST",
    headers: { "content-type": "application/json", "x-goog-api-key": process.env.GEMINI_API_KEY },
    body: JSON.stringify({
      systemInstruction: { parts: [{ text: system }] },
      contents: messages.map((turn) => ({ role: turn.role === "assistant" ? "model" : "user", parts: [{ text: turn.content }] })),
    }),
  });
  if (!response.ok) throw new Error(`The model's server answered ${response.status}.`);
  const reply = await response.json();
  return (reply.candidates?.[0]?.content?.parts ?? []).map((part) => part.text ?? "").join("");
}

Your brand's theme

A surface drawn with theme="inherit" takes the theme of the page around it, so it wears your own theme, from mlola.theme.json or the Theme Studio, with the contrast that theme was solved for. With theme: "page" the prompt tells the model to leave themes alone.

TSX
await composeRender({ rules, request, generate, theme: "page" });

<RenderSurface messages={messages} theme="inherit" onAction={sendToYourServer} />

A model that also answers in words

In a chat, most answers are words and some are better as a surface. Give the model a tool and it decides. renderTool answers with a name, a description and a JSON Schema, the three parts every provider's tool calling is built from, and checkRenderReply reads the call's arguments whether your SDK parsed them or not.

TSX
import { checkRenderReply, renderSystemPrompt, renderTool } from "mlola-ui/render/ai";

const system = renderSystemPrompt(rules, { output: "tool" });
const tool = renderTool(); // { name: "render_ui", description, inputSchema }

// When the model calls the tool:
const { valid, messages, repair } = checkRenderReply(call.arguments, rules);
if (valid) sendToTheClient(messages);
answerTheCall(valid ? "Shown to the person." : repair);

As it streams

createRenderReader reads messages out of the model's text as it arrives, whatever the model wrapped them in: JSON Lines, an array, a code fence. A reply it could only read in part (a piece that is not JSON, a message still open at the end) is refused whole, with what was wrong. On the page, Render Surface draws nothing that fails the check, so the last good state stays on screen while your server asks again.

One mistake it reads through: a closing brace or bracket a model miscounted at the end of a long line. Where a closer can only mean one thing it is read as that, mended counts the closers added or left out, and the messages go through the check like any other reply. A reply that stops with a message open is still refused, since it may have been cut off.

TSX
import { createRenderReader } from "mlola-ui/render/ai";

const reader = createRenderReader(rules);
for await (const chunk of textFromTheModel) {
  for (const message of reader.push(chunk)) sendToTheClient(message);
}
const { messages, error } = reader.end();

A server that is not JavaScript reads the same prompt from the CLI:

Terminal
npx mlola-ui render prompt                      # the system prompt, for a server in any language
npx mlola-ui render prompt --tool --page-theme  # for a tool, with the page's theme

The catalog

25 free components. get_render_catalog and npx mlola-ui render catalog list every prop and the values it takes.

ComponentWhat it does
Layout
StackPlaces its children in a column with the theme's spacing. Use it as the root of most surfaces.
RowPlaces its children side by side and wraps them on a narrow screen. Use it for a row of buttons or badges.
GridPlaces its children in equal columns that collapse to one column on a phone.
Text
HeadingA heading. Levels follow the page outline: never skip a level going deeper.
TextA paragraph of plain text.
Containers
CardGroups related content on a surface of its own.
AlertA message about the surface: a confirmation, a warning or an error. Warnings and errors interrupt a screen reader; calmer tones are announced politely.
EmptyStateSays that there is nothing here yet and what to do next.
TabsShows one panel at a time, chosen from a row of tabs. Its children are Tab components.
TabOne tab of a Tabs: its name and its panel.
AccordionA list of sections that open and close. Its children are AccordionItem components.
AccordionItemOne section of an Accordion: its title and its content.
Forms
InputA one-line text field with its label.
TextareaA text field for several lines, with its label.
SelectPicks one value from a list that opens on demand. Use RadioGroup when there are only two to five options and room to show them.
RadioGroupPicks one value from options that are all visible.
CheckboxA yes or no choice with its label.
SwitchTurns a setting on or off right away.
SliderPicks a number in a range by dragging or with the arrow keys.
DatePickerA date field that opens a calendar and picks one day. The reader sees the date in their own format; the value is an ISO date.
Actions
ButtonSends an action back to the agent. Its text says what happens ("Create workspace", not "Submit").
Data
BadgeA short label for a status or a count.
ProgressShows how far along something is.
AvatarA person or a team shown by their initials.
TableRows of data under named columns. Cells hold text and numbers.

Themes: graphite, atelier, machined, aerogel, nordic, each light, dark or following the reader. Client-side functions and icon-only buttons are not in the catalog yet. Charts, stats and a data grid are in the Pro catalog.

The Pro catalog

Mlola Pro adds charts, stats and a data grid. The Pro catalog is every component above and 6 more, under its own catalogId, https://ui.mlola.com/a2ui/catalog/pro/v1, and Render Pro draws it. Both of these are drawn from the catalog's own examples:

Orders, July to September

Orders

1,284

+18% on the quarter before

New customers

508

+12%

Returning customers

776

60% of orders

1,284 orders, up 18% on the quarter before.

By month

  • New customers
  • Returning customers
Orders by month, new and returning customers
monthNew customersReturning customers
July148231
August163254
September197291

By channel

  • Web
  • App
  • Phone

Against the plan

  • Orders
  • Plan
Orders pass the plan in September.
Orders each month against the plan
monthOrdersPlan
July379400
August417420
September488440

Autumn price list

Linen throw
68
2026-10-19
Oak serving board
42
2026-10-19
Stoneware mug, set of four
54
2026-11-02

Change a price, then press the button to see the rows the agent receives.

A chart reads rows: one object for each category, with its name in one field and a number in each field a series draws. Rows sit in the component or in the data model, and a chart bound to the data model draws again when an update changes them. This is the bar chart above, and the rows it reads:

JSON
[
  {"version":"v0.9.1","updateComponents":{"surfaceId":"quarter","components":[
    {"id":"by-month-chart","component":"BarChart","label":"Orders by month, new and returning customers","data":{"path":"/months"},"x":"month","series":[{"key":"new","label":"New customers"},{"key":"returning","label":"Returning customers"}],"stacked":true}
  ]}},
  {"version":"v0.9.1","updateDataModel":{"surfaceId":"quarter","path":"/months","value":[{"month":"July","new":148,"returning":231},{"month":"August","new":163,"returning":254},{"month":"September","new":197,"returning":291}]}}
]

The check holds what a chart draws: every row written into it has its name, and a number wherever a series draws one, so "163" in place of 163 is refused with the row it is in. What a person edits in a grid is written to the data model, so a Button whose action sends that path sends the rows as they stand.

ComponentWhat it does
BarChartAmounts for each category as bars, one group or one stack per category. Use it to compare categories; a person reads the exact numbers by pointing or with the arrow keys.
LineChartValues over a sequence as lines: days, months, steps. Use it for a trend; a person reads the exact numbers by pointing or with the arrow keys.
DonutChartParts of a whole as a ring, with the total in the middle. Use it for a few parts; for more than six, or to compare amounts, use a BarChart.
SparklineA small trend line with no axes, for beside a number in a Card or a Row.
StatOne number a person looks for first: what it measures, the number, how it moved and a small trend line. Put a few side by side in a Grid at the top; a Stat is a tile of its own, so it does not go inside a Card.
DataGridMany rows a person works with like a spreadsheet: sort by a column, move with the arrow keys, select and copy cells, and edit the columns you mark editable. For a few rows to read, use a Table.

Render Pro comes with a Mlola Pro license and takes the props of Render Surface. The catalog arrives in your project beside it as plain data, which a server passes to composeRender, renderSystemPrompt and validateRender in place of the free catalog's rules. The MCP tools and the CLI's render commands hold the free catalog.

TSX
npx mlola-ui add render-pro

import { RenderPro } from "@/components/render-pro";
import { renderProRules } from "@/components/render-pro/rules";

// On your server, with a model you call yourself:
const { valid, messages } = await composeRender({ rules: renderProRules, request, generate });

// On the page:
<RenderPro messages={messages} onAction={sendToYourServer} />