Learn how to integrate your existing React components into UI Builder. This focused guide shows you how to take any React component and make it available in the visual editor.
Integrating a custom component into UI Builder is a straightforward 3-step process:
UI Builder works with your existing React components without any modifications. Here's a realistic example:
tsx1// components/ui/user-card.tsx 2interface UserCardProps { 3 className?: string; 4 children?: React.ReactNode; 5 name: string; 6 email: string; 7 role: 'admin' | 'user' | 'viewer'; 8 avatarUrl?: string; 9 isOnline?: boolean; 10} 11 12export function UserCard({ 13 className, 14 children, 15 name, 16 email, 17 role, 18 avatarUrl, 19 isOnline = false 20}: UserCardProps) { 21 return ( 22 <div className={cn("border rounded-lg p-4 bg-card", className)}> 23 <div className="flex items-center gap-3"> 24 {avatarUrl && ( 25 <img 26 src={avatarUrl} 27 alt={name} 28 className="w-10 h-10 rounded-full" 29 /> 30 )} 31 <div className="flex-1"> 32 <div className="flex items-center gap-2"> 33 <h3 className="font-semibold">{name}</h3> 34 {isOnline && ( 35 <div className="w-2 h-2 bg-green-500 rounded-full" /> 36 )} 37 </div> 38 <p className="text-sm text-muted-foreground">{email}</p> 39 <span className={cn( 40 "text-xs px-2 py-1 rounded", 41 role === 'admin' ? 'bg-red-100 text-red-700' : 42 role === 'user' ? 'bg-blue-100 text-blue-700' : 43 'bg-gray-100 text-gray-700' 44 )}> 45 {role} 46 </span> 47 </div> 48 </div> 49 {children && ( 50 <div className="mt-3 pt-3 border-t"> 51 {children} 52 </div> 53 )} 54 </div> 55 ); 56}
Key Requirements for UI Builder Components:
className: For styling integrationchildren: For content composition (optional)Next, create a definition that tells UI Builder how to work with your component:
tsx1import { z } from 'zod'; 2import { UserCard } from '@/components/ui/user-card'; 3import { classNameFieldOverrides, childrenFieldOverrides } from '@/lib/ui-builder/registry/form-field-overrides'; 4 5const userCardDefinition = { 6 // The React component itself 7 component: UserCard, 8 9 // Zod schema defining props for the auto-generated form 10 schema: z.object({ 11 className: z.string().optional(), 12 children: z.any().optional(), 13 name: z.string().default('John Doe'), 14 email: z.string().default('john@example.com'), 15 role: z.enum(['admin', 'user', 'viewer']).default('user'), 16 avatarUrl: z.string().optional(), 17 isOnline: z.boolean().default(false), 18 }), 19 20 // Import path for code generation 21 from: '@/components/ui/user-card', 22 23 // Custom form field overrides (optional) 24 fieldOverrides: { 25 className: (layer) => classNameFieldOverrides(layer), 26 children: (layer) => childrenFieldOverrides(layer), 27 email: (layer) => ({ 28 inputProps: { 29 type: 'email', 30 placeholder: 'user@example.com' 31 } 32 }), 33 avatarUrl: (layer) => ({ 34 inputProps: { 35 type: 'url', 36 placeholder: 'https://example.com/avatar.jpg' 37 } 38 }) 39 } 40};
Schema Design Tips:
.default() values for better user experiencez.coerce.number() for numeric inputs (handles string conversion)z.coerce.date() for date inputs (handles string conversion)className and children props for flexibilityInclude your definition in the componentRegistry prop:
tsx1import UIBuilder from '@/components/ui/ui-builder'; 2import { primitiveComponentDefinitions } from '@/lib/ui-builder/registry/primitive-component-definitions'; 3import { complexComponentDefinitions } from '@/lib/ui-builder/registry/complex-component-definitions'; 4 5const myComponentRegistry = { 6 // Include pre-built components 7 ...primitiveComponentDefinitions, // div, span, img, etc. 8 ...complexComponentDefinitions, // Button, Badge, Card, etc. 9 10 // Add your custom components 11 UserCard: userCardDefinition, 12 // Add more custom components... 13}; 14 15export function App() { 16 return ( 17 <UIBuilder componentRegistry={myComponentRegistry} /> 18 ); 19}
That's it! Your UserCard component is now available in the UI Builder editor.
Make components automatically bind to system data when added to the canvas:
tsx1UserCard: { 2 component: UserCard, 3 schema: z.object({...}), 4 from: '@/components/ui/user-card', 5 6 // Auto-bind properties to variables when component is added 7 defaultVariableBindings: [ 8 { 9 propName: 'name', 10 variableId: 'current-user-name', 11 immutable: false // Users can unbind this 12 }, 13 { 14 propName: 'email', 15 variableId: 'current-user-email', 16 immutable: true // Locked for security 17 }, 18 { 19 propName: 'role', 20 variableId: 'current-user-role', 21 immutable: true // Prevent role tampering 22 } 23 ] 24}
Use Cases for Variable Bindings:
Immutable Bindings: Set immutable: true to prevent users from unbinding critical data like user IDs, brand colors, or security permissions.
Provide default child components when users add your component:
tsx1UserCard: { 2 component: UserCard, 3 schema: z.object({...}), 4 from: '@/components/ui/user-card', 5 6 // Default children when component is added 7 defaultChildren: [ 8 { 9 id: 'user-actions', 10 type: 'div', 11 name: 'Action Buttons', 12 props: { 13 className: 'flex gap-2 mt-2' 14 }, 15 children: [ 16 { 17 id: 'edit-btn', 18 type: 'Button', 19 name: 'Edit Button', 20 props: { 21 variant: 'outline', 22 size: 'sm' 23 }, 24 children: 'Edit Profile' 25 }, 26 { 27 id: 'message-btn', 28 type: 'Button', 29 name: 'Message Button', 30 props: { 31 variant: 'default', 32 size: 'sm' 33 }, 34 children: 'Send Message' 35 } 36 ] 37 } 38 ] 39}
Important: All component types referenced in defaultChildren must exist in your componentRegistry (like Button and div in the example above).
Here's a complete example showing a blog post component with all advanced features:
tsx1// components/ui/blog-post-card.tsx 2interface BlogPostCardProps { 3 className?: string; 4 children?: React.ReactNode; 5 title: string; 6 excerpt: string; 7 author: string; 8 publishedAt: Date; 9 readTime: number; 10 category: string; 11 featured?: boolean; 12} 13 14export function BlogPostCard({ 15 className, 16 children, 17 title, 18 excerpt, 19 author, 20 publishedAt, 21 readTime, 22 category, 23 featured = false 24}: BlogPostCardProps) { 25 return ( 26 <article className={cn( 27 "border rounded-lg p-6 bg-card hover:shadow-md transition-shadow", 28 featured && "border-primary bg-primary/5", 29 className 30 )}> 31 {featured && ( 32 <div className="text-xs text-primary font-medium mb-2"> 33 ⭐ Featured Post 34 </div> 35 )} 36 <div className="text-sm text-muted-foreground mb-2"> 37 {category} • {readTime} min read 38 </div> 39 <h2 className="text-xl font-semibold mb-3">{title}</h2> 40 <p className="text-muted-foreground mb-4">{excerpt}</p> 41 <div className="flex items-center justify-between"> 42 <div className="text-sm"> 43 By {author} • {publishedAt.toLocaleDateString()} 44 </div> 45 {children} 46 </div> 47 </article> 48 ); 49} 50 51// Component definition with all features 52const blogPostCardDefinition = { 53 component: BlogPostCard, 54 schema: z.object({ 55 className: z.string().optional(), 56 children: z.any().optional(), 57 title: z.string().default('Sample Blog Post Title'), 58 excerpt: z.string().default('A brief description of the blog post content...'), 59 author: z.string().default('John Author'), 60 publishedAt: z.coerce.date().default(new Date()), 61 readTime: z.coerce.number().default(5), 62 category: z.string().default('Technology'), 63 featured: z.boolean().default(false), 64 }), 65 from: '@/components/ui/blog-post-card', 66 fieldOverrides: { 67 className: (layer) => classNameFieldOverrides(layer), 68 children: (layer) => childrenFieldOverrides(layer), 69 excerpt: (layer) => ({ 70 fieldType: 'textarea', 71 inputProps: { 72 placeholder: 'Brief post description...' 73 } 74 }), 75 publishedAt: (layer) => ({ 76 fieldType: 'date' 77 }) 78 }, 79 defaultVariableBindings: [ 80 { 81 propName: 'author', 82 variableId: 'current-author-name', 83 immutable: false 84 } 85 ], 86 defaultChildren: [ 87 { 88 id: 'post-actions', 89 type: 'div', 90 name: 'Post Actions', 91 props: { className: 'flex gap-2' }, 92 children: [ 93 { 94 id: 'read-more', 95 type: 'Button', 96 name: 'Read More', 97 props: { 98 variant: 'outline', 99 size: 'sm' 100 }, 101 children: 'Read More' 102 } 103 ] 104 } 105 ] 106};
View the Immutable Bindings Example to see custom components with automatic variable bindings in action. This example demonstrates:
After adding your component to the registry:
With these patterns, your custom components will provide a seamless editing experience while maintaining the flexibility and power of your existing React components.