Yell’s structured format makes it ideal for AI generation. This guide covers how to prompt AI to generate valid Yell YAML and how to integrate an AI adapter into your workflow.
AI can generate UI from natural language, but JSX is ad-hoc and unpredictable:
Prompt: "Create a login form"
AI returns: JSX blob (may have syntax errors, unknown components, inline logic)
Yell makes AI generation reliable:
Prompt: "Create a login form"
AI returns: Valid YAML with known components, bounded expressions, no inline functions
The adapter is a pipeline that:
import { YellAIAdapter } from '@yell/ai-adapter';
const adapter = new YellAIAdapter({
registry,
tokens,
model: 'claude-3-5-sonnet',
});
const result = await adapter.generateYAML({
prompt: 'Create a login page with email and password fields',
});
if (result.errors) {
// Handle validation errors
console.log('Fix these issues:', result.errors);
} else {
// Use the valid YAML
console.log(result.yaml);
}
"Create a [component type] with [properties].
The component should use these design tokens: [tokens].
Do not use: inline functions, complex expressions, mutations."
✅ Good: "Create a dashboard with a header, 3 feature cards, and a CTA button. Use $tokens.primary for buttons. No inline onClick handlers."
❌ Bad: "Make a cool dashboard with buttons that do stuff"
The adapter enforces these rules on generated YAML:
onClick must be a reference, not () => ...showWhen can only use ==, !=, &&, ||, !, >, <import { YellAIAdapter } from '@yell/ai-adapter';
import { parseYAML, renderToString, createRegistry } from '@yell/core';
const registry = createRegistry();
// ... register components
const adapter = new YellAIAdapter({
registry,
tokens: loadTokens('prod'),
model: 'claude-3-5-sonnet',
});
async function handleUserRequest(prompt: string) {
const result = await adapter.generateYAML({ prompt });
if (result.errors) {
return {
ok: false,
errors: result.errors,
suggestion: result.suggestion,
};
}
const config = parseYAML(result.yaml);
const { html, hydrationMap } = renderToString(config, registry);
return {
ok: true,
html,
hydrationMap,
metadata: result.metadata,
};
}
The adapter uses these guardrails to ensure valid output:
Only types in the registry can be generated:
function generateWithWhitelist(prompt, registry) {
const allowedTypes = getRegisteredTypes(registry);
const componentList = allowedTypes.join(', ');
return `Generate YAML using only these components: ${componentList}.
Do not invent or assume components that don't exist.`;
}
const EXPRESSION_GUARDRAILS = `
Expressions must use only these operators: ==, !=, &&, ||, !, >, <.
Do not use: function calls, ternary operators, method calls, array operations.
Example VALID: showWhen: isOpen == true
Example INVALID: showWhen: users.filter(u => u.active)
`;
function generateWithSchemaGuidance(prompt, component, schema) {
const propDescriptions = Object.entries(schema.shape)
.map(([key, value]) => `${key}: ${value.description}`)
.join('\n');
return `${prompt}
Component "${component}" has these props:
${propDescriptions}`;
}
When AI generates invalid YAML, the adapter returns structured errors:
{
ok: false,
errors: [
{
path: 'children[0]',
type: 'unknown_component',
message: 'Unknown component type "FakeButton". Did you mean "Button"?',
suggestion: 'Button'
},
{
path: 'children[0].props.onClick',
type: 'inline_function',
message: 'onClick cannot be an inline function. Use a reference.',
suggestion: 'onClick: handleClick'
}
],
suggestion: 'Try this instead:\n[corrected YAML]'
}
Use the CLI to test:
yell ai "Create a login form" --registry ./components.yaml --model claude-3-5-sonnet
This outputs:
yell validate on AI output