Learn how to connect variables to component properties through the UI and programmatically. This page focuses on the binding mechanicsβsee Variables for fundamentals and Data Binding for external data integration.
How Variable Binding Works
Variable binding replaces static property values with dynamic references. When bound, a component property stores a variable reference object:
tsx1// Before binding - static value 2const button = { 3 props: { 4 children: 'Click me', 5 disabled: false 6 } 7}; 8 9// After binding - variable references 10const button = { 11 props: { 12 children: { __variableRef: 'button-text-var' }, 13 disabled: { __variableRef: 'is-loading-var' } 14 } 15};
π‘ See it in action: The demo above shows variable bindings with real-time value resolution from the working example.
Binding Variables Through the UI
Step-by-Step Binding Process
- Select a component in the editor canvas
- Open the Properties panel (right sidebar)
- Find the property you want to bind
- Click the link icon (π) next to the property field
- Choose a variable from the dropdown menu
- The property is now bound and shows the variable info
Visual Indicators in the Props Panel
Bound properties are visually distinct:
- Link icon (π) indicates the property supports binding
- Variable card displays when a property is bound
- Variable name and type are shown (e.g.,
userName
β’string
) - Current value shows the variable's resolved value
- Unlink button (πβ) allows unbinding (if not immutable)
- Lock icon (π) indicates immutable bindings that cannot be changed
Unbinding Variables
To remove a variable binding:
- Select the component with bound properties
- Find the bound property in the props panel
- Click the unlink icon next to the variable card
- Property reverts to its schema default value
Note: Immutable bindings (π) cannot be unbound through the UI.
Working Example: Variable Bindings in Action
Here's the actual structure from our live demo showing real variable bindings:
tsx1// Page structure with variable bindings 2const page: ComponentLayer = { 3 id: "variables-demo-page", 4 type: "div", 5 props: { 6 className: "max-w-4xl mx-auto p-8 space-y-8" 7 }, 8 children: [ 9 { 10 id: "page-title", 11 type: "h1", 12 props: { 13 className: "text-4xl font-bold text-gray-900", 14 children: { __variableRef: "pageTitle" } // β Variable binding 15 } 16 }, 17 { 18 id: "user-name", 19 type: "span", 20 props: { 21 className: "text-gray-900", 22 children: { __variableRef: "userName" } // β Another binding 23 } 24 }, 25 { 26 id: "primary-button", 27 type: "Button", 28 props: { 29 variant: "default", 30 children: { __variableRef: "buttonText" }, // β Button text binding 31 disabled: { __variableRef: "isLoading" } // β Boolean binding 32 } 33 } 34 ] 35}; 36 37// Variables that match the bindings 38const variables: Variable[] = [ 39 { 40 id: "pageTitle", 41 name: "Page Title", 42 type: "string", 43 defaultValue: "UI Builder Variables Demo" 44 }, 45 { 46 id: "userName", 47 name: "User Name", 48 type: "string", 49 defaultValue: "John Doe" 50 }, 51 { 52 id: "buttonText", 53 name: "Primary Button Text", 54 type: "string", 55 defaultValue: "Click Me!" 56 }, 57 { 58 id: "isLoading", 59 name: "Loading State", 60 type: "boolean", 61 defaultValue: false 62 } 63];
Automatic Variable Binding
Default Variable Bindings
Components can automatically bind to variables when added to the canvas:
tsx1const componentRegistry = { 2 UserProfile: { 3 component: UserProfile, 4 schema: z.object({ 5 userId: z.string().default("user_123"), 6 displayName: z.string().default("John Doe"), 7 email: z.string().email().default("john@example.com") 8 }), 9 from: "@/components/ui/user-profile", 10 defaultVariableBindings: [ 11 { 12 propName: "userId", 13 variableId: "current_user_id", 14 immutable: true // Cannot be unbound 15 }, 16 { 17 propName: "displayName", 18 variableId: "current_user_name", 19 immutable: false // Can be changed 20 } 21 ] 22 } 23};
Immutable Bindings
Immutable bindings prevent accidental unbinding of critical data:
- System data: User IDs, tenant IDs, session info
- Security: Permissions, access levels, authentication state
- Branding: Company logos, colors, brand consistency
- Template integrity: Essential bindings in white-label scenarios
tsx1// Example: Brand-consistent component with locked bindings 2const BrandedButton = { 3 component: Button, 4 schema: z.object({ 5 text: z.string().default("Click Me"), 6 brandColor: z.string().default("#3b82f6"), 7 companyName: z.string().default("Acme Corp") 8 }), 9 defaultVariableBindings: [ 10 { 11 propName: "brandColor", 12 variableId: "company_brand_color", 13 immutable: true // π Locked to maintain brand consistency 14 }, 15 { 16 propName: "companyName", 17 variableId: "company_name", 18 immutable: true // π Company identity protected 19 } 20 // text prop left unbound for content flexibility 21 ] 22};
Variable Resolution
At runtime, variable references are resolved to actual values:
tsx1// Variable reference in component props 2const buttonProps = { 3 children: { __variableRef: 'welcome-message' }, 4 disabled: { __variableRef: 'is-loading' } 5}; 6 7// Variables definition 8const variables = [ 9 { 10 id: 'welcome-message', 11 name: 'welcomeMessage', 12 type: 'string', 13 defaultValue: 'Welcome!' 14 }, 15 { 16 id: 'is-loading', 17 name: 'isLoading', 18 type: 'boolean', 19 defaultValue: false 20 } 21]; 22 23// Runtime values override defaults 24const variableValues = { 25 'welcome-message': 'Hello, Jane!', 26 'is-loading': true 27}; 28 29// Resolution process: 30// 1. Find variable by ID β 'welcome-message' 31// 2. Use runtime value if provided β 'Hello, Jane!' 32// 3. Fall back to default if no runtime value β 'Welcome!' 33// 4. Final resolved props: { children: 'Hello, Jane!', disabled: true }
π See More: Learn about Data Binding for external data integration and Rendering Pages for LayerRenderer usage.
Managing Bindings Programmatically
Using Layer Store Methods
tsx1import { useLayerStore } from '@/lib/ui-builder/store/layer-store'; 2 3function CustomBindingControl() { 4 const bindPropToVariable = useLayerStore((state) => state.bindPropToVariable); 5 const unbindPropFromVariable = useLayerStore((state) => state.unbindPropFromVariable); 6 const isBindingImmutable = useLayerStore((state) => state.isBindingImmutable); 7 8 const handleBind = () => { 9 // Bind a component's 'title' prop to a variable 10 bindPropToVariable('button-123', 'title', 'page-title-var'); 11 }; 12 13 const handleUnbind = () => { 14 // Check if binding is immutable first 15 if (!isBindingImmutable('button-123', 'title')) { 16 unbindPropFromVariable('button-123', 'title'); 17 } 18 }; 19 20 return ( 21 <div> 22 <button onClick={handleBind}>Bind Title</button> 23 <button onClick={handleUnbind}>Unbind Title</button> 24 </div> 25 ); 26}
Variable Reference Detection
tsx1import { isVariableReference } from '@/lib/ui-builder/utils/variable-resolver'; 2 3// Check if a prop value is a variable reference 4const propValue = layer.props.children; 5 6if (isVariableReference(propValue)) { 7 console.log('Bound to variable:', propValue.__variableRef); 8} else { 9 console.log('Static value:', propValue); 10}
Binding Best Practices
Design Patterns
- Use meaningful variable names that clearly indicate their purpose
- Set appropriate default values for better editor preview experience
- Use immutable bindings for system-critical or brand-related data
- Group related variables with consistent naming patterns
- Bind the same variable to multiple components for consistency
UI/UX Considerations
- Visual indicators help users understand which properties are bound
- Immutable bindings should be clearly marked to avoid user confusion
- Unbinding should revert to sensible default values from the schema
- Variable cards provide clear information about bound variables
Performance Tips
- Variable resolution is optimized through memoization in the rendering process
- Only bound properties are processed during variable resolution
- Static values pass through without processing overhead
- Variable updates trigger efficient re-renders only for affected components
Troubleshooting Binding Issues
Variable Not Found
- Check variable ID matches exactly in both definition and reference
- Verify variable exists in the variables array
- Ensure variable scope includes the needed variable in your context
Binding Not Working
- Confirm variable reference format uses
{ __variableRef: 'variable-id' }
- Check variable type compatibility with component prop expectations
- Verify component schema allows the property to be bound
Immutable Binding Issues
- Check defaultVariableBindings configuration in component registry
- Verify immutable flag is set correctly for auto-bound properties
- Use layer store methods to check binding immutability programmatically
tsx1// Debug variable bindings in browser dev tools 2const layer = useLayerStore.getState().findLayerById('my-component'); 3console.log('Layer props:', layer?.props); 4 5// Verify variable resolution 6import { resolveVariableReferences } from '@/lib/ui-builder/utils/variable-resolver'; 7 8const resolved = resolveVariableReferences( 9 layer.props, 10 variables, 11 variableValues 12); 13console.log('Resolved props:', resolved);
π Next Steps: Now that you understand variable binding mechanics, explore Data Binding to connect external data sources and Variables for variable management fundamentals.