Style Binding
Dynamically control element styles and CSS classes based on component state. WildflowerJS provides both data-bind-style for inline styles and data-bind-class for CSS class manipulation.
- Dynamic inline styles via
data-bind-style - Conditional CSS classes via
data-bind-class - Expression-based evaluation
- Access to state, computed properties, and component scope
Dynamic Inline Styles
Basic Style Binding
Use data-bind-style with a computed property name that returns a style object. The computed property should return an object with CSS properties (camelCase):
<div data-component="style-demo">
<!-- Basic style binding with computed style object -->
<div data-bind-style="boxStyle">
<p>This box uses dynamic colors!</p>
<p>Background: <span data-bind="bgColorDisplay"></span></p>
<p>Text: <span data-bind="textColorDisplay"></span></p>
</div>
<!-- Controls -->
<div class="mt-3">
<button class="btn btn-primary btn-sm me-2" data-action="randomColors">
Random Colors
</button>
<button class="btn btn-secondary btn-sm" data-action="resetColors">
Reset
</button>
</div>
</div>
wildflower.component('style-demo', {
state: {
bgColor: '#3498db',
textColor: '#ffffff'
},
computed: {
boxStyle() {
return {
backgroundColor: this.bgColor,
color: this.textColor,
padding: '20px',
borderRadius: '8px',
textAlign: 'center'
};
},
bgColorDisplay() {
return this.bgColor;
},
textColorDisplay() {
return this.textColor;
}
},
randomColors() {
const colors = [
{ bg: '#e74c3c', text: '#ffffff' },
{ bg: '#2ecc71', text: '#ffffff' },
{ bg: '#9b59b6', text: '#ffffff' },
{ bg: '#f39c12', text: '#000000' },
{ bg: '#1abc9c', text: '#ffffff' },
{ bg: '#34495e', text: '#ffffff' }
];
const chosen = colors[Math.floor(Math.random() * colors.length)];
this.bgColor = chosen.bg;
this.textColor = chosen.text;
},
resetColors() {
this.bgColor = '#3498db';
this.textColor = '#ffffff';
}
});
Dynamic Styles with Calculations
Computed properties can perform calculations to determine styles dynamically:
<div data-component="progress-demo">
<!-- Progress bar using computed styles -->
<div style="background: #ecf0f1; border-radius: 10px; overflow: hidden; height: 30px;">
<div data-bind-style="progressStyle">
<span data-bind="progressDisplay"></span>
</div>
</div>
<!-- Temperature indicator -->
<div class="mt-4">
<p>Temperature: <strong data-bind="temperatureDisplay"></strong></p>
<div data-bind-style="temperatureStyle"
style="padding: 10px; border-radius: 8px; text-align: center;">
<span data-bind="temperatureLabel"></span>
</div>
</div>
<!-- Controls -->
<div class="mt-3">
<button class="btn btn-sm btn-primary me-2" data-action="decreaseProgress">-10%</button>
<button class="btn btn-sm btn-primary me-2" data-action="increaseProgress">+10%</button>
<button class="btn btn-sm btn-secondary me-2" data-action="decreaseTemp">Cooler</button>
<button class="btn btn-sm btn-danger" data-action="increaseTemp">Hotter</button>
</div>
</div>
wildflower.component('progress-demo', {
state: {
progress: 45,
temperature: 20
},
computed: {
progressDisplay() {
return this.progress + '%';
},
temperatureDisplay() {
return this.temperature + '°C';
},
progressStyle() {
// Calculate color based on progress
const hue = (this.progress / 100) * 120; // 0=red, 120=green
return {
width: this.progress + '%',
height: '100%',
backgroundColor: `hsl(${hue}, 70%, 50%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontWeight: 'bold',
transition: 'all 0.3s ease'
};
},
temperatureStyle() {
const temp = this.temperature;
let bg, text;
if (temp < 10) {
bg = '#3498db'; text = '#fff'; // Cold - blue
} else if (temp < 25) {
bg = '#2ecc71'; text = '#fff'; // Comfortable - green
} else if (temp < 35) {
bg = '#f39c12'; text = '#000'; // Warm - orange
} else {
bg = '#e74c3c'; text = '#fff'; // Hot - red
}
return {
backgroundColor: bg,
color: text,
transition: 'all 0.3s ease'
};
},
temperatureLabel() {
const temp = this.temperature;
if (temp < 10) return 'Cold!';
if (temp < 25) return 'Comfortable';
if (temp < 35) return 'Warm';
return 'Hot!';
}
},
increaseProgress() {
this.progress = Math.min(100, this.progress + 10);
},
decreaseProgress() {
this.progress = Math.max(0, this.progress - 10);
},
increaseTemp() {
this.temperature = Math.min(50, this.temperature + 5);
},
decreaseTemp() {
this.temperature = Math.max(-10, this.temperature - 5);
}
});
Dynamic CSS Classes
Object Class Binding
The most concise way to toggle classes is with object syntax, an object where keys are class names and values are boolean expressions:
<div data-component="object-class-demo">
<!-- Single conditional class -->
<div class="box" data-bind-class="{ active: isActive }">
<span data-bind="isActive ? 'Active' : 'Inactive'"></span>
</div>
<!-- Multiple conditional classes -->
<div class="alert" data-bind-class="{ error: hasError, warning: hasWarning, success: isSuccess }">
Status indicator
</div>
<!-- Expression-based conditions -->
<div class="item" data-bind-class="{ selected: id === selectedId, highlighted: priority === 'high' }">
Item <span data-bind="id"></span>
</div>
<div class="mt-3">
<button class="btn btn-primary btn-sm me-2" data-action="toggleActive">Toggle Active</button>
<button class="btn btn-secondary btn-sm me-2" data-action="cycleErrors">Cycle Errors</button>
<button class="btn btn-info btn-sm" data-action="toggleSelection">Toggle Selection</button>
</div>
</div>
wildflower.component('object-class-demo', {
state: {
isActive: true,
hasError: false,
hasWarning: true,
isSuccess: false,
id: 1,
selectedId: 1,
priority: 'high'
},
toggleActive() {
this.isActive = !this.isActive;
},
cycleErrors() {
if (this.hasError) {
this.hasError = false;
this.isSuccess = true;
this.hasWarning = false;
} else if (this.isSuccess) {
this.isSuccess = false;
this.hasWarning = true;
} else {
this.hasWarning = false;
this.hasError = true;
}
},
toggleSelection() {
this.selectedId = this.selectedId === 1 ? 99 : 1;
}
});
.box {
padding: 20px; text-align: center;
border: 2px solid #bdc3c7; background: #ecf0f1;
margin-bottom: 1rem; transition: all 0.3s ease;
}
.box.active {
border-color: #3498db; background: #e8f4fc;
box-shadow: 0 0 10px rgba(52,152,219,0.3);
}
.alert {
padding: 15px; border-radius: 8px; margin-bottom: 1rem;
background: #f8f9fa; border: 1px solid #dee2e6;
transition: all 0.3s ease;
}
.alert.error { background: #f8d7da; border-color: #f5c6cb; color: #721c24; }
.alert.warning { background: #fff3cd; border-color: #ffc107; color: #856404; }
.alert.success { background: #d4edda; border-color: #c3e6cb; color: #155724; }
.item {
padding: 10px; border: 1px solid #dee2e6;
border-radius: 4px; transition: all 0.3s ease;
}
.item.selected { background: #e8f4fc; border-color: #3498db; color: #1a5276; }
.item.highlighted { font-weight: bold; }
{ className: condition } on standalone elements, inside data-list templates, and inside conditional blocks (data-show, data-render). Static classes on the element (via the class attribute) are always preserved.
Expression Class Binding
Use data-bind-class with a JavaScript expression that returns a string of class names:
<div data-component="class-demo">
<!-- Conditional class with ternary -->
<div data-bind-class="isActive ? 'box active' : 'box'"
style="padding: 20px; margin-bottom: 1rem; text-align: center;">
<p>Status: <span data-bind="isActive ? 'Active' : 'Inactive'"></span></p>
</div>
<!-- Multiple conditional classes -->
<div data-bind-class="'alert ' + (status === 'success' ? 'alert-success' : status === 'error' ? 'alert-danger' : 'alert-info')">
<strong>Status:</strong> <span data-bind="status"></span>
</div>
<!-- Size classes -->
<button data-bind-class="buttonClass">
Button (<span data-bind="size"></span>)
</button>
<!-- Controls -->
<div class="mt-3">
<button class="btn btn-primary btn-sm me-2" data-action="toggleActive">
Toggle Active
</button>
<button class="btn btn-secondary btn-sm me-2" data-action="cycleStatus">
Cycle Status
</button>
<button class="btn btn-info btn-sm" data-action="cycleSize">
Cycle Size
</button>
</div>
</div>
wildflower.component('class-demo', {
state: {
isActive: false,
status: 'info',
size: 'normal'
},
computed: {
buttonClass() {
const sizeClasses = {
'small': 'btn btn-primary btn-sm',
'normal': 'btn btn-primary',
'large': 'btn btn-primary btn-lg'
};
return sizeClasses[this.size] || 'btn btn-primary';
}
},
toggleActive() {
this.isActive = !this.isActive;
},
cycleStatus() {
const statuses = ['info', 'success', 'error'];
const currentIndex = statuses.indexOf(this.status);
this.status = statuses[(currentIndex + 1) % statuses.length];
},
cycleSize() {
const sizes = ['small', 'normal', 'large'];
const currentIndex = sizes.indexOf(this.size);
this.size = sizes[(currentIndex + 1) % sizes.length];
}
});
.box {
border: 2px solid #bdc3c7;
background-color: #ecf0f1;
transition: all 0.3s ease;
}
.box.active {
border-color: #3498db;
background-color: #e8f4fc;
box-shadow: 0 0 10px rgba(52, 152, 219, 0.3);
}
.alert {
padding: 15px;
border-radius: 8px;
margin-bottom: 1rem;
}
.alert-info {
background-color: #d1ecf1;
border: 1px solid #bee5eb;
color: #0c5460;
}
.alert-success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.alert-danger {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
Class Binding with Computed Properties
Use computed properties for complex class logic:
<div data-component="card-demo">
<!-- Card with multiple computed class conditions -->
<div data-bind-class="cardClasses"
style="padding: 20px; border-radius: 8px; margin-bottom: 1rem;">
<h5 data-bind="title"></h5>
<p data-bind="description"></p>
<div>
<span class="badge" data-bind-class="priorityBadgeClass">
<span data-bind="priority"></span> priority
</span>
<span class="badge ms-2" data-bind-class="statusBadgeClass">
<span data-bind="status"></span>
</span>
</div>
</div>
<!-- Controls -->
<div class="mt-3">
<select class="form-select form-select-sm d-inline-block w-auto me-2" data-model="priority">
<option value="low">Low Priority</option>
<option value="medium">Medium Priority</option>
<option value="high">High Priority</option>
</select>
<select class="form-select form-select-sm d-inline-block w-auto me-2" data-model="status">
<option value="pending">Pending</option>
<option value="in-progress">In Progress</option>
<option value="completed">Completed</option>
</select>
<div class="form-check form-check-inline">
<input type="checkbox" class="form-check-input" data-model="featured" id="featured">
<label class="form-check-label" for="featured">Featured</label>
</div>
</div>
</div>
wildflower.component('card-demo', {
state: {
title: 'Task Card Example',
description: 'This card demonstrates computed class bindings.',
priority: 'medium',
status: 'pending',
featured: false
},
computed: {
cardClasses() {
const classes = ['card'];
// Priority-based border color
if (this.priority === 'high') {
classes.push('border-danger');
} else if (this.priority === 'medium') {
classes.push('border-warning');
} else {
classes.push('border-info');
}
// Status-based background with matching dark text
if (this.status === 'completed') {
classes.push('bg-success-subtle text-success-emphasis');
} else if (this.status === 'in-progress') {
classes.push('bg-warning-subtle text-warning-emphasis');
}
// Featured highlight - use ring glow that's visible in both light/dark themes
if (this.featured) {
classes.push('featured-glow');
}
return classes.join(' ');
},
priorityBadgeClass() {
const colors = {
'low': 'bg-info',
'medium': 'bg-warning text-dark',
'high': 'bg-danger'
};
return colors[this.priority] || 'bg-secondary';
},
statusBadgeClass() {
const colors = {
'pending': 'bg-secondary',
'in-progress': 'bg-primary',
'completed': 'bg-success'
};
return colors[this.status] || 'bg-secondary';
}
}
});
Style Binding Syntax Reference
| Binding Type | Syntax | Description |
|---|---|---|
data-bind-style (recommended) |
data-bind-style="styleObject" |
Reference computed property that returns style object |
data-bind-style (inline) |
data-bind-style="{ color: textColor }" |
Inline object expression (simple cases only) |
data-bind-class (object) |
data-bind-class="{ active: isActive }" |
Object with class names as keys, booleans as values |
data-bind-class (expression) |
data-bind-class="isActive ? 'active' : ''" |
Expression returning class string |
data-bind-class (computed) |
data-bind-class="cardClasses" |
Computed property name (prefix optional) |
How Expression Resolution Works
Understanding how WildflowerJS resolves values in different binding types helps you write cleaner, more predictable code.
data-bind (Text Binding)
data-bind resolves values automatically. State and computed properties are both accessible by name:
data-bind="count"- Looks upstate.countdata-bind="total"- Looks up computed propertytotal(or state if no computed exists)data-bind="props.label"- Looks upprops.label
When a computed property and state property share the same name, computed takes precedence. The computed: prefix can be used for clarity but is never required.
data-bind-style (Style Binding)
data-bind-style uses a merged context for expression evaluation. Before evaluating your expression, the framework creates a single object containing:
- All
stateproperties - All evaluated
computedproperty values - List item data (when inside a
data-list)
This means you can reference any state or computed property directly by name:
<!-- Both work - no prefix needed -->
<div data-bind-style="myStyleObject"> <!-- works for state OR computed -->
<div data-bind-style="{ color: textColor }"> <!-- textColor from state OR computed -->
data-bind-class (Class Binding)
data-bind-class supports three approaches:
- Object syntax
{ className: condition }: toggles each class based on its boolean value - Expressions evaluate against the merged context (like style bindings)
- computed: prefix explicitly references a computed property
<!-- Object syntax - toggle classes by boolean -->
<div data-bind-class="{ active: isActive, highlighted: priority === 'high' }">
<!-- Expression - merged context, no prefix needed -->
<div data-bind-class="isActive ? 'active' : ''">
<!-- Computed reference - no prefix needed -->
<div data-bind-class="cardClasses">
Quick Reference
| Binding | State Access | Computed Access | Resolution |
|---|---|---|---|
data-bind |
propertyName |
propertyName or computed:propertyName |
Auto-resolved (computed wins) |
data-bind-style |
propertyName |
propertyName |
Merged context |
data-bind-class |
propertyName |
propertyName or computed:propertyName |
Both supported |
computed: prefix is always optional. When a computed property and state property share the same name, computed takes precedence. The prefix can still be useful for readability, making it clear that a value is derived rather than stored.
- More reliable - avoid parsing issues with complex expressions
- More maintainable - keep logic in JavaScript, not HTML
- More performant - cached and only recalculated when dependencies change
Best Practices
- Use computed properties for complex style/class logic - keeps templates clean
- Prefer classes over inline styles when possible - better for performance and maintainability
- Use CSS transitions for smooth visual changes when styles update
- Keep style objects simple - complex calculations should be in computed properties
- Use camelCase for CSS properties in style objects (backgroundColor, not background-color)
Performance Tip
When binding complex style objects, use computed properties to avoid re-evaluating the expression on every render cycle:
// Good - computed property caches the result
computed: {
boxStyle() {
return {
backgroundColor: this.color,
transform: `scale(${this.scale})`,
opacity: this.isVisible ? 1 : 0
};
}
}
// In template: data-bind-style="boxStyle"
data-bind-style and data-bind-class also support the data-wf- prefix (e.g., data-wf-bind-style) for compatibility with third-party libraries.