Skip to content
Angular ng components 4 min read

View Encapsulation

View encapsulation decides how the styles you declare on a component are scoped to that component’s template. Without scoping, a p { color: red; } rule in one component would leak out and repaint every paragraph on the page. Angular solves this with three encapsulation strategies, and choosing the right one is the difference between predictable, maintainable styling and a cascade of accidental overrides. This page explains all three modes, how Angular emulates the Shadow DOM, and when each option is the correct call.

How styles get scoped

Every component can carry styles through styles (inline) or styleUrls (external files). By default those styles only apply to the component’s own template, never to children rendered through projection or to the rest of the document. The mechanism behind that scoping is controlled by the encapsulation property of the @Component decorator, which accepts a value from the ViewEncapsulation enum.

import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-card',
  template: `<div class="card"><ng-content /></div>`,
  styles: [`.card { padding: 1rem; border: 1px solid #ddd; }`],
  encapsulation: ViewEncapsulation.Emulated, // the default
})
export class CardComponent {}

ViewEncapsulation.Emulated

Emulated is the default and the mode you will use the vast majority of the time. Angular does not use the browser’s native Shadow DOM here; instead it emulates the isolation. At runtime Angular adds a unique attribute to every element in the component (something like _ngcontent-abc-1) and rewrites your CSS selectors to include a matching attribute selector. The result behaves like scoped styles while still living in the global stylesheet.

So a rule written as .card { padding: 1rem; } is transformed into roughly:

.card[_ngcontent-abc-1] { padding: 1rem; }

Because the attribute is unique per component, the rule only ever matches elements rendered by this component. Global styles from styles.css still cascade in, which is usually what you want for design tokens and resets.

ViewEncapsulation.ShadowDom

ShadowDom uses the real browser feature. Angular attaches a shadow root to the host element and renders the template and styles inside it. This gives true isolation in both directions: your component styles cannot leak out, and crucially, global page styles cannot leak in.

import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-widget',
  template: `<button class="cta">Buy now</button>`,
  styles: [`.cta { background: rebeccapurple; color: white; }`],
  encapsulation: ViewEncapsulation.ShadowDom,
})
export class WidgetComponent {}

This is ideal for embeddable widgets or custom elements that must survive on a host page with unknown CSS. The trade-off is that your global theme, fonts, and CSS variables defined outside the shadow boundary will not reach inside unless you forward them. CSS custom properties (variables) do pierce the boundary, which makes them the recommended channel for theming Shadow DOM components.

Switching an existing app to ShadowDom is rarely a drop-in change. Anything that relied on global stylesheets reaching the component, or on document.querySelector finding elements, will break because the content now lives behind a shadow root.

ViewEncapsulation.None

None disables scoping entirely. Every selector you write becomes a global rule, exactly as if you had pasted it into styles.css. It is occasionally useful for a top-level theming component or for deliberately styling content you cannot otherwise reach, but it is dangerous: a generic selector here will silently restyle the whole application.

import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-theme',
  template: `<ng-content />`,
  styles: [`body { font-family: 'Inter', sans-serif; }`],
  encapsulation: ViewEncapsulation.None,
})
export class ThemeComponent {}

Comparing the three modes

ModeMechanismStyles leak out?Global styles leak in?Typical use
Emulated (default)Attribute selectors added at runtimeNoYesAlmost all components
ShadowDomNative browser shadow rootNoNo (only CSS variables)Embeddable widgets, custom elements
NonePlain global CSSYesYesTop-level theming, intentional globals

Piercing the boundary with :host and ::ng-deep

Regardless of mode, the :host pseudo-class targets the component’s own host element, and :host-context() styles the host based on an ancestor. These are first-class and encouraged.

@Component({
  selector: 'app-badge',
  template: `<span><ng-content /></span>`,
  styles: [`
    :host { display: inline-block; border-radius: 999px; }
    :host-context(.dark) { background: #111; color: #eee; }
  `],
})
export class BadgeComponent {}

The deprecated ::ng-deep combinator forces a style through Emulated scoping into child components. It still works but is on its way out, so prefer CSS custom properties or restructuring your styles instead of reaching for it.

Best Practices

  • Stick with the default Emulated mode unless you have a concrete reason to change it; it gives clean scoping with full access to global design tokens.
  • Reserve ShadowDom for genuinely embeddable or third-party-hosted components, and expose theming through CSS custom properties since they cross the shadow boundary.
  • Avoid ViewEncapsulation.None in feature components; one broad selector can repaint the entire app. Limit it to a single intentional theming layer.
  • Use :host and :host-context() for host and contextual styling instead of wrapping elements just to get a styling hook.
  • Treat ::ng-deep as legacy; refactor toward CSS variables or shared utility classes rather than spreading deep selectors.
  • Keep app-wide concerns (fonts, resets, design tokens) in the global styles.css, and keep component styles small and local.
Last updated June 14, 2026
Was this helpful?