LogoLogo
HomeDiscourseBlogDiscord
  • Introduction
  • Introduction
    • Quick start
    • Aurelia for new developers
    • Hello world
      • Creating your first app
      • Your first component - part 1: the view model
      • Your first component - part 2: the view
      • Running our app
      • Next steps
  • Templates
    • Template Syntax
      • Attribute binding
      • Event binding
      • Text interpolation
      • Template promises
      • Template references
      • Template variables
      • Globals
    • Custom attributes
    • Value converters (pipes)
    • Binding behaviors
    • Form Inputs
    • CSS classes and styling
    • Conditional Rendering
    • List Rendering
    • Lambda Expressions
    • Local templates (inline templates)
    • SVG
  • Components
    • Component basics
    • Component lifecycles
    • Bindable properties
    • Styling components
    • Slotted content
    • Scope and context
    • CustomElement API
    • Template compilation
      • processContent
      • Extending templating syntax
      • Modifying template parsing with AttributePattern
      • Extending binding language
      • Using the template compiler
      • Attribute mapping
  • Getting to know Aurelia
    • Routing
      • @aurelia/router
        • Getting Started
        • Creating Routes
        • Routing Lifecycle
        • Viewports
        • Navigating
        • Route hooks
        • Router animation
        • Route Events
        • Router Tutorial
        • Router Recipes
      • @aurelia/router-lite
        • Getting started
        • Router configuration
        • Configuring routes
        • Viewports
        • Navigating
        • Lifecycle hooks
        • Router hooks
        • Router events
        • Navigation model
        • Current route
        • Transition plan
    • App configuration and startup
    • Enhance
    • Template controllers
    • Understanding synchronous binding
    • Dynamic composition
    • Portalling elements
    • Observation
      • Observing property changes with @observable
      • Effect observation
      • HTML observation
      • Using observerLocator
    • Watching data
    • Dependency injection (DI)
    • App Tasks
    • Task Queue
    • Event Aggregator
  • Developer Guides
    • Animation
    • Testing
      • Overview
      • Testing attributes
      • Testing components
      • Testing value converters
      • Working with the fluent API
      • Stubs, mocks & spies
    • Logging
    • Building plugins
    • Web Components
    • UI virtualization
    • Errors
      • Kernel Errors
      • Template Compiler Errors
      • Dialog Errors
      • Runtime HTML Errors
    • Bundlers
    • Recipes
      • Apollo GraphQL integration
      • Auth0 integration
      • Containerizing Aurelia apps with Docker
      • Cordova/Phonegap integration
      • CSS-in-JS with Emotion
      • DOM style injection
      • Firebase integration
      • Markdown integration
      • Multi root
      • Progress Web Apps (PWA's)
      • Securing an app
      • SignalR integration
      • Strongly-typed templates
      • TailwindCSS integration
      • WebSockets Integration
      • Web Workers Integration
    • Playground
      • Binding & Templating
      • Custom Attributes
        • Binding to Element Size
      • Integration
        • Microsoft FAST
        • Ionic
    • Migrating to Aurelia 2
      • For plugin authors
      • Side-by-side comparison
    • Cheat Sheet
  • Aurelia Packages
    • Validation
      • Validation Tutorial
      • Plugin Configuration
      • Defining & Customizing Rules
      • Architecture
      • Tagging Rules
      • Model Based Validation
      • Validation Controller
      • Validate Binding Behavior
      • Displaying Errors
      • I18n Internationalization
      • Migration Guide & Breaking Changes
    • i18n Internationalization
    • Fetch Client
      • Overview
      • Setup and Configuration
      • Response types
      • Working with forms
      • Intercepting responses & requests
      • Advanced
    • Event Aggregator
    • State
    • Store
      • Configuration and Setup
      • Middleware
    • Dialog
  • Tutorials
    • Building a ChatGPT inspired app
    • Building a realtime cryptocurrency price tracker
    • Building a todo application
    • Building a weather application
    • Building a widget-based dashboard
    • React inside Aurelia
    • Svelte inside Aurelia
    • Synthetic view
    • Vue inside Aurelia
  • Community Contribution
    • Joining the community
    • Code of conduct
    • Contributor guide
    • Building and testing aurelia
    • Writing documentation
    • Translating documentation
Powered by GitBook
On this page
  • Basic Class Binding
  • Single Class Binding: The .class Syntax
  • Multiple Classes: Comma-Separated Syntax
  • Style Binding
  • Single Style Properties
  • Alternative Style Syntax
  • CSS Custom Properties
  • Vendor Prefixes
  • The !important Declaration
  • Advanced Class Binding Techniques
  • String-Based Class Binding
  • Advanced Style Binding
  • Object-Based Style Binding
  • String Interpolation
  • Computed Style Properties
  • Component Styling Strategies
  • Convention-Based Styling
  • Shadow DOM
  • Shadow DOM Special Selectors
  • Global Shared Styles in Shadow DOM
  • CSS Modules
  • Real-World Examples and Patterns
  • 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
  • Performance Optimization
  • Troubleshooting Common Issues
  • "My styles aren't updating!"
  • "My CSS classes have weird names!"
  • "Shadow DOM is blocking my global styles!"
  • Migration and Compatibility
  • Coming from Aurelia 1?
  • Browser Support
  • Summary

Was this helpful?

Export as PDF
  1. Components

Styling components

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:

<button submit.class="isFormValid">Submit Form</button>
<div loading.class="isLoading">Content here...</div>
<nav-item active.class="isCurrentPage">Home</nav-item>
export class MyComponent {
  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".

Multiple Classes: Comma-Separated Syntax

When you need to toggle multiple related classes together, you can use comma-separated class names:

<div alert,alert-danger,fade-in,shake.class="hasError">
  Error message content
</div>
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
  }
}

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:

<div background-color.style="themeColor">Themed content</div>
<progress width.style="progressPercentage + '%'">Loading...</progress>
<aside opacity.style="sidebarVisible ? '1' : '0.3'">Sidebar</aside>
export class ThemedComponent {
  themeColor = '#3498db';
  progressPercentage = 75;
  sidebarVisible = true;
}

Alternative Style Syntax

Aurelia supports two equivalent syntaxes for style binding:

<!-- 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>

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.

CSS Custom Properties

Aurelia fully supports CSS custom properties (CSS variables), enabling powerful theming capabilities:

<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 ThemeManager {
  brandColor = '#e74c3c';

  switchToDarkMode() {
    this.brandColor = '#34495e';
  }
}

Vendor Prefixes

Aurelia supports vendor-prefixed CSS properties for cross-browser compatibility:

<div -webkit-user-select.style="userSelectValue">Non-selectable content</div>
<div style.-webkit-user-select="userSelectValue">Alternative syntax</div>

The !important Declaration

Aurelia automatically handles the !important CSS declaration when included in style values:

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
}

Advanced Class Binding Techniques

Advanced class binding techniques provide greater flexibility for complex styling scenarios.

String-Based Class Binding

For scenarios requiring more flexibility than boolean toggling, you can bind class strings directly:

<div class.bind="dynamicClasses">Content with dynamic classes</div>
<div class="base-class ${additionalClasses}">Mixed static and dynamic</div>
export class FlexibleComponent {
  dynamicClasses = 'btn btn-primary active';
  additionalClasses = 'fade-in hover-effect';

  updateClasses() {
    this.dynamicClasses = `btn btn-${this.isSuccess ? 'success' : 'danger'}`;
  }
}

When to use what:

  • .class syntax: When you need boolean toggling of specific classes

  • class.bind: When you need to build class strings dynamically

  • Template interpolation: When you want to mix static and dynamic classes

Advanced Style Binding

Advanced style binding techniques enable sophisticated styling patterns and better code organization.

Object-Based Style Binding

For complex styling scenarios, bind an entire style object:

<div style.bind="cardStyles">Beautifully styled card</div>
export class StylishComponent {
  cardStyles = {
    backgroundColor: '#ffffff',
    border: '1px solid #e1e1e1',
    borderRadius: '8px',
    padding: '16px',
    boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
  };

  switchToNightMode() {
    this.cardStyles = {
      ...this.cardStyles,
      backgroundColor: '#2d3748',
      color: '#ffffff',
      borderColor: '#4a5568'
    };
  }
}

String Interpolation

Combine static and dynamic styles using template interpolation:

<div style="padding: 16px; background: ${bgColor}; transform: scale(${scale})">
  Combined static and dynamic styles
</div>
export class HybridComponent {
  bgColor = 'linear-gradient(45deg, #3498db, #2ecc71)';
  scale = 1.0;

  animateIn() {
    this.scale = 1.1;
  }
}

Computed Style Properties

Create dynamic styles based on component state:

export class ComputedStyleComponent {
  progress = 0.7;
  theme = 'light';

  get progressBarStyles() {
    return {
      width: `${this.progress * 100}%`,
      backgroundColor: this.theme === 'dark' ? '#3498db' : '#2ecc71',
      transition: 'all 0.3s ease'
    };
  }
}
<div class="progress-container">
  <div class="progress-bar" style.bind="progressBarStyles"></div>
</div>

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:

my-awesome-component.ts    (component logic)
my-awesome-component.html  (template)
my-awesome-component.css   (styles - automatically imported!)

This means you can focus on writing CSS without worrying about imports:

/* my-awesome-component.css */
:host {
  display: block;
  padding: 16px;
}

.content {
  background: linear-gradient(45deg, #3498db, #2ecc71);
  border-radius: 8px;
}

Shadow DOM

For complete style isolation, use Shadow DOM:

import { useShadowDOM } from 'aurelia';

@useShadowDOM()
export class IsolatedComponent {
  // Styles are completely encapsulated
}

Shadow DOM Configuration Options:

// 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 { }

// Disable Shadow DOM for a specific component
@useShadowDOM(false)
export class NoShadowComponent { }

Shadow DOM Special Selectors

Shadow DOM provides special CSS selectors for enhanced styling control:

/* 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;
}

Global Shared Styles in Shadow DOM

To share styles across Shadow DOM components, configure shared styles in your application:

// 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();

CSS Modules

CSS Modules provide an alternative to Shadow DOM for scoped styling:

/* my-component.module.css */
.title {
  font-size: 24px;
  color: #333;
}

.button {
  composes: title; /* Inherit styles from title */
  background-color: #3498db;
  padding: 8px 16px;
}
<!-- Webpack transforms class names to unique identifiers -->
<h1 class="title">My Title</h1>
<button class="button">Click Me</button>

Real-World Examples and Patterns

The following examples demonstrate practical applications of class and style binding techniques in common scenarios.

Responsive Design with Dynamic Classes

export class ResponsiveComponent {
  screenSize = 'desktop';

  get responsiveClasses() {
    return {
      'mobile-layout': this.screenSize === 'mobile',
      'tablet-layout': this.screenSize === 'tablet',
      'desktop-layout': this.screenSize === 'desktop'
    };
  }

  @listener('resize', window)
  updateScreenSize() {
    const width = window.innerWidth;
    if (width < 768) {
      this.screenSize = 'mobile';
    } else if (width < 1024) {
      this.screenSize = 'tablet';
    } else {
      this.screenSize = 'desktop';
    }
  }
}
<div class.bind="responsiveClasses">
  <header class="header ${screenSize === 'mobile' ? 'mobile-header' : ''}">
    <!-- Responsive header -->
  </header>
</div>

Theme System with CSS Variables

export class ThemeManager {
  currentTheme = 'light';

  get themeVariables() {
    const themes = {
      light: {
        '--primary-color': '#3498db',
        '--background-color': '#ffffff',
        '--text-color': '#333333'
      },
      dark: {
        '--primary-color': '#2ecc71',
        '--background-color': '#2d3748',
        '--text-color': '#ffffff'
      }
    };

    return themes[this.currentTheme];
  }

  toggleTheme() {
    this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
  }
}
<div style.bind="themeVariables" class="theme-container">
  <button
    style="background: var(--primary-color); color: var(--text-color)"
    click.delegate="toggleTheme()">
    Toggle Theme
  </button>
</div>

Loading States with Animations

export class LoadingComponent {
  isLoading = false;
  loadingProgress = 0;

  async loadData() {
    this.isLoading = true;
    this.loadingProgress = 0;

    // Simulate loading with progress
    const interval = setInterval(() => {
      this.loadingProgress += 10;
      if (this.loadingProgress >= 100) {
        clearInterval(interval);
        this.isLoading = false;
      }
    }, 100);
  }

  get progressBarStyle() {
    return {
      width: `${this.loadingProgress}%`,
      transition: 'width 0.1s ease'
    };
  }
}
}
<div loading.class="isLoading">
  <div class="progress-container" show.bind="isLoading">
    <div class="progress-bar" style.bind="progressBarStyle"></div>
  </div>

  <div class="content" hide.bind="isLoading">
    <!-- Your actual content -->
  </div>
</div>

Complex Form Validation Styling

export class ValidationForm {
  email = '';
  password = '';

  get emailValidation() {
    return {
      isEmpty: !this.email,
      isInvalid: this.email && !this.isValidEmail(this.email),
      isValid: this.email && this.isValidEmail(this.email)
    };
  }

  get passwordValidation() {
    return {
      isEmpty: !this.password,
      isTooShort: this.password && this.password.length < 8,
      isValid: this.password && this.password.length >= 8
    };
  }

  isValidEmail(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
}
<form>
  <div class="field">
    <input
      type="email"
      value.bind="email"
      empty.class="emailValidation.isEmpty"
      invalid.class="emailValidation.isInvalid"
      valid.class="emailValidation.isValid">

    <span
      class="error-message"
      show.bind="emailValidation.isInvalid">
      Please enter a valid email
    </span>

    <span
      class="success-indicator"
      show.bind="emailValidation.isValid">
      ✓
    </span>
  </div>
</form>

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

export class OptimizedComponent {
  private _cachedStyles: any = null;
  private _lastTheme: string = '';

  // Cache expensive style calculations
  get expensiveStyles() {
    if (this._cachedStyles && this._lastTheme === this.currentTheme) {
      return this._cachedStyles;
    }

    this._cachedStyles = this.calculateComplexStyles();
    this._lastTheme = this.currentTheme;
    return this._cachedStyles;
  }

  private calculateComplexStyles() {
    // Your expensive calculations here
    return { /* styles */ };
  }
}

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.

// ❌ This won't trigger updates
export class BadComponent {
  styles = { color: 'red' };

  changeColor() {
    this.styles.color = 'blue'; // Mutation won't be detected
  }
}

// ✅ This will work
export class GoodComponent {
  styles = { color: 'red' };

  changeColor() {
    this.styles = { ...this.styles, color: 'blue' }; // New object
  }
}

"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.Solution: Configure shared styles in your app startup.

Migration and Compatibility

Coming from Aurelia 1?

The syntax is mostly the same, with some improvements:

<!-- Aurelia 1 & 2 (still works) -->
<div class.bind="myClasses"></div>

<!-- Aurelia 2 (new!) -->
<div loading,spinner,active.class="isLoading"></div>

Browser Support

All binding features work in modern browsers. For older browsers:

  • CSS custom properties require a polyfill for IE11

  • Shadow DOM requires a polyfill for older browsers

  • CSS Modules work everywhere (they're processed at build time)

Summary

This guide has covered the complete range of class and style binding capabilities in Aurelia 2. Key takeaways include:

  1. Basic class binding - Use .class syntax for simple boolean toggling

  2. Multiple class binding - Leverage comma-separated syntax for related classes

  3. Style property binding - Apply individual CSS properties with .style syntax

  4. Advanced techniques - Implement complex styling with objects, interpolation, and CSS variables

  5. 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.

PreviousBindable propertiesNextSlotted content

Last updated 4 days ago

Was this helpful?