Often the look of an element depends on data: an error is red, a selected row is highlighted, a progress bar has a width. Angular gives you four ways to change classes and styles from your component.
What you will learn
- Single class and style bindings
- Binding many classes or styles at once
ngClassandngStyle, and when they are still useful
Class binding: one class on or off
<li [class.done]="task.done">{{ task.title }}</li>
The class done is present when task.done is true and removed when it is false. This is the simplest and most common form.
Style binding: one style, with units
<div [style.width.%]="progress()">...</div>
<p [style.color]="isError() ? 'crimson' : 'inherit'">Message</p>
<p [style.font-size.px]="size()">Resizable text</p>
The part after the second dot is the unit: .px, .%, .rem, .em.
Many at once: [class] and [style]
You can bind an object (or a string or array) to [class] and [style]:
<button [class]="{ primary: isPrimary(), large: isLarge() }">Save</button>
<div [style]="{ 'background-color': color(), opacity: faded() ? 0.5 : 1 }">Box</div>
ngClass and ngStyle
NgClass and NgStyle are the older directives. They accept strings, arrays and objects, and they still work in current Angular:
<span [ngClass]="{ badge: true, 'badge-ok': status() === 'ok', 'badge-bad': status() === 'bad' }">
{{ status() }}
</span>
<div [ngStyle]="{ 'width.%': progress(), background: barColor() }"></div>
Today, plain [class] and [style] bindings cover almost everything ngClass and ngStyle did, with less code and no import. Angular even ships a migration for it: ng generate @angular/core:ngclass-to-class. You will meet ngClass in older projects, so you should be able to read it.
A working example
// src/app/styling/status-demo.ts
import { Component, computed, signal } from '@angular/core';
import { NgClass, NgStyle } from '@angular/common';
type Status = 'ok' | 'warning' | 'error';
@Component({
selector: 'app-status-demo',
imports: [NgClass, NgStyle],
template: `
<h2>Class and style binding</h2>
<p>
Status:
<span [class.badge]="true" [class.ok]="status() === 'ok'" [class.warning]="status() === 'warning'" [class.error]="status() === 'error'">
{{ status() }}
</span>
</p>
<p>Same badge with ngClass:
<span class="badge" [ngClass]="{ ok: status() === 'ok', warning: status() === 'warning', error: status() === 'error' }">
{{ status() }}
</span>
</p>
<div class="track" aria-label="Progress" role="progressbar" [attr.aria-valuenow]="progress()" aria-valuemin="0" aria-valuemax="100">
<div class="bar" [style.width.%]="progress()" [style.background-color]="barColor()"></div>
</div>
<div class="track"><div class="bar" [ngStyle]="{ 'width.%': progress(), 'background-color': barColor() }"></div></div>
<button type="button" (click)="next()">Next status</button>
<button type="button" (click)="more()">+20%</button>
`,
styles: `
.badge { padding: .1rem .6rem; border-radius: 1rem; color: white; background: gray; }
.ok { background: seagreen; }
.warning { background: darkorange; }
.error { background: crimson; }
.track { width: 100%; height: 12px; background: #eee; border-radius: 6px; margin: .5rem 0; overflow: hidden; }
.bar { height: 100%; transition: width .3s; }
`,
})
export class StatusDemo {
protected readonly status = signal<Status>('ok');
protected readonly progress = signal(20);
protected readonly barColor = computed(() =>
this.progress() < 40 ? 'crimson' : this.progress() < 80 ? 'darkorange' : 'seagreen',
);
protected next(): void {
const order: Status[] = ['ok', 'warning', 'error'];
this.status.set(order[(order.indexOf(this.status()) + 1) % order.length]);
}
protected more(): void {
this.progress.update((p) => (p >= 100 ? 0 : Math.min(100, p + 20)));
}
}
// src/app/app.ts
import { Component } from '@angular/core';
import { StatusDemo } from './styling/status-demo';
@Component({
imports: [StatusDemo],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main>
<app-status-demo />
</main>
Which one should I use?
- One class on or off: Use:
[class.name]="condition" - One style value: Use:
[style.width.px]="value" - Several classes or styles: Use:
[class]="{...}"and[style]="{...}" - Reading older code: Use:
ngClassandngStyle
Whenever you can, put the look in CSS classes and only toggle class names from the component. Reserve style bindings for values that really are dynamic, such as a width computed from data.
Try it yourself
- Add a "selected" row highlight using
[class.selected]. - Make a font-size slider that changes
[style.font-size.px]of a paragraph. - Replace every
ngClassabove with[class.x]bindings and compare the code.
Quick check
- How do you add the class
activeonly when a signal is true? - What does the
.pxin[style.width.px]mean? - Why prefer CSS classes over many inline styles?
Next, continue with Computed Signals and Effects in Angular.