Understanding layer structure is fundamental to working with UI Builder. Layers define the hierarchical component tree that powers both the visual editor and the rendering system.
Every element in UI Builder is represented as a ComponentLayer with this structure:
tsx1interface ComponentLayer { 2 id: string; // Unique identifier 3 type: string; // Component type from registry 4 name?: string; // Optional display name for editor 5 props: Record<string, any>; // Component properties 6 children: ComponentLayer[] | string | VariableReference; // Child layers, text, or variable ref 7}
id: Required unique identifier for each layertype: Must match a key in your component registry (e.g., 'Button', 'div', 'Card')name: Optional display name shown in the layers panelprops: Object containing all component properties (className, variant, etc.)children: Array of child layers, a string for text content, or a VariableReference for dynamic texttsx1const textLayer: ComponentLayer = { 2 id: 'heading-1', 3 type: 'h1', 4 name: 'Page Title', 5 props: { 6 className: 'text-3xl font-bold text-center' 7 }, 8 children: 'Welcome to My App' 9};
tsx1const buttonLayer: ComponentLayer = { 2 id: 'cta-button', 3 type: 'Button', 4 name: 'CTA Button', 5 props: { 6 variant: 'default', 7 size: 'lg', 8 className: 'w-full max-w-sm' 9 }, 10 children: [ 11 { 12 id: 'button-text', 13 type: 'span', 14 name: 'Button Text', 15 props: {}, 16 children: 'Get Started' 17 }, 18 { 19 id: 'button-icon', 20 type: 'Icon', 21 name: 'Arrow Icon', 22 props: { 23 iconName: 'ArrowRight', 24 size: 'medium' 25 }, 26 children: [] 27 } 28 ] 29};
Layers form a tree structure where containers hold other layers:
tsx1const cardLayer: ComponentLayer = { 2 id: 'product-card', 3 type: 'div', 4 name: 'Product Card', 5 props: { 6 className: 'bg-white rounded-lg shadow-md p-6' 7 }, 8 children: [ 9 { 10 id: 'card-header', 11 type: 'div', 12 name: 'Header', 13 props: { className: 'mb-4' }, 14 children: [ 15 { 16 id: 'product-title', 17 type: 'h3', 18 name: 'Product Title', 19 props: { className: 'text-xl font-semibold' }, 20 children: 'Amazing Product' 21 }, 22 { 23 id: 'product-badge', 24 type: 'Badge', 25 name: 'Status Badge', 26 props: { variant: 'secondary' }, 27 children: 'New' 28 } 29 ] 30 }, 31 { 32 id: 'card-content', 33 type: 'div', 34 name: 'Content', 35 props: { className: 'space-y-3' }, 36 children: [ 37 { 38 id: 'description', 39 type: 'p', 40 name: 'Description', 41 props: { className: 'text-gray-600' }, 42 children: 'This product will change your life.' 43 }, 44 { 45 id: 'price', 46 type: 'div', 47 name: 'Price Container', 48 props: { className: 'flex items-center justify-between' }, 49 children: [ 50 { 51 id: 'price-text', 52 type: 'span', 53 name: 'Price', 54 props: { className: 'text-2xl font-bold text-green-600' }, 55 children: '$99.99' 56 }, 57 { 58 id: 'buy-button', 59 type: 'Button', 60 name: 'Buy Button', 61 props: { variant: 'default', size: 'sm' }, 62 children: 'Add to Cart' 63 } 64 ] 65 } 66 ] 67 } 68 ] 69};
Layers that hold and organize other layers:
tsx1// Flex container 2{ 3 id: 'nav-container', 4 type: 'div', 5 name: 'Navigation', 6 props: { 7 className: 'flex items-center justify-between p-4' 8 }, 9 children: [/* nav items */] 10} 11 12// Grid container 13{ 14 id: 'grid-layout', 15 type: 'div', 16 name: 'Image Grid', 17 props: { 18 className: 'grid grid-cols-1 md:grid-cols-3 gap-6' 19 }, 20 children: [/* grid items */] 21}
Layers that display content:
tsx1// Text content 2{ 3 id: 'paragraph-1', 4 type: 'p', 5 name: 'Description', 6 props: { 7 className: 'text-base leading-relaxed' 8 }, 9 children: 'Your content here...' 10} 11 12// Rich content 13{ 14 id: 'article-content', 15 type: 'Markdown', 16 name: 'Article Body', 17 props: {}, 18 children: '# Article Title\n\nThis is **markdown** content.' 19} 20 21// Images 22{ 23 id: 'hero-image', 24 type: 'img', 25 name: 'Hero Image', 26 props: { 27 src: '/hero.jpg', 28 alt: 'Hero image', 29 className: 'w-full h-64 object-cover' 30 }, 31 children: [] 32}
Layers that users can interact with:
tsx1// Buttons 2{ 3 id: 'submit-btn', 4 type: 'Button', 5 name: 'Submit Button', 6 props: { 7 type: 'submit', 8 variant: 'default' 9 }, 10 children: 'Submit Form' 11} 12 13// Form inputs 14{ 15 id: 'email-input', 16 type: 'Input', 17 name: 'Email Field', 18 props: { 19 type: 'email', 20 placeholder: 'Enter your email', 21 className: 'w-full' 22 }, 23 children: [] 24}
Layers can have different types of children:
For simple text content:
tsx1{ 2 id: 'simple-text', 3 type: 'p', 4 name: 'Paragraph', 5 props: {}, 6 children: 'This is simple text content' // string 7}
For nested components:
tsx1{ 2 id: 'container', 3 type: 'div', 4 name: 'Container', 5 props: {}, 6 children: [ // array of ComponentLayer objects 7 { 8 id: 'child-1', 9 type: 'span', 10 name: 'First Child', 11 props: {}, 12 children: 'Hello' 13 }, 14 { 15 id: 'child-2', 16 type: 'span', 17 name: 'Second Child', 18 props: {}, 19 children: 'World' 20 } 21 ] 22}
For dynamic text content bound to variables:
tsx1{ 2 id: 'dynamic-text', 3 type: 'span', 4 name: 'Dynamic Text', 5 props: {}, 6 children: { __variableRef: 'welcomeMessage' } // VariableReference 7}
When rendered, the variable reference is resolved to the variable's value.
For self-closing elements:
tsx1{ 2 id: 'line-break', 3 type: 'br', 4 name: 'Line Break', 5 props: {}, 6 children: [] // empty array 7}
Ensure every layer has a unique id:
tsx1// ✅ Good - unique IDs 2{ id: 'header-logo', type: 'img', ... } 3{ id: 'nav-menu', type: 'nav', ... } 4{ id: 'footer-copyright', type: 'p', ... } 5 6// ❌ Bad - duplicate IDs 7{ id: 'button', type: 'Button', ... } 8{ id: 'button', type: 'Button', ... } // Duplicate!
Use descriptive names for the layers panel:
tsx1// ✅ Good - descriptive names 2{ id: 'hero-cta', name: 'Hero Call-to-Action', type: 'Button', ... } 3{ id: 'product-grid', name: 'Product Grid', type: 'div', ... } 4 5// ❌ Bad - generic names 6{ id: 'button-1', name: 'Button', type: 'Button', ... } 7{ id: 'div-2', name: 'div', type: 'div', ... }
Make sure all referenced component types exist in your registry:
tsx1// If your Button has span children, include span in registry 2const registry = { 3 ...primitiveComponentDefinitions, // includes 'span' 4 Button: { /* your button definition */ } 5};
Follow HTML semantic structure where possible:
tsx1// ✅ Good - semantic structure 2{ 3 id: 'article', 4 type: 'article', 5 name: 'Blog Post', 6 children: [ 7 { id: 'title', type: 'h1', name: 'Title', ... }, 8 { id: 'meta', type: 'div', name: 'Meta Info', ... }, 9 { id: 'content', type: 'div', name: 'Content', ... } 10 ] 11}