The component registry is the heart of UI Builder. It defines which React components are available in the visual editor and how they should be configured. Understanding the registry is essential for using UI Builder effectively with your own components.
Now that you understand the component registry:
Remember: The registry is just a configuration object. The real power comes from how you design your components and their schemas to create the best editing experience for your users.
The component registry is a TypeScript object that maps component type names to their definitions. It tells UI Builder:
tsx1import { ComponentRegistry } from '@/components/ui/ui-builder/types'; 2 3const myComponentRegistry: ComponentRegistry = { 4 // Complex component with React component 5 'Button': { 6 component: Button, // React component 7 schema: z.object({...}), // Zod schema for props 8 from: '@/components/ui/button' // Import path 9 }, 10 // Primitive component (no React component needed) 11 'span': { 12 schema: z.object({...}) // Just the schema 13 } 14};
Each registry entry can have these properties:
schema: Zod schema defining the component's props and their typescomponent: The React component (required for complex components)from: Import path for code generation (required for complex components)isFromDefaultExport: Boolean, use default export in generated codefieldOverrides: Object mapping prop names to custom form fieldsdefaultChildren: Array of ComponentLayer objects or stringdefaultVariableBindings: Array of automatic variable bindingschildOf: Array of parent component type names this component can be a child ofHTML elements that don't need a React component:
tsx1span: { 2 schema: z.object({ 3 className: z.string().optional(), 4 children: z.any().optional(), 5 }) 6 // No 'component' or 'from' needed 7}
Custom React components that need to be imported:
tsx1Button: { 2 component: Button, 3 schema: z.object({ 4 className: z.string().optional(), 5 children: z.any().optional(), 6 variant: z.enum(['default', 'destructive']).default('default'), 7 }), 8 from: '@/components/ui/button' 9}
UI Builder includes example component definitions for testing and getting started:
tsx1import { primitiveComponentDefinitions } from '@/lib/ui-builder/registry/primitive-component-definitions'; 2import { complexComponentDefinitions } from '@/lib/ui-builder/registry/complex-component-definitions'; 3 4const componentRegistry: ComponentRegistry = { 5 ...primitiveComponentDefinitions, // div, span, h1, h2, h3, p, ul, ol, li, img, iframe, a 6 ...complexComponentDefinitions, // Button, Badge, Card, Icon, Flexbox, Grid, Markdown, etc. 7};
Available Pre-built Components:
Primitive Components:
div, spanh1, h2, h3, pul, ol, liimg, iframea (links)Complex Components:
Flexbox, GridMarkdown, CodePanelButton, BadgeCard, Icon, AccordionHere's a minimal registry with one custom component:
tsx1import { z } from 'zod'; 2import { Alert } from '@/components/ui/alert'; 3import { primitiveComponentDefinitions } from '@/lib/ui-builder/registry/primitive-component-definitions'; 4import { commonFieldOverrides } from '@/lib/ui-builder/registry/form-field-overrides'; 5 6const myComponentRegistry: ComponentRegistry = { 7 // Include primitive components for basic HTML elements 8 ...primitiveComponentDefinitions, 9 10 // Add your custom component 11 Alert: { 12 component: Alert, 13 schema: z.object({ 14 className: z.string().optional(), 15 children: z.any().optional(), 16 variant: z.enum(['default', 'destructive']).default('default'), 17 }), 18 from: '@/components/ui/alert', 19 fieldOverrides: commonFieldOverrides() 20 } 21};
Important: Make sure all component types referenced in your defaultChildren are included in your registry:
tsx1const componentRegistry: ComponentRegistry = { 2 ...primitiveComponentDefinitions, // ← Includes 'span' needed below 3 Button: { 4 component: Button, 5 schema: z.object({...}), 6 from: '@/components/ui/button', 7 // This Button references 'span' in defaultChildren 8 defaultChildren: [{ 9 id: 'btn-text', 10 type: 'span', // ← Must be in registry 11 name: 'Button Text', 12 props: {}, 13 children: 'Click me' 14 }] 15 } 16};
Some components only make sense as children of specific parent components. For example, AccordionItem should only be a child of Accordion. The childOf property enforces these relationships:
tsx1AccordionItem: { 2 component: AccordionItem, 3 schema: z.object({ ... }), 4 from: '@/components/ui/accordion', 5 childOf: ['Accordion'] // Can only be added inside Accordion 6}
childOf doesn't include the current parent typechildOf constraint isn't satisfied by the target parenttsx1SelectItem: { 2 component: SelectItem, 3 schema: z.object({ ... }), 4 from: '@/components/ui/select', 5 childOf: ['SelectContent', 'SelectGroup'] // Valid in either parent 6}
| Component | childOf |
|---|---|
| AccordionItem | ['Accordion'] |
| AccordionTrigger | ['AccordionItem'] |
| CardHeader, CardContent, CardFooter | ['Card'] |
| TabsList, TabsContent | ['Tabs'] |
| TabsTrigger | ['TabsList'] |
| SelectTrigger, SelectContent | ['Select'] |
| SelectItem | ['SelectContent', 'SelectGroup'] |
| DialogTrigger, DialogContent | ['Dialog'] |
Use childOf when:
The Zod schema is crucial as it drives the auto-generated form in the properties panel:
tsx1schema: z.object({ 2 // Use .default() values for better UX 3 title: z.string().default('Default Title'), 4 5 // Use coerce for type conversion from strings 6 count: z.coerce.number().default(1), 7 8 // Boolean props become toggle switches 9 disabled: z.boolean().optional(), 10 11 // Enums become select dropdowns 12 variant: z.enum(['default', 'destructive']).default('default'), 13 14 // Special props need field overrides 15 className: z.string().optional(), 16 children: z.any().optional(), 17})
For production applications, you should create your own component registry with your specific components:
tsx1// Your production registry 2const productionRegistry: ComponentRegistry = { 3 // Add only the components you need 4 MyButton: { /* your button definition */ }, 5 MyCard: { /* your card definition */ }, 6 MyModal: { /* your modal definition */ }, 7 // Include primitives for basic HTML 8 ...primitiveComponentDefinitions, 9};
The pre-built registries are examples to help you understand the system and test quickly, but you should replace them with your own component definitions that match your design system.