ngClass & ngStyle
Static class and style attributes only get you so far. Real UIs react to state: a button turns green when valid, a row highlights when selected, a progress bar grows as a percentage changes. Angular gives you two attribute directives — ngClass and ngStyle — that bind CSS classes and inline styles to component state, so the DOM stays in sync with your signals automatically. They sit alongside Angular’s simpler [class.x] and [style.x] bindings, and knowing when to reach for each keeps templates clean and fast.
The simple bindings come first
Before reaching for ngClass, remember that Angular has built-in property bindings for a single class or style. They are the most direct option and should be your default when you toggle one thing.
<!-- Toggle a single class on a boolean -->
<button [class.active]="isActive()">Save</button>
<!-- Set a single style property -->
<div [style.width.px]="width()"></div>
<div [style.color]="textColor()"></div>
[class.active] adds the active class when the expression is truthy and removes it otherwise. The [style.width.px] syntax binds a numeric value with a unit suffix. Reach for ngClass/ngStyle when you need to drive several classes or styles from one expression.
Using ngClass
ngClass adds and removes CSS classes based on the value you bind. It accepts three shapes: a string, an array of strings, or an object whose keys are class names and whose values are booleans.
import { Component, signal, computed } from '@angular/core';
import { NgClass } from '@angular/common';
@Component({
selector: 'app-alert',
standalone: true,
imports: [NgClass],
template: `
<div [ngClass]="alertClasses()">{{ message() }}</div>
<button (click)="cycle()">Next severity</button>
`,
})
export class AlertComponent {
message = signal('Disk space is running low.');
severity = signal<'info' | 'warning' | 'error'>('warning');
// Object form: each key is toggled by its boolean value
alertClasses = computed(() => ({
alert: true,
'alert-info': this.severity() === 'info',
'alert-warning': this.severity() === 'warning',
'alert-error': this.severity() === 'error',
}));
cycle() {
const order = ['info', 'warning', 'error'] as const;
const next = (order.indexOf(this.severity()) + 1) % order.length;
this.severity.set(order[next]);
}
}
When severity is 'warning', the rendered element is:
Output:
<div class="alert alert-warning">Disk space is running low.</div>
The three accepted forms behave like this:
| Bound value | Example | Result |
|---|---|---|
| String | [ngClass]="'box shadow'" | Adds both box and shadow |
| Array | [ngClass]="['box', theme()]" | Adds box plus the dynamic theme class |
| Object | [ngClass]="{ active: isOn() }" | Adds active only when isOn() is truthy |
ngClass is additive: it never clobbers classes set statically in the class attribute. A static class="card" and [ngClass]="{ selected: chosen() }" coexist happily on the same element.
Computing the object in a
computed()signal (as above) is preferable to building it inline in the template. It keeps the template readable and recomputes only when a dependency actually changes.
Using ngStyle
ngStyle sets multiple inline CSS properties from an object. Keys are CSS property names (camelCase or dash-case both work) and values are the strings or numbers to apply. As with classes, you can append a unit to the key.
import { Component, signal, computed } from '@angular/core';
import { NgStyle } from '@angular/common';
@Component({
selector: 'app-meter',
standalone: true,
imports: [NgStyle],
template: `
<div class="track">
<div class="fill" [ngStyle]="barStyles()"></div>
</div>
<input type="range" min="0" max="100" [value]="percent()"
(input)="percent.set(+$any($event.target).value)" />
`,
styles: [`.track { width: 200px; background: #eee; } .fill { height: 12px; }`],
})
export class MeterComponent {
percent = signal(40);
barStyles = computed(() => ({
'width.%': this.percent(),
backgroundColor: this.percent() > 80 ? '#d33' : '#3a3',
transition: 'width 150ms ease',
}));
}
At 40 percent the fill div renders as:
Output:
<div class="fill" style="width: 40%; background-color: rgb(51, 170, 51); transition: width 150ms ease;"></div>
Note the 'width.%' key — the .% (or .px, .em, .rem) suffix tells Angular which unit to append. Without a unit suffix the value is used verbatim, which is why backgroundColor and transition pass through unchanged.
ngClass vs ngStyle at a glance
ngClass | ngStyle | |
|---|---|---|
| Targets | CSS class list | Inline style attribute |
| Best for | Reusable named styles defined in CSS | One-off dynamic values (computed width, color) |
| Specificity | Lives in stylesheets, easy to override | Inline, high specificity, hard to override |
| Accepts | String / array / object | Object only |
Prefer ngClass whenever the visual states are nameable and finite — they belong in CSS where designers can edit them. Reserve ngStyle for genuinely dynamic numeric values like positions, widths, or interpolated colors that cannot be enumerated as classes.
Best practices
- Use
[class.x]and[style.x]for a single class or property; reach forngClass/ngStyleonly when binding several at once. - Build the bound object in a
computed()signal rather than inline in the template, so it recomputes only when dependencies change. - Keep visual states in CSS via
ngClassinstead of hardcoding colors and sizes withngStyle— it keeps styling overridable and themeable. - Remember
ngClassis additive and merges with the staticclassattribute; you don’t need to repeat static classes inside it. - Always supply a unit suffix (
width.px,width.%) for numeric style values, or pass a fully formed string. - Import
NgClass/NgStyleexplicitly in standalone components — they are no longer globally available withoutCommonModule.