Master the art of dynamic styling in Aurelia 2. Learn everything from basic class toggling to advanced CSS custom properties, plus component styling strategies that will make your apps both beautiful
Dynamic styling is a fundamental aspect of modern web applications, and Aurelia 2 provides powerful, flexible mechanisms for binding CSS classes and styles to your elements. Whether you need to toggle an active state, implement a theming system, or create responsive layouts, Aurelia's binding system makes these tasks straightforward and maintainable.
This comprehensive guide covers everything from basic class toggling to advanced styling techniques, giving you the knowledge and tools to implement any styling requirement in your Aurelia 2 applications.
Basic Class Binding
The most common use case for dynamic styling is conditionally applying CSS classes based on component state.
Single Class Binding: The .class Syntax
The .class binding is the foundation of dynamic styling in Aurelia. The syntax is straightforward:
exportclassMyComponent{ isFormValid =false; isLoading =true; isCurrentPage =false; // When isFormValid becomes true, the 'submit' class gets added // When isLoading is false, the 'loading' class gets removed}
How it works: The syntax is className.class="booleanExpression". When the expression is truthy, the class is added. When it's falsy, the class is removed.
Note: You can use any valid CSS class name, including ones with special characters like my-awesome-class.class="isAwesome" or Unicode characters like ✓.class="isComplete".
TailwindCSS note: Tailwind’s content scanner won’t pick up class names that only appear in attribute names. For Tailwind classes that include special characters (for example width-[360px]), prefer the object form with class.bind so the class token appears in an attribute value:
Multiple Classes: Comma-Separated Syntax
When you need to toggle multiple related classes together, you can use comma-separated class names:
Important: No spaces around the commas! The parser expects class1,class2,class3, not class1, class2, class3.
Style Binding
Aurelia provides multiple approaches for binding CSS styles, from individual properties to complex style objects.
Single Style Properties
To bind individual CSS properties dynamically, use the .style syntax:
Alternative Style Syntax
Aurelia supports two equivalent syntaxes for style binding:
Use whichever feels more natural to you. Some developers prefer the first syntax because it reads like "set the background-color style to myColor", while others prefer the second because it's more similar to traditional CSS.
For complex styling scenarios, bind an entire style object:
String Interpolation
Combine static and dynamic styles using template interpolation:
Computed Style Properties
Create dynamic styles based on component state:
Component Styling Strategies
Beyond template bindings, Aurelia provides several approaches for styling components themselves.
Convention-Based Styling
Aurelia automatically imports stylesheets that match your component names:
This means you can focus on writing CSS without worrying about imports:
Shadow DOM
For complete style isolation, use Shadow DOM:
Shadow DOM Configuration Options:
Shadow DOM Special Selectors
Shadow DOM provides special CSS selectors for enhanced styling control:
Global Shared Styles in Shadow DOM
To share styles across Shadow DOM components, configure shared styles in your application:
CSS Modules
CSS Modules provide scoped styling by transforming class names to unique identifiers at build time. Aurelia provides the cssModules() helper to integrate CSS Modules with your components:
The cssModules() helper transforms class names in your template at compile time. In the example above, class="title" becomes class="title_abc123".
Key features:
Works with static classes, class.bind, and interpolation (class="some ${myClass}")
The following examples demonstrate practical applications of class and style binding techniques in common scenarios.
Responsive Design with Dynamic Classes
Theme System with CSS Variables
Loading States with Animations
Complex Form Validation Styling
Performance Tips and Best Practices
Do's and Don'ts
✅ DO:
Use .class for simple boolean toggling
Use CSS custom properties for theming
Prefer computed getters for complex style calculations
Use Shadow DOM for true component isolation
Cache complex style objects when possible
❌ DON'T:
Inline complex style calculations in templates
Use string concatenation for class names when .class will do
Forget about CSS specificity when using !important
Mix too many styling approaches in one component
Performance Optimization
Troubleshooting Common Issues
"My styles aren't updating!"
Problem: Styles don't change when data changes. Solution: Make sure you're using proper binding syntax and that your properties are observable.
"My CSS classes have weird names!"
Problem: Using CSS Modules and seeing transformed class names. Solution: This is expected behavior! CSS Modules transform class names to ensure uniqueness.
"Shadow DOM is blocking my global styles!"
Problem: Global CSS frameworks aren't working inside Shadow DOM components. Why: Shadow DOM isolates styles; open/closed mode does not change CSS encapsulation. Solutions:
Use Light DOM for components that should inherit global framework styles
Register framework CSS as shared styles with StyleConfiguration.shadowDOM({ sharedStyles: [...] })
Use CSS variables or ::part to expose safe customization points
Advanced techniques - Implement complex styling with objects, interpolation, and CSS variables
Component styling - Choose appropriate encapsulation strategies for your use case
These techniques provide the foundation for building maintainable, dynamic user interfaces that respond effectively to application state changes.
Additional Resources: For more information on binding syntax, see the template syntax guide. To understand when styles are applied, refer to the component lifecycles documentation.
export class ErrorComponent {
hasError = false;
triggerError() {
this.hasError = true; // All four classes get added at once!
}
clearError() {
this.hasError = false; // All four classes get removed together
}
}
<!-- These do exactly the same thing! -->
<div background-color.style="myColor"></div>
<div style.background-color="myColor"></div>
<!-- Works with any CSS property -->
<div font-size.style="textSize"></div>
<div style.font-size="textSize"></div>
<div --primary-color.style="brandColor">
<p style="color: var(--primary-color)">Branded text!</p>
</div>
<!-- Or with the alternative syntax -->
<div style.--primary-color="brandColor">
<p style="color: var(--primary-color)">Same result!</p>
</div>
export class ImportantComponent {
criticalColor = 'red!important';
// Aurelia automatically:
// 1. Strips the !important from the value
// 2. Sets the CSS property priority correctly
// 3. Applies the style with proper priority
}
<div class.bind="dynamicClasses">Content with dynamic classes</div>
<div class="base-class ${additionalClasses}">Mixed static and dynamic</div>
import { useShadowDOM } from 'aurelia';
@useShadowDOM()
export class IsolatedComponent {
// Styles are completely encapsulated
}
// Open mode (default) - JavaScript can access shadowRoot
@useShadowDOM({ mode: 'open' })
export class OpenComponent { }
// Closed mode - shadowRoot is not accessible
@useShadowDOM({ mode: 'closed' })
export class ClosedComponent { }
// To use Light DOM (no Shadow DOM), simply don't use the decorator
export class LightDomComponent { }
/* Style the component host element */
:host {
display: block;
border: 1px solid #e1e1e1;
}
/* Style the host when it has a specific class */
:host(.active) {
background-color: #f8f9fa;
}
/* Style the host based on ancestor context */
:host-context(.dark-theme) {
background-color: #2d3748;
color: #ffffff;
}
/* Style slotted content */
::slotted(.special-content) {
font-weight: bold;
color: #3498db;
}
// main.ts
import Aurelia, { StyleConfiguration } from 'aurelia';
import { MyApp } from './my-app';
import bootstrap from 'bootstrap/dist/css/bootstrap.css';
import customTheme from './theme.css';
Aurelia
.register(StyleConfiguration.shadowDOM({
sharedStyles: [bootstrap, customTheme]
}))
.app(MyApp)
.start();
import { customElement, cssModules } from 'aurelia';
// Import the CSS module (bundler provides the class mapping)
import styles from './my-component.module.css';
// styles = { title: 'title_abc123', button: 'button_def456' }
@customElement({
name: 'my-component',
template: `
<h1 class="title">My Title</h1>
<button class="button">Click Me</button>
`,
dependencies: [cssModules(styles)]
})
export class MyComponent {}