Angular gives each component its own styles. Understanding where styles live and how far they reach prevents most "why is my CSS not working?" problems.
What you will learn
- Global styles versus component styles
- View encapsulation and its three modes
:host, and why::ng-deepis a last resort- Adding Bootstrap and Font Awesome
- A few responsive design habits
Global styles
Global styles apply to the whole app. They live in src/styles.css and are listed in the styles array of angular.json:
/* src/styles.css */
:root {
--brand: #0b6cff;
--text: #1b1f24;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
color: var(--text);
}
a { color: var(--brand); }
Put in global styles: resets, fonts, CSS variables (design tokens), typography and utility classes. Use CSS variables for colours and spacing, so components can follow the theme without importing anything.
Component styles
A component can have its own styles, inline or in a file:
@Component({
selector: 'app-card',
template: `<article><h3>Title</h3></article>`,
styles: `article { border: 1px solid #ddd; padding: 1rem; }`, // inline
// or: styleUrl: './card.css',
})
By default these styles apply only to this component's template. A rule like h3 { color: red; } in app-card does not turn every h3 in the app red.
View encapsulation
How does Angular keep styles private? Through view encapsulation. There are three modes:
- `Emulated` (default): How it works: Angular adds unique attributes (
_ngcontent-xyz) to elements and rewrites your selectors; Use when: Almost always - `None`: How it works: Styles become global, no isolation; Use when: A shared global stylesheet inside a component, or third-party markup
- `ShadowDom`: How it works: Uses the browser's real Shadow DOM; Use when: Strict isolation, web components
// src/app/styling2/encapsulation-demo.ts
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-emulated',
template: `<p class="note">Emulated</p>`,
styles: `.note { color: seagreen; }`,
})
export class EmulatedDemo {}
@Component({
selector: 'app-none',
template: `<p class="note-none">None (global leak)</p>`,
styles: `.note-none { color: crimson; }`,
encapsulation: ViewEncapsulation.None,
})
export class NoneDemo {}
@Component({
selector: 'app-shadow',
template: `<p class="note-shadow">ShadowDom</p>`,
styles: `.note-shadow { color: royalblue; }`,
encapsulation: ViewEncapsulation.ShadowDom,
})
export class ShadowDemo {}
Open the browser DevTools: the emulated paragraph has an attribute like _ngcontent-ng-c123, the None one has none (its style is global), and the ShadowDom one lives inside a #shadow-root.
:host and :host-context
:host styles the element that hosts the component (<app-card> itself) from inside the component's own stylesheet:
:host { display: block; margin: 1rem 0; }
:host(.featured) { border-color: gold; }
:host-context(.dark-theme) { background: #111; }
Components are display: inline by default. Almost every component should set :host { display: block; }, or whatever it needs.
Styling child components
A parent's styles do not reach into a child component's template. Options, from best to worst:
- Inputs and CSS variables. The child exposes
--card-accent, and the parent setsstyle="--card-accent: tomato". - Global classes for shared looks.
- `::ng-deep`, which pierces encapsulation. It is deprecated, so avoid it. If you must use it, scope it with
:host ::ng-deep .x { }so it does not become global.
// src/app/styling2/themed-card.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-themed-card',
template: `<article><ng-content /></article>`,
styles: `
:host { display: block; margin: 1rem 0; }
article { border-left: 4px solid var(--card-accent, #0b6cff); padding: .5rem 1rem; background: #f6f8fa; }
`,
})
export class ThemedCard {}
// src/app/styling2/styling-page.ts
import { Component } from '@angular/core';
import { EmulatedDemo, NoneDemo, ShadowDemo } from './encapsulation-demo';
import { ThemedCard } from './themed-card';
@Component({
selector: 'app-styling-page',
imports: [EmulatedDemo, NoneDemo, ShadowDemo, ThemedCard],
template: `
<app-emulated />
<app-none />
<app-shadow />
<app-themed-card>Default accent</app-themed-card>
<app-themed-card style="--card-accent: tomato">Custom accent from the parent</app-themed-card>
`,
})
export class StylingPage {}
// src/app/app.ts
import { Component } from '@angular/core';
import { StylingPage } from './styling2/styling-page';
@Component({
imports: [StylingPage],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main>
<h1>Styling</h1>
<app-styling-page />
</main>
CSS custom properties inherit through the DOM, even through encapsulation, which is why they are the clean way to theme child components.
Class and style binding
Change styles from data with [class.x], [style.width.px], [class] and [style]. The earlier lesson on ngClass and ngStyle covers them.
Add Bootstrap
Bootstrap is a popular CSS framework with a grid and ready-made components.
npm install bootstrap
Add its CSS to the styles array in angular.json, before your own styles:
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css"
]
We built a project with this setup, and Bootstrap's classes (container, row, col-md-6, card, btn btn-primary) were present in the generated stylesheet. Use the grid in templates:
<div class="container">
<div class="row g-3">
<div class="col-12 col-md-6">Left</div>
<div class="col-12 col-md-6">Right</div>
</div>
</div>
Bootstrap's JavaScript components (modals, dropdowns) manipulate the DOM themselves, so for Angular prefer a wrapper library such as ng-bootstrap, which provides them as Angular components. Check that the version you choose supports your Angular version before installing. If you only need the look, CSS-only Bootstrap is enough. Consider bundle size: the CSS file is about 40 kB compressed, and you can import only parts of Bootstrap with Sass to reduce it.
Add Font Awesome
The simplest way is the free CSS package:
npm install @fortawesome/fontawesome-free
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"node_modules/@fortawesome/fontawesome-free/css/all.min.css",
"src/styles.css"
]
Now icons are classes. The CLI copied the font files into the build output (the media folder) in our test.
<button type="button" class="btn btn-primary">
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i> Save
</button>
Accessibility: mark decorative icons with aria-hidden="true". An icon-only button needs an accessible name: <button aria-label="Delete"><i class="fa-solid fa-trash" aria-hidden="true"></i></button>. There is also an official Angular wrapper, @fortawesome/angular-fontawesome, that renders icons as SVG components. Check its compatibility with your Angular version first. Many projects use the free CSS or inline SVG icons.
Responsive design in practice
- Mobile first: write the small-screen layout, then add
@media (min-width: 768px)rules. - Use flexible units:
rem,%,fr,clamp(), and CSS Grid and Flexbox. - Always keep
<meta name="viewport" content="width=device-width, initial-scale=1">inindex.html. New Angular projects include it. - Make touch targets at least about 44 by 44 pixels.
- Test in the browser's device toolbar and on a real phone.
- For layout logic in code, the CDK's
BreakpointObservertells components when the screen size changes.
.cards { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
This one rule makes a responsive card grid without any media query.
Test the encapsulation modes
// src/app/styling2/encapsulation.spec.ts
import { TestBed } from '@angular/core/testing';
import { EmulatedDemo, NoneDemo, ShadowDemo } from './encapsulation-demo';
describe('view encapsulation', () => {
it('emulated adds scoping attributes to elements', async () => {
const fixture = TestBed.createComponent(EmulatedDemo);
await fixture.whenStable();
const p = (fixture.nativeElement as HTMLElement).querySelector('p')!;
const hasScope = p.getAttributeNames().some((n) => n.startsWith('_ngcontent'));
expect(hasScope).toBe(true);
});
it('none adds no scoping attributes', async () => {
const fixture = TestBed.createComponent(NoneDemo);
await fixture.whenStable();
const p = (fixture.nativeElement as HTMLElement).querySelector('p')!;
expect(p.getAttributeNames().some((n) => n.startsWith('_ngcontent'))).toBe(false);
});
it('shadow dom renders inside a shadow root', async () => {
const fixture = TestBed.createComponent(ShadowDemo);
await fixture.whenStable();
const host = fixture.nativeElement as HTMLElement;
expect(host.shadowRoot).not.toBeNull();
expect(host.shadowRoot!.querySelector('p')?.textContent).toContain('ShadowDom');
});
});
Try it yourself
- Add a CSS variable
--radiusinstyles.cssand use it in two components. - Switch
EmulatedDemotoViewEncapsulation.Noneand watch the style leak to other elements with the same class. - Make the card grid responsive and test it at 360, 768 and 1200 pixels wide.
Quick check
- Why does a component style not affect other components by default?
- How do you style the component's own host element?
- What is the clean way to let a parent theme a child component?
Next, continue with Conditional Rendering in Angular with @if, @else and @switch.