Angular Styling and ViewEncapsulation: Emulated, ShadowDom, None, :host, ::ng-deep [2026]
Angular's CSS story is opinionated: by default, every component's styles are scoped to its own template. That isolation is achieved through ViewEncapsulation.Emulated — a clever compile-time trick that adds attribute selectors to your CSS without you noticing. Knowing how it works lets you debug "why is my style not applying?" the moment you hit it. This lesson walks the three encapsulation modes, the :host and :host-context() selectors, the (deprecated but still-seen) ::ng-deep combinator, and the CSS-variable patterns that have replaced most of them.
This is lesson 2.11 of the Angular Tutorial, part of Module 2. After this lesson you will know why your CSS applies (or doesn't), and the modern approach to styling that does not depend on penetrating component boundaries.
The three encapsulation modes #
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-card',
encapsulation: ViewEncapsulation.Emulated, // default — almost never set explicitly
// ...
})
| Mode | What it does | Behaves like |
|---|---|---|
Emulated (default) |
Angular adds a unique attribute (_ngcontent-c5) to every element your template renders, and rewrites your CSS selectors to require that attribute. Styles only apply inside this component. |
Scoped CSS without Shadow DOM |
ShadowDom |
Wraps the component in a real Shadow DOM root. Styles AND DOM are completely isolated from the rest of the page. | Web Components |
None |
No isolation. Your CSS rules are global. | A plain <link> in <head> |
99% of components use Emulated. ShadowDom is the right call for components meant to ship as actual web-component custom elements (lesson 2.14). None is mostly an escape hatch for legacy migrations.
What Emulated actually does #
Given this component:
@Component({
selector: 'app-greeting',
template: '<h1>Hello</h1>',
styles: 'h1 { color: tomato; }',
})
export class GreetingComponent {}
Angular emits HTML like this:
<h1 _ngcontent-c5>Hello</h1>
And CSS like this (in a <style> block in <head>):
h1[_ngcontent-c5] { color: tomato; }
The attribute selector [_ngcontent-c5] matches only elements rendered by THIS component. An <h1> rendered by some other component has a different attribute (_ngcontent-c8) and does not match.
This is what makes h1 { color: tomato } in one component not affect <h1> elements elsewhere. Open browser DevTools on any Angular app — you will see the _ngcontent-* attributes everywhere.
:host — selecting the component's own element #
A component's host element (<app-greeting> in the above example) is NOT part of the template — it is OUTSIDE the template. To style it, use :host:
:host {
display: block;
border: 1px solid #ddd;
padding: 16px;
}
:host:hover {
border-color: tomato;
}
:host.featured {
background: #fef3c7;
}
The last selector matches when the host element has the featured class — applied by the parent: <app-greeting class="featured">. This is one of the cleanest ways to vary a component's styling from outside.
:host-context() — selecting based on ancestors #
A narrower selector that applies styles based on something further up the DOM:
:host-context(.dark-theme) h1 {
color: white;
}
:host-context([dir="rtl"]) {
text-align: right;
}
The first rule applies when any ancestor of the component has .dark-theme. The second when an ancestor has dir="rtl". Useful for theme handling without prop-drilling theme tokens through every component.
::ng-deep — the deprecated escape hatch #
Older Angular code uses ::ng-deep to penetrate child component boundaries:
/* In parent component CSS, reaches into <app-child>'s template */
::ng-deep app-child h1 { color: red; }
Three facts:
- It is deprecated. The Angular team has been removing this for years.
- It still works in 2026. Browser support is via the
>>>and/deep/selector combinator (browsers stopped implementing it years ago, but Angular's compiler still understands it). - Do not use it in new code. The modern replacements are CSS custom properties (variables) for theming, and
:host-context()for ancestor-aware styling.
If you inherit code with ::ng-deep, leave it alone unless you are refactoring the affected components anyway. New code should use CSS variables.
CSS custom properties — the modern theming story #
A host can expose CSS variables that child components consume:
/* Parent component's CSS */
:host {
--card-bg: white;
--card-border: #e5e7eb;
}
:host(.dark) {
--card-bg: #1f2937;
--card-border: #374151;
}
/* Child component's CSS */
:host {
background: var(--card-bg, white);
border: 1px solid var(--card-border, #e5e7eb);
}
CSS variables cascade through encapsulation boundaries naturally — that's not Angular's behavior, it is the CSS spec. So a parent component can set theme tokens, and every child reads them without ::ng-deep, without :host-context(), without any Angular gymnastics. This is the cleanest theming pattern for 2026 Angular apps.
Styling sources — the priority list #
A component's styles can come from many places. The cascade order:
- Global styles in
src/styles.css(orstyles.scss) — applies everywhere, highest scope <style>tags inindex.html— global, equivalent tostyles.css- Component-scoped styles via the
stylesorstyleUrlfield — applies only inside this component (viaEmulatedmode) - Inline
style="..."attribute on individual elements — wins over everything (highest specificity) [style.X]="..."property bindings — same as inline, generated by Angular
Know which level you are operating at. A 90% of issues with "my CSS isn't applying" turn out to be at the wrong level — you wrote a global rule, but Angular's encapsulation gave the element an attribute selector that beats it.
Where styling tokens live #
A realistic file layout for a small Angular app:
| File | Purpose |
|---|---|
src/styles.css |
Global tokens: CSS variables for color/spacing/typography, reset / normalize, base typography |
src/app/app.css |
Layout styles for the root component (page padding, max-width container) |
src/app/<feature>/<comp>.css |
Per-component styles |
:host selectors in component CSS |
Component's own outer dimensions, hover/focus states |
For design-system theming, define every meaningful token in styles.css as a CSS variable, then every component reads var(--color-primary, fallback). No ::ng-deep, no :host-context(), no Sass mixins. Plain CSS variables get you 90% of the way.
ShadowDom mode — when it makes sense #
ViewEncapsulation.ShadowDom is the right call for one specific case: shipping a component as a custom element (Angular Elements, lesson 2.14) for use outside an Angular host page. The Shadow DOM gives you real isolation — the consuming page cannot accidentally style your widget, and your widget cannot leak styles into the page.
For in-app components, Shadow DOM is more isolation than you want:
- CSS variables still cascade in, but anything else (like a global typography stylesheet) does not
- You cannot easily reach into the component to style it from the parent
- Some accessibility tools have issues with Shadow DOM roots
Use Emulated for everything except true custom-element distribution.
None mode — when to use it #
ViewEncapsulation.None makes the component's CSS global. There is one legitimate use: wrapper components for a global stylesheet split into TS files. If you want to organize a global CSS file as several scoped TS files for ergonomic reasons, None lets you do it.
In practice, just put the rules in styles.css and skip the contortion. Use None only for legacy migration, never for new code.
A complete styling example #
A component using every pattern from this lesson:
@Component({
selector: 'app-card',
template: `
<header>
<ng-content select="[card-title]" />
</header>
<section>
<ng-content />
</section>
`,
styles: `
:host {
display: block;
background: var(--card-bg, white);
border: 1px solid var(--card-border, #e5e7eb);
border-radius: var(--card-radius, 8px);
padding: var(--card-padding, 16px);
transition: border-color 200ms;
}
:host:hover { border-color: var(--brand, #14b8a6); }
:host(.featured) { background: var(--card-featured-bg, #fef3c7); }
:host-context(.dark) {
--card-bg: #1f2937;
--card-border: #374151;
}
header { font-weight: 700; margin-bottom: 8px; }
section { color: var(--text-secondary, #4b5563); }
`,
})
export class CardComponent {}
Five techniques in one block:
:hostfor outer dimensions:host:hoverfor state-based outer styling:host(.featured)for class-driven variations:host-context(.dark)for theme-aware overrides- CSS variables with fallbacks throughout
- Native scoped selectors (
header,section) for inner template elements
No ::ng-deep needed.
Common gotchas #
| Symptom | Cause | Fix |
|---|---|---|
| Global style not affecting a component | Component's Emulated mode added an attribute selector; global rule doesn't match |
Either tighten the global rule (html h1 { ... }), use a CSS variable, or fix the component-level styles |
:host in a global stylesheet does nothing |
:host only works inside a component's own CSS |
Move the rule into the component, or use a regular selector |
Style applied to <app-X> but not inside it |
<app-X>'s host element is in the parent's scope; the inside is in <app-X>'s scope |
Use :host in <app-X>'s CSS, not the parent's |
| Child component styles bleeding to siblings | ViewEncapsulation.None is on, or you used ::ng-deep |
Restore Emulated; replace ::ng-deep with CSS variables |
| Shadow DOM swallowed your global font | ShadowDom mode isolates everything except CSS variables | Set the font via a CSS variable on :root and use var(--font) in the component |
What's next #
Lesson 2.12 covers host element metadata — the host: { '(click)': '...', '[class.X]': '...' } syntax for binding the host element from inside the component class. Lesson 2.13 covers signal queries (viewChild, contentChild). Lesson 2.14 walks programmatic component creation. After that, Module 2 is done and Module 3 takes over with the signal system in depth.
Try it yourself #
A dark-mode toggle that uses CSS variables and :host-context():
// global styles.css
:root { --bg: white; --text: #111; }
:root.dark { --bg: #111; --text: #eee; }
@Component({
selector: 'app-card',
template: `<p>{{ msg() }}</p>`,
styles: `
:host {
display: block;
background: var(--bg);
color: var(--text);
padding: 16px;
border-radius: 8px;
transition: background 200ms, color 200ms;
}
`,
})
export class CardComponent {
msg = input('Hello');
}
In your root template:
@Component({
template: `
<button (click)="toggle()">Toggle theme</button>
<app-card msg="This is themed" />
`,
})
export class AppComponent {
toggle() { document.documentElement.classList.toggle('dark'); }
}
Click toggle. The card recolors, no ::ng-deep, no theme service, no signal — just CSS variables doing their job through encapsulation.
get_best_practicesUse CSS variables. Declare them on the parent (often :root or :host), and consume them in the child via var(--name, fallback). CSS variables cascade through Angular’s encapsulation boundaries naturally — no ::ng-deep, no :host-context(). For one-off overrides where a variable doesn’t fit, you can add a class to the child’s host (<app-child class="compact">) and style it inside the child via :host(.compact).Lesson 2.12 picks up host metadata — controlling the component's own outer element from inside the class.
Up next in Angular
More from this topic
Enjoyed this article?
Get new Angular tutorials delivered. No spam — just code-first articles when they ship.


