Variable Binding

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.

Live Variable Binding Example

How Variable Binding Works

Variable binding replaces static property values with dynamic references. When bound, a component property stores a variable reference object:

tsx
1// 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

  1. Select a component in the editor canvas
  2. Open the Properties panel (right sidebar)
  3. Find the property you want to bind
  4. Click the link icon (πŸ”—) next to the property field
  5. Choose a variable from the dropdown menu
  6. 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:

  1. Select the component with bound properties
  2. Find the bound property in the props panel
  3. Click the unlink icon next to the variable card
  4. 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:

tsx
1// 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:

tsx
1const 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
tsx
1// 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:

tsx
1// 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

tsx
1import { 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

tsx
1import { 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
tsx
1// 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.