Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions Articles/5 Things That Look Terrible as Plain Text.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# 5 Things That Look Terrible as Plain Text (And How OpenUI Fixes Them)

AI applications started with text because text is the easiest thing for a model to return.

That made sense for the first wave of chatbots. A user asks a question, the model replies with a paragraph, a list, or a markdown table. For many tasks, that is still enough. Summaries, explanations, drafts, and code suggestions all work reasonably well as text.

But the moment an AI product moves from answering questions to helping users make decisions, plain text starts to break down.

The problem is not that text is bad. The problem is that text is a weak interface for structured work. You cannot sort a paragraph. You cannot validate a markdown form. You cannot safely approve an action hidden inside a wall of prose. You cannot compare five options quickly if they are buried in bullets.

This is where generative UI becomes useful. Instead of forcing every answer into markdown, the model can compose an interface from approved components: tables, forms, charts, cards, buttons, tabs, and workflows. OpenUI gives that process a concrete shape through OpenUI Lang, a compact line-oriented format that lets an LLM describe UI using a developer-defined component library.

Here are five common AI outputs that look terrible as plain text, and how they become more useful when rendered as actual interfaces.

The snippets below use the current default `openuiLibrary` signatures generated by `openuiLibrary.prompt(openuiPromptOptions)`, including components such as `Stack`, `TextContent`, `Table`, `Form`, `Card`, and `Button`.

## 1. Comparisons

Plain text is especially bad at comparison.

Imagine asking an AI assistant:

> Compare these three CRM plans for a 40-person sales team.

A typical response looks like this:

```txt
Plan A is cheaper and includes basic reporting, but it lacks advanced routing.
Plan B costs more but includes territory management, forecasting, and automation.
Plan C is the most expensive and is best for large enterprises with complex workflows.
```

That is readable, but it makes the user do too much work. Pricing, limits, tradeoffs, and recommendations are all mixed together. If the user wants to compare contract length, support level, or automation features, they have to scan sentences and reconstruct the table in their head.

A better interface is a comparison table with clear columns, highlighted differences, and an action at the end:

```txt
root = Stack([title, table, actions])
title = TextContent("CRM plan comparison", "large-heavy")
table = Table([planCol, priceCol, routingCol, forecastCol, fitCol])
planCol = Col("Plan", plans)
priceCol = Col("Monthly cost", prices, "number")
routingCol = Col("Lead routing", routing)
forecastCol = Col("Forecasting", forecasting)
fitCol = Col("Best fit", fit)
plans = ["Starter", "Growth", "Enterprise"]
prices = [1200, 2400, 5200]
routing = ["Basic", "Advanced", "Custom"]
forecasting = ["No", "Yes", "Yes"]
fit = ["Small teams", "Scaling sales orgs", "Complex enterprise workflows"]
actions = Buttons([shortlistBtn, exportBtn], "row")
shortlistBtn = Button("Shortlist Growth", Action([@ToAssistant("Shortlist the Growth plan")]), "primary")
exportBtn = Button("Export comparison", Action([@ToAssistant("Export this comparison")]), "secondary")
```

The same information becomes easier to scan and easier to act on. The model is not just explaining the comparison. It is generating the right shape for comparison.

## 2. Forms

Forms are one of the clearest examples of why text is not enough.

If a support agent asks an AI assistant to "create a refund request for this customer," a text answer might say:

```txt
Refund request:
- Customer: Maya Chen
- Order ID: ORD-18392
- Reason: Duplicate charge
- Amount: $89.00
- Recommended action: Approve refund
```

That looks fine until the user needs to edit it. What if the amount is wrong? What if the reason needs to be changed? What if approval requires a required field, a validation rule, or a confirmation step?

Plain text turns a workflow into a note. A generated form turns it back into a workflow. With the default OpenUI library, the form can define the required fields, input types, validation rules, and submit action. The host application can still fill, validate, or persist values however its product logic requires.

```txt
root = Stack([title, form])
title = TextContent("Refund request", "large-heavy")
form = Form("refund-request", buttons, [orderId, amount, reason])
orderId = FormControl("Order ID", Input("orderId", "Enter order ID", "text", {required: true}))
amount = FormControl("Refund amount", Input("amount", "Enter refund amount", "number", {required: true, numeric: true}))
reason = FormControl("Reason", Select("reason", reasons, "Choose a reason", {required: true}))
reasons = [duplicate, productIssue, cancellation, other]
duplicate = SelectItem("duplicate-charge", "Duplicate charge")
productIssue = SelectItem("product-issue", "Product issue")
cancellation = SelectItem("customer-cancellation", "Customer cancellation")
other = SelectItem("other", "Other")
buttons = Buttons([approveBtn, cancelBtn], "row")
approveBtn = Button("Approve refund", Action([@ToAssistant("Submit the refund request")]), "primary")
cancelBtn = Button("Cancel", Action([@ToAssistant("Cancel the refund request")]), "secondary")
```

This is the important shift: the AI output is no longer the final answer. It is a usable surface where the human can verify, edit, and submit.

OpenUI is useful here because the model is not generating arbitrary HTML. It is composing approved components such as `Form`, `FormControl`, `Input`, `Select`, `SelectItem`, `Button`, and `Buttons`. The host application still owns validation, permissions, and what happens when the user submits or confirms the generated form.

## 3. Dashboards

Dashboards are where plain text fails most visibly.

Ask an AI system for a weekly product health summary and it might return:

```txt
Activation decreased by 4.2% this week. Retention was flat. Expansion revenue increased in the enterprise segment. The biggest concern is the mobile onboarding funnel, where completion dropped from 61% to 49%.
```

That is a useful summary, but it is not a dashboard. It hides the relative importance of each metric. It gives no visual hierarchy. It does not let the user inspect the underlying segments. It is hard to tell what is a metric, what is a trend, and what is a recommendation.

A better generated UI could combine metric cards and a follow-up table. Even a minimal version is already more useful:

```txt
root = Stack([title, summary, metrics, table])
title = TextContent("Weekly product health", "large-heavy")
summary = TextContent("Activation is down, driven mostly by mobile onboarding.")
metrics = Stack([activationCard, retentionCard, expansionCard], "row")
activationCard = Card([activationTitle, activationValue])
activationTitle = TextContent("Activation", "small")
activationValue = TextContent("-4.2%", "large-heavy")
retentionCard = Card([retentionTitle, retentionValue])
retentionTitle = TextContent("Retention", "small")
retentionValue = TextContent("+0.3%", "large-heavy")
expansionCard = Card([expansionTitle, expansionValue])
expansionTitle = TextContent("Expansion", "small")
expansionValue = TextContent("+6.8%", "large-heavy")
table = Table([areaCol, changeCol, ownerCol])
areaCol = Col("Area", areas)
changeCol = Col("Change", changes, "number")
ownerCol = Col("Owner", owners)
areas = ["Mobile onboarding", "Web activation", "Enterprise expansion"]
changes = [-12, -2, 7]
owners = ["Growth", "Activation", "Sales"]
```

The interface gives users a starting point for investigation instead of a paragraph they have to translate into a dashboard themselves.

This is also where OpenUI's streaming-first design matters. The docs describe OpenUI Lang as line-oriented, so the renderer can parse and render pieces as they arrive rather than waiting for a large JSON object to become complete. For dashboards, perceived speed matters because users should see structure quickly and data fill in progressively.

## 4. Search Results and Recommendations

AI-generated recommendations often become long lists of prose:

```txt
I recommend the Sony WH-1000XM5 because it has strong noise cancellation and battery life. The Bose QuietComfort Ultra is also a good option if comfort matters most. The AirPods Max are best if you are already in the Apple ecosystem, but they are heavier and more expensive.
```

This is not terrible for three items. It collapses quickly when there are ten items, multiple constraints, prices, ratings, availability, and tradeoffs.

Search and recommendation UIs need structure. Users expect cards, filters, badges, prices, summaries, and comparison actions. A paragraph is the wrong container.

OpenUI can express a card-based result list with actions while still keeping the output constrained:

```txt
root = Stack([title, results, actions])
title = TextContent("Recommended headphones", "large-heavy")
results = Stack([sony, bose, apple])
sony = Card([sonyTitle, sonyText])
sonyTitle = TextContent("Sony WH-1000XM5")
sonyText = TextContent("$399 - Best for noise cancellation and battery life", "small")
bose = Card([boseTitle, boseText])
boseTitle = TextContent("Bose QuietComfort Ultra")
boseText = TextContent("$429 - Best for comfort and travel", "small")
apple = Card([appleTitle, appleText])
appleTitle = TextContent("AirPods Max")
appleText = TextContent("$549 - Best for Apple ecosystem users", "small")
actions = Buttons([compareBtn], "row")
compareBtn = Button("Compare selected", Action([@ToAssistant("Compare these recommendations")]), "primary")
```

This still is not a full shopping app. That is the point. The AI does not need to own the whole application. It needs a safe way to return the right interface for the current step.

## 5. Actions That Require Trust

The worst place to hide information is right before a user takes an action.

Plain text is risky for approvals, purchases, deployments, refunds, account changes, and anything that changes state. A paragraph like this is not enough:

```txt
I found three inactive users who still have admin access. You should remove admin access from Jordan, Priya, and Luis. This will reduce security risk.
```

That might be correct, but the user needs more than a recommendation. They need to see who is affected, why the action is recommended, what will happen, and how to confirm or cancel.

A generated confirmation interface is much safer:

```txt
root = Stack([title, warning, table, actions])
title = TextContent("Review admin access changes", "large-heavy")
warning = TextContent("These users have been inactive for more than 90 days.")
table = Table([userCol, roleCol, lastSeenCol])
userCol = Col("User", users)
roleCol = Col("Current role", roles)
lastSeenCol = Col("Last active", lastSeen)
users = ["Jordan Lee", "Priya Shah", "Luis Romero"]
roles = ["Admin", "Admin", "Admin"]
lastSeen = ["104 days ago", "121 days ago", "98 days ago"]
actions = Buttons([confirmBtn, cancelBtn], "row")
confirmBtn = Button("Remove admin access", Action([@ToAssistant("Remove admin access for these users")]), "primary")
cancelBtn = Button("Cancel", Action([@ToAssistant("Cancel the access change")]), "secondary")
```

This is not just prettier than text. It is more accountable. The user can inspect the records before clicking. The application can enforce permissions. The action handler can log the decision. The generated UI becomes part of a controlled workflow instead of an unstructured recommendation.

## What OpenUI Actually Adds

The examples above are not about making AI responses more decorative. They are about matching the output format to the task.

OpenUI helps because it gives teams a practical contract between the model and the frontend:

- The model outputs OpenUI Lang instead of arbitrary prose or raw React.
- The output is constrained to a component library the developer controls.
- The renderer maps those component calls to real React components.
- The line-oriented format can stream progressively.
- Invalid or unsupported output can be handled by the renderer instead of executed as code.

That contract is what separates generative UI from "the model wrote some HTML." A good generative UI system should not let the model invent the product. It should let the model assemble trusted interface primitives for the user's current task.

Plain text will always have a place in AI products. Sometimes the right answer really is a paragraph. But comparisons, forms, dashboards, recommendations, and approvals all need structure, state, and interaction.

That is the line where text output stops being enough.

## References

- [OpenUI documentation](https://www.openui.com/docs)
- [OpenUI Lang syntax](https://www.openui.com/docs/openui-lang/syntax)
- [OpenUI benchmarks](https://www.openui.com/docs/openui-lang/benchmarks)
- [OpenUI GitHub repository](https://github.com/thesysdev/openui)