View Encapsulation
View Encapsulation is Angular's mechanism for scoping a component's CSS styles so they do not leak out and affect other components — and so external styles do not bleed in unexpectedly.
Without encapsulation, a CSS rule like p { color: red } in one component would affect every <p> tag in the entire application. Angular prevents this problem with three encapsulation modes.
The Three Encapsulation Modes
Mode | Value | How It Works | When to Use |
|---|---|---|---|
Emulated (default) | ViewEncapsulation.Emulated | Angular adds unique attribute selectors to scope styles | Almost always — the safe default |
None | ViewEncapsulation.None | Styles are global — no scoping at all | Global styles, style overrides |
ShadowDom | ViewEncapsulation.ShadowDom | Native Shadow DOM — true style isolation | Custom elements, web component libraries |
Emulated Encapsulation (Default)
ViewEncapsulation.Emulated is the default. Angular simulates Shadow DOM by automatically adding a unique attribute (like _ngcontent-abc-c123) to every element in the component's template and rewriting the CSS selectors to match only those attributes.
// No encapsulation setting needed — Emulated is the default
@Component({
selector: 'app-button',
standalone: true,
template: `<button class="btn">Click me</button>`,
styles: [`
.btn {
background: #4caf50;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
}
`],
// encapsulation: ViewEncapsulation.Emulated ← this is the default
})
export class ButtonComponent {}What Angular actually renders in the DOM:
<!-- Angular transforms your template to: -->
<app-button _nghost-abc-c123>
<button class="btn" _ngcontent-abc-c123>Click me</button>
</app-button>
<!-- And rewrites your CSS to: -->
<style>
.btn[_ngcontent-abc-c123] {
background: #4caf50;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
}
</style>The scoped selector .btn[_ngcontent-abc-c123] means this style only matches the <button> inside ButtonComponent — not any other button in the app.
ViewEncapsulation.None
When encapsulation is set to None, Angular does not scope the component's styles at all. The styles are injected as global CSS that affects the entire page:
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-global-styles',
standalone: true,
template: `<div class="container"><ng-content /></div>`,
styles: [`
/* These styles are GLOBAL — they affect the entire app */
* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
.container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; }
`],
encapsulation: ViewEncapsulation.None,
})
export class GlobalStylesComponent {}ViewEncapsulation.None makes your component's styles global. This can inadvertently affect other components. Only use it deliberately for theme-level or global utility styles.ViewEncapsulation.None is useful for wrapper/theme components that intentionally need to style their descendants, or for components that need to style third-party content inside them.ViewEncapsulation.ShadowDom
ShadowDom uses the browser's native Shadow DOM API for true encapsulation. The component gets a real Shadow DOM boundary — styles cannot cross it in either direction (by default):
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-isolated-widget',
standalone: true,
template: `
<div class="widget">
<h3>I am fully isolated</h3>
<p>Global styles cannot affect me.</p>
</div>
`,
styles: [`
/* True Shadow DOM — these NEVER leak out */
.widget {
all: initial; /* reset all inherited styles */
font-family: 'Roboto', sans-serif;
padding: 1rem;
border: 2px solid #2196f3;
border-radius: 8px;
}
h3 { color: #2196f3; margin: 0 0 0.5rem; }
p { color: #333; margin: 0; }
`],
encapsulation: ViewEncapsulation.ShadowDom,
})
export class IsolatedWidgetComponent {}Shadow DOM renders as:
<!-- In the DOM inspector, you see: -->
<app-isolated-widget>
#shadow-root (open)
<style>.widget { ... }</style>
<div class="widget">
<h3>I am fully isolated</h3>
<p>Global styles cannot affect me.</p>
</div>
</app-isolated-widget>Styling Child Components from a Parent
A common question: "How do I style a child component's elements from the parent?"
With Emulated encapsulation (the default), parent styles cannot reach into a child component's view. Angular provides a few solutions:
// Option 1: Use the :host pseudo-class to style the host element
@Component({
selector: 'app-card',
standalone: true,
template: `<div class="card"><ng-content /></div>`,
styles: [`
:host {
display: block; /* make the host element a block */
margin-bottom: 1rem;
}
:host(.featured) .card { /* style when parent adds class="featured" */
border: 2px solid gold;
}
:host-context(.dark-theme) .card { /* style based on ancestor class */
background: #333;
color: white;
}
`],
})
export class CardComponent {}// Option 2: Use ::ng-deep (deprecated but still works)
// This pierces encapsulation and styles elements inside child components
@Component({
selector: 'app-container',
standalone: true,
template: `<app-third-party-component />`,
styles: [`
/* Scope ::ng-deep with :host to avoid truly global styles */
:host ::ng-deep .third-party-class {
color: red !important;
}
`],
})
export class ContainerComponent {}::ng-deep is deprecated. While it still works, avoid it for new code. Prefer passing styles via @Input() CSS custom properties (CSS variables), adding classes to the host via [class] binding, or using ViewEncapsulation.None on a parent wrapper component.CSS Custom Properties — The Modern Approach
CSS custom properties (variables) cross the Shadow DOM boundary naturally. This makes them the best pattern for customizable component theming:
// button.component.ts — uses CSS variables for theming
@Component({
selector: 'app-button',
standalone: true,
template: `<button class="btn"><ng-content /></button>`,
styles: [`
.btn {
background: var(--btn-bg, #4caf50); /* fallback if not set */
color: var(--btn-color, white);
padding: var(--btn-padding, 0.5rem 1rem);
border-radius: var(--btn-radius, 4px);
border: none;
cursor: pointer;
}
`],
})
export class ButtonComponent {}<!-- Parent can customize via CSS variables — works even with ShadowDom! --> <app-button style="--btn-bg: #e91e63; --btn-radius: 999px;"> Rounded Pink Button </app-button> <!-- Or via a CSS class --> <app-button class="danger-btn">Delete</app-button>
/* styles.css (global) */
.danger-btn {
--btn-bg: #f44336;
--btn-color: white;
}The :host and :host-context Selectors
Angular provides special selectors for styling the component's host element from within:
/* :host — selects the component's host element (<app-card>) */
:host {
display: block;
border: 1px solid #eee;
border-radius: 8px;
overflow: hidden;
}
/* :host(selector) — styles the host only when it has the selector */
:host(.elevated) {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* :host-context(selector) — styles the host based on an ancestor */
:host-context(.dark-mode) {
background: #1e1e1e;
color: #fff;
border-color: #444;
}Global Styles vs Component Styles
Style Type | Location | Scope | Use For |
|---|---|---|---|
Global styles | src/styles.css | Entire app | CSS resets, fonts, themes, utility classes |
Component styles | @Component styles/styleUrl | This component only (Emulated/ShadowDom) | Component-specific UI |
ViewEncapsulation.None | @Component with None | Entire app (leaked global) | Intentional global overrides |
CSS custom properties | Anywhere | Inherited through DOM tree | Theming customizable components |
// Configure multiple style files per component
@Component({
selector: 'app-data-table',
standalone: true,
styleUrls: [
'./data-table.component.css', // main component styles
'./data-table.component.theme.css', // theme variations
],
// OR (Angular 17+ single file shorthand)
styleUrl: './data-table.component.css',
})Practical Example — Building a Themed Button
import { Component, Input } from '@angular/core';
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
type ButtonSize = 'sm' | 'md' | 'lg';
@Component({
selector: 'app-btn',
standalone: true,
template: `
<button
[class]="classes"
[disabled]="disabled"
>
<ng-content />
</button>
`,
styles: [`
:host { display: inline-block; }
button {
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: opacity 0.2s;
}
button:hover { opacity: 0.9; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-sm { padding: 0.25rem 0.75rem; font-size: 0.875rem; }
.btn-md { padding: 0.5rem 1rem; font-size: 1rem; }
.btn-lg { padding: 0.75rem 1.5rem; font-size: 1.125rem; }
.btn-primary { background: #2196f3; color: white; }
.btn-secondary { background: #9e9e9e; color: white; }
.btn-danger { background: #f44336; color: white; }
.btn-ghost { background: transparent; color: #2196f3; border: 1px solid #2196f3; }
`],
})
export class BtnComponent {
@Input() variant: ButtonVariant = 'primary';
@Input() size: ButtonSize = 'md';
@Input() disabled = false;
get classes(): string {
return `btn-${this.size} btn-${this.variant}`;
}
}<!-- Using the themed button -->
<app-btn variant="primary" size="lg">Save Changes</app-btn>
<app-btn variant="danger">Delete Account</app-btn>
<app-btn variant="ghost" size="sm">Cancel</app-btn>
<app-btn [disabled]="isLoading">
{{ isLoading ? 'Saving...' : 'Save' }}
</app-btn>Summary
Emulated (default): Angular adds attribute selectors to scope styles — no native browser support needed
None: styles are global — useful for intentional cross-component styling
ShadowDom: uses native Shadow DOM for true isolation — best for web components / design systems
Use :host to style the component host element from within the component styles
Use CSS custom properties (variables) as a clean API for parent-controlled theming
Avoid ::ng-deep — prefer CSS variables or explicit @Input for style customization
Put global resets, fonts, and theme tokens in src/styles.css