ai_ui_render
A Flutter framework for safely generating UI from AI prompts. Render Flutter widgets from JSON with guardrailed component catalogs, streaming support, and rich action handling.
Features
- Guardrailed Components - AI can only use components you explicitly define in your catalog
- JSON to Widget Rendering - Convert JSON tree structures into Flutter widgets
- Streaming Support - Render UI progressively as AI generates it (JSONL format)
- Visibility Conditions - Show/hide components based on data, auth state, or complex logic
- Rich Actions - Handle user interactions with confirmation dialogs and callbacks
- Data Binding - Dynamic values that resolve from your data model
- Validation - Built-in validators for forms and custom validation support
Screenshots
Installation
Add to your pubspec.yaml:
dependencies:
ai_ui_render: ^0.1.0
Then run:
flutter pub get
Quick Start
1. Define a Component Catalog
The catalog defines which components AI can generate - this is your safety guardrail:
import 'package:ai_ui_render/ai_ui_render.dart';
import 'package:flutter/material.dart';
final catalog = createCatalog('my-app')
.component(
'Text',
builder: (props, children) => Text(
props['text'] as String? ?? '',
style: TextStyle(
fontSize: (props['size'] as num?)?.toDouble() ?? 14,
fontWeight: props['bold'] == true ? FontWeight.bold : FontWeight.normal,
),
),
hasChildren: false,
description: 'Display text content',
)
.component(
'Button',
builder: (props, children) => ElevatedButton(
onPressed: () {
// Handle action
},
child: Text(props['label'] as String? ?? 'Button'),
),
hasChildren: false,
description: 'Clickable button',
)
.component(
'Card',
builder: (props, children) => Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(children: children),
),
),
description: 'Card container',
)
.build();
2. Create a Registry
final registry = createRegistryFromCatalog(catalog);
3. Render a UI Tree
// JSON structure (typically from AI)
final uiTree = UITree.fromJson({
'root': 'main-card',
'elements': {
'main-card': {
'key': 'main-card',
'type': 'Card',
'props': {},
'children': ['greeting', 'action-btn'],
},
'greeting': {
'key': 'greeting',
'type': 'Text',
'props': {'text': 'Hello, World!', 'size': 24, 'bold': true},
'children': [],
},
'action-btn': {
'key': 'action-btn',
'type': 'Button',
'props': {'label': 'Click Me'},
'children': [],
},
},
});
// Render it
StandaloneJsonRenderer(
tree: uiTree,
registry: registry,
)
Streaming UI Generation
For AI-powered streaming generation:
final streamController = UIStreamController(
UIStreamOptions(
api: 'https://your-api.com/generate',
onComplete: (tree) => print('Generation complete!'),
onError: (error) => print('Error: $error'),
),
);
// Send a prompt
await streamController.send('Create a dashboard with revenue metrics');
// Use in widget
ListenableBuilder(
listenable: streamController,
builder: (context, child) {
return StandaloneJsonRenderer(
tree: streamController.tree,
registry: registry,
loading: streamController.isStreaming,
);
},
)
Visibility Conditions
Control component visibility based on data or auth state:
// In JSON
{
'key': 'admin-panel',
'type': 'Card',
'props': {},
'visible': {'auth': 'signedIn'}, // Only show when signed in
'children': [],
}
// Complex conditions
{
'visible': {
'and': [
{'auth': 'signedIn'},
{'path': '/user/isAdmin'},
{'gt': [{'path': '/user/level'}, 5]}
]
}
}
Available conditions:
true/false- Always visible/hidden{'path': '/data/path'}- Visible when path is truthy{'auth': 'signedIn'}/{'auth': 'signedOut'}- Auth-based{'and': [...]}- All conditions must be true{'or': [...]}- Any condition must be true{'not': {...}}- Negates a condition{'eq': [a, b]},{'neq': [a, b]}- Equality checks{'gt': [a, b]},{'gte': [a, b]},{'lt': [a, b]},{'lte': [a, b]}- Comparisons
Data Binding
Use dynamic values that resolve from your data model:
// In JSON props
{
'type': 'Text',
'props': {
'text': {'path': '/user/name'} // Resolves from data model
}
}
// Provide data when rendering
StandaloneJsonRenderer(
tree: uiTree,
registry: registry,
dataModel: {
'user': {'name': 'John Doe', 'level': 10},
},
)
Actions
Define and handle user actions:
final catalog = createCatalog('my-app')
.action('deleteItem', description: 'Delete an item')
.action('saveForm', description: 'Save form data')
.build();
// In JSON
{
'type': 'Button',
'props': {
'label': 'Delete',
'action': {
'name': 'deleteItem',
'params': {'id': {'path': '/selectedItem/id'}},
'confirm': {
'title': 'Confirm Delete',
'message': 'Are you sure you want to delete this item?',
'variant': 'danger'
}
}
}
}
Validation
Built-in validators for form fields:
final validators = Validators.compose([
Validators.required('Email is required'),
Validators.email('Invalid email format'),
]);
final result = validators('[email protected]');
print(result.isValid); // true
Available validators:
required- Value must not be null or emptyemail- Valid email formatminLength(n)/maxLength(n)- String length constraintsmin(n)/max(n)- Numeric constraintspattern(regex)- Regex pattern matchingurl- Valid URL format
With Providers
For full state management integration:
JsonUIProvider(
registry: registry,
initialData: {'user': {'name': 'John'}},
authState: AuthState(isSignedIn: true),
actionHandlers: {
'deleteItem': (params) async {
// Handle delete
},
},
child: JsonRenderer(
tree: uiTree,
registry: registry,
),
)
JSON Tree Structure
The UI tree uses a flat structure optimized for LLM generation:
{
"root": "main-element-key",
"elements": {
"main-element-key": {
"key": "main-element-key",
"type": "ComponentType",
"props": { "prop1": "value1" },
"children": ["child-key-1", "child-key-2"],
"visible": true
},
"child-key-1": {
"key": "child-key-1",
"type": "Text",
"props": { "text": "Hello" },
"children": []
}
}
}
Generate AI Prompts
Generate a prompt describing your catalog for AI:
final prompt = generateCatalogPrompt(catalog);
// Use this prompt to instruct AI about available components
Example App
The example directory contains a comprehensive demo application with 5 interactive pages:
1. Components Gallery
Showcases all 25+ built-in components organized by category:
- Layout: Card, Row, Column, Container, Center, Expanded, Wrap, Spacer, Divider
- Text: Text, Heading, RichText
- Buttons: Button, IconButton
- Display: Icon, Badge, Avatar, Image, ProgressBar, Chip
- Input: TextField, Checkbox, Switch
- Feedback: Alert, Loading
2. Data Binding
Demonstrates dynamic values that resolve from your data model with live updates.
3. Visibility Conditions
Interactive demo of all visibility condition types with toggles to see real-time effects.
4. Actions
Shows the action system with confirmation dialogs, parameters, and success/error handlers.
5. Streaming
Simulates AI streaming UI generation with progressive rendering.
Running the Example
cd example
flutter run
API Reference
Core Classes
| Class | Description |
|---|---|
UITree |
Flat tree structure containing all UI elements |
UIElement |
Single UI element with type, props, and children |
AiUiCatalog |
Registry of allowed components (safety guardrail) |
ComponentRegistry |
Maps component types to widget builders |
DynamicValue<T> |
Value that can be literal or path reference |
VisibilityCondition |
Condition for showing/hiding elements |
Action |
User action with params, confirm, callbacks |
Widgets
| Widget | Description |
|---|---|
JsonRenderer |
Main renderer with Provider integration |
StandaloneJsonRenderer |
Standalone renderer without Provider |
JsonUIProvider |
Combined provider for data, visibility, actions |
UIStreamProvider |
Provider for streaming UI generation |
Functions
| Function | Description |
|---|---|
createCatalog(name) |
Create a catalog builder |
createRegistryFromCatalog(catalog) |
Create registry from catalog |
generateCatalogPrompt(catalog) |
Generate AI prompt from catalog |
evaluateVisibility(condition, ctx) |
Evaluate visibility condition |
resolveAction(action, dataModel) |
Resolve dynamic values in action |
validateForm(data, validations) |
Validate form data |
Validators
Validators.required([message]) // Not null or empty
Validators.email([message]) // Valid email format
Validators.minLength(n, [message]) // Min string length
Validators.maxLength(n, [message]) // Max string length
Validators.min(n, [message]) // Min numeric value
Validators.max(n, [message]) // Max numeric value
Validators.pattern(regex, [message]) // Regex pattern
Validators.url([message]) // Valid URL
Validators.compose([...]) // Combine validators
Architecture
┌─────────────────────────────────────────────────────────┐
│ Your App │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Catalog │ │ Registry │ │ Data Model │ │
│ │ (Components)│ │ (Builders) │ │ (State) │ │
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ JsonRenderer / StandaloneJsonRenderer ││
│ │ ┌─────────────────────────────────────────────┐ ││
│ │ │ ElementBuilder │ ││
│ │ │ - Resolves dynamic values │ ││
│ │ │ - Evaluates visibility │ ││
│ │ │ - Builds widgets recursively │ ││
│ │ └─────────────────────────────────────────────┘ ││
│ └─────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ Flutter Widgets ││
│ └─────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────┘
┌──────────────────────────────────┐
│ AI / LLM │
│ ┌────────────────────────────┐ │
│ │ Catalog Prompt │ │
│ │ (Generated from catalog) │ │
│ └────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────┐ │
│ │ JSON UI Tree │ │
│ │ (Streamed via JSONL) │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE for details.
Libraries
- ai_ui_render
- AI UI Render - Flutter JSON to UI rendering framework