Why I Built Fable UI
The missing layer between AI tool calls and product UI, and why I made it installable.
I kept seeing the same small failure in AI products.
Ask for revenue and the number arrives inside a sentence. Ask for orders and you get a markdown table. Ask for a refund and the assistant says it can help, when the product really needs a confirmation step with visible boundaries.
The model understood the request.
The interface did not.
That is why I built Fable UI.
The gap
React already has component libraries. shadcn/ui already proved that developers value owning the source. The Vercel AI SDK already gives us tool calls and streaming.
The missing part was smaller and more specific:
How does an AI agent use product UI without being allowed to invent the product?
Wiring one custom tool by hand is easy enough. You write a schema, register the tool, listen for its result, and render a component.
Then you do it again.
Soon the real unit is scattered across the codebase: component, schema, tool description, renderer, examples, safety rules, and the sentence in someone's head that says, "do not use this for records."
That is product behavior. It should travel together.
The better unit
Fable UI treats one AI-aware surface as a bundle:
- React component
- Zod schema
- AI SDK tool
- renderer states
- model-facing manifest
- examples and docs
- eval prompts
Take MetricCard. The visual part shows one number. The contract says much more: use it for one primary metric, reject lists and browsing, and prefer another tool when the user needs rows or an action.
The component is only the visible part.
The useful unit is the component plus the rules around it.
The boundary
I do not trust the model to be the designer, database client, permission system, and frontend engineer at the same time.
Fable gives it a narrower job.
The model may select an installed surface and provide typed intent. The host application still owns:
- authentication and authorization
- tenant or organization scope
- data access
- server-side validation
- business rules
- side effects and persistence
A confirmation card captures a decision. It does not grant permission.
A data browser can request an allowlisted resource. It does not get a raw endpoint, Firestore path, secret, or SQL query.
The model can ask. The host decides.
How it works
The runtime flow is intentionally boring:
User prompt
Model selects an installed tool
Schema validates the payload
Tool state streams to the client
Fable renderer maps it to React UI
Host handles data and actionsA definition keeps the tool and renderer contract connected:
export const showMetric = defineFableComponent({
name: "show_metric",
schema: showMetricInputSchema,
tool: showMetricTool,
renderer: {
Component: MetricCard,
loadingProps: { label: "Metric", value: "..." },
toProps: (data) => data
}
})The AI route gets showMetric.tool. The client renderer gets the full definition.
At the rendering boundary, Fable validates streamed input before turning it into props. Partial input, malformed output, and unknown tools resolve to controlled states instead of breaking the conversation.
Why manifests matter
The React code was not the uncomfortable part. The manifest was.
A tool description has to stay short. A manifest can explain the real selection boundary:
- use when
- do not use when
- neighboring tools
- conflict cases
- safe payload expectations
- trigger prompts
- anti-triggers
- eval prompts
This is not documentation after the fact. It shapes which surface the model chooses.
A beautiful component is still wrong if it appears at the wrong moment. MetricCard should not steal "show the last ten orders." DataBrowser should not become a confirmation flow. A chart should not pretend it can fetch private analytics by itself.
In AI UI, knowing when to leave a component alone is part of component quality.
The first surfaces
I started with common product moments:
MetricCardfor one number- charts for visual trends and comparisons
- a table for small, bounded row sets
DataBrowserfor search, filters, sorting, pagination, and detailFormCardfor a few missing structured valuesConfirmationCardbefore a host-owned side effectSuggestedActionsfor safe ways to continue the conversation
DataBrowser is the clearest proof of the idea.
If someone asks for ten open orders, a paragraph is weak and a markdown table is barely enough. The user probably needs to inspect rows. The model can select an orders resource from a safe catalog, while the host resolves the real data with its own auth and organization context.
The assistant knows what to ask for.
It does not know the private plumbing.
Why copy and own
These files influence product behavior, so I want them inside the product.
A team should be able to inspect the schema, tune the manifest, remove a surface, change failure states, and match its design system. A restaurant POS and a finance product may both use a metric card, but their data and safety rules are not generic.
The shadcn registry already gives Fable the right distribution model. I did not need a custom CLI first.
Copy and own is not only developer experience here.
It is part of the trust model.
Testing the behavior
A normal component registry tests rendering.
Fable also has to test selection.
For every surface, I want three prompt groups:
- prompts that should select it
- prompts that should remain text or select nothing
- conflicts where a neighboring surface should win
For show_metric, "What is today's revenue?" is positive. "Show the last ten orders" should lose to a row surface. "Refund this order" should move toward confirmation and server validation.
This is where manifests stop being opinions and become testable product infrastructure.
Where it is now
Fable UI is a live MVP with a public registry, documentation, a playground, core rendering helpers, manifests, resource concepts, and the first set of installable surfaces.
It is not a proven standard.
The install flow needs more testing in external Next.js apps. The selection evals need repeatable runs across models and prompt changes. The examples are still strongest around the Vercel AI SDK.
I would rather describe that honestly than make the project sound finished.
The next proof is not more components. It is reliable installation and reliable selection.
The model
The interesting part of Fable UI is not that AI can show a card.
It is that the card arrives with a contract.
The component knows its state. The schema knows the payload. The manifest knows the boundary. The renderer knows how to fail. The host knows it still owns reality.
That is the whole model:
The agent expresses intent.
The registry exposes trusted surfaces.
The schema validates the payload.
The host enforces reality.The model does not become unlimited.
The product becomes explicit about what it is allowed to choose.