Angular Styling

Angular scopes component CSS by default, while still giving you clear escape hatches — ViewEncapsulation options and dynamic [ngClass]/[ngStyle] bindings — for cases that need broader or more dynamic control.

Component-Scoped Styles

Styles declared in a component's 'styles' array (or a linked stylesheet) are automatically scoped to that component's template. Angular rewrites selectors under the hood so a rule like '.card' in one component never accidentally styles a '.card' element in another.

card.component.ts

import { Component, ViewEncapsulation } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

@Component({
  selector: 'app-card',
  standalone: true,
  encapsulation: ViewEncapsulation.Emulated,
  template: `
    <div class="card">
      <h3>{{ title }}</h3>
      <p><ng-content></ng-content></p>
    </div>
  `,
  styles: [`
    .card {
      border: 1px solid #ddd;
      border-radius: 8px;
      padding: 1rem;
    }
    h3 {
      margin: 0 0 0.5rem;
      color: #2c3e50;
    }
  `]
})
export class CardComponent {
  title = 'Default Title';
}

bootstrapApplication(CardComponent).catch((err) => console.error(err));

How View Encapsulation Works

ViewEncapsulation controls the strategy Angular uses to scope styles. Emulated is the default and works in every browser without any real Shadow DOM. ShadowDom uses the browser's native Shadow DOM for true style and DOM isolation. None disables scoping entirely, so the styles become global.

ModeBehavior
ViewEncapsulation.EmulatedDefault. Angular adds unique attributes to elements and rewrites selectors to fake style scoping.
ViewEncapsulation.ShadowDomUses the browser's native Shadow DOM; styles and markup are fully isolated from the rest of the page.
ViewEncapsulation.NoneNo scoping at all — styles are inserted globally and can affect any matching element on the page.

Dynamic Classes with [ngClass]

The NgClass directive adds or removes CSS classes based on an expression. Passing an object applies each key whose value is truthy; this is the cleanest way to toggle several conditional classes at once from component state.

status-badge.component.ts

import { Component, Input } from '@angular/core';
import { NgClass } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';

@Component({
  selector: 'app-status-badge',
  standalone: true,
  imports: [NgClass],
  template: `
    <span
      class="badge"
      [ngClass]="{
        'badge--success': status === 'active',
        'badge--warning': status === 'pending',
        'badge--danger': status === 'failed'
      }"
    >
      {{ status }}
    </span>
  `,
  styles: [`
    .badge { padding: 2px 8px; border-radius: 4px; font-size: 0.85rem; }
    .badge--success { background: #d4edda; color: #155724; }
    .badge--warning { background: #fff3cd; color: #856404; }
    .badge--danger { background: #f8d7da; color: #721c24; }
  `]
})
export class StatusBadgeComponent {
  @Input() status: 'active' | 'pending' | 'failed' = 'active';
}

bootstrapApplication(StatusBadgeComponent).catch((err) => console.error(err));

Dynamic Inline Styles with [ngStyle]

NgStyle sets multiple inline CSS properties from an object expression, which is useful when a value — like a computed width or color — can't be expressed with a fixed CSS class. For a single style or class, the simpler [style.prop] and [class.name] bindings are usually a better fit.

progress-bar.component.ts

import { Component, Input } from '@angular/core';
import { NgStyle } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';

@Component({
  selector: 'app-progress-bar',
  standalone: true,
  imports: [NgStyle],
  template: `
    <div class="track">
      <div
        class="fill"
        [class.fill--complete]="percent === 100"
        [ngStyle]="{ width: percent + '%', backgroundColor: color }"
      ></div>
    </div>
  `,
  styles: [`
    .track { background: #eee; height: 8px; border-radius: 4px; overflow: hidden; }
    .fill { height: 100%; transition: width 0.3s ease; }
    .fill--complete { opacity: 0.7; }
  `]
})
export class ProgressBarComponent {
  @Input() percent = 0;
  @Input() color = '#4caf50';
}

bootstrapApplication(ProgressBarComponent).catch((err) => console.error(err));
  • Prefer [class.name] and [style.prop] bindings for a single conditional class or style.
  • Reach for [ngClass]/[ngStyle] when you need several conditions evaluated together from one expression.
  • Use CSS custom properties (variables) for theming instead of computing every color in TypeScript.
  • Avoid rebuilding the [ngStyle] object on every change detection cycle — precompute it when the underlying data changes.
Note: The @HostBinding decorator (or the host property in @Component) lets a component add classes or styles to its own host element, which is useful for a component that should visually adapt based on its own inputs.
Note: ViewEncapsulation.None removes all style scoping for that component, so its styles leak globally and can unintentionally override styles in completely unrelated components — use it only for deliberate, app-wide style sheets.

Exercise: Angular Styling

Where do you define CSS scoped to just one component?