Angular ngClass, ngStyle, Class Binding and Style Binding Explained

Angular Tutorial: Beginner to Advanced · 4. Control flow and lists · 4 min read · Updated

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
  • ngClass and ngStyle, 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: ngClass and ngStyle

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

  1. Add a "selected" row highlight using [class.selected].
  2. Make a font-size slider that changes [style.font-size.px] of a paragraph.
  3. Replace every ngClass above with [class.x] bindings and compare the code.

Quick check

  1. How do you add the class active only when a signal is true?
  2. What does the .px in [style.width.px] mean?
  3. Why prefer CSS classes over many inline styles?

Next, continue with Computed Signals and Effects in Angular.

Frequently asked questions

Does ngClass still work?

Yes, it is still in @angular/common. Class and style bindings are simpler, so new code usually prefers them.

How do I avoid style leaking between components?

Angular scopes component styles by default (view encapsulation). The styling lesson explains how it works and how to change it.