Angular – Component Styles, Sass and Bootstrap

June 30, 20263 min readUpdated 8/21/2026

Angular scopes a component's styles to that component by default. That single fact changes how you write CSS in an Angular app, and it is worth understanding before you fight it.

View encapsulation

Styles declared on a component apply to that component's template and nowhere else:

styles: ':host { display: block; }',

Angular does this without a shadow DOM. It stamps a unique attribute onto the component's elements and rewrites the selectors to match — a rule for .card becomes something like .card[_ngcontent-abc]. The consequence: a component's styles cannot leak out, and a parent's styles cannot reach in.

Three modes exist. Emulated is the default and is what the above describes. ShadowDom uses the real browser feature, which is stricter and stops global stylesheets reaching the component at all — including Bootstrap, which is usually a surprise rather than a plan. None turns scoping off and dumps the styles into the page globally.

:host

Inside a component's styles, :host is the component's own element — the <app-bar-chart> tag itself, which is otherwise unstyleable from inside.

⚠️ This is a real bug the pizza app paid for. A custom element is display: inline by default, and an inline element has no width to measure — so a chart that sizes itself with ResizeObserver measured its content instead of the available space and silently drew at half size. One line fixes it, and every chart component in the app carries it:

styles: ':host { display: block; }',

:host(.condition) applies only when the host also matches a selector, and :host-context(.dark) applies when any ancestor matches — which is the supported way to respond to a theme class set on <body>.

The global stylesheet

Not everything should be scoped. Brand tokens, typography and third-party CSS are global by nature, and they are listed in angular.json:

"styles": [
  "node_modules/bootstrap/dist/css/bootstrap.min.css",
  "src/styles.scss"
]

The order is load-bearing. Bootstrap first, ours second, so our rules win on equal specificity — no !important anywhere in the app.

Sass variables and CSS custom properties

The theme uses both, and the distinction is the most useful thing in this lesson:

$pizza-red: #d8102a;
$pizza-black: #231f20;
$pizza-cream: #fff8f0;

Those are Sass variables. They exist at build time only — they are substituted into the output and then they are gone. That is what lets Sass compute a hover shade from the base colour with color.adjust: Sass can do arithmetic on a colour, the browser cannot do it on a var().

theme.scss then re-emits the same values as CSS custom properties on :root. That looks redundant and is not — a custom property is a real runtime value, so Bootstrap 5.3 can read it, and a component can override one for its own subtree. Neither is possible with a Sass variable, because by the time the browser sees the file there is nothing left to override.

The rule of thumb the app follows: Sass variable if only the build needs it, custom property if the browser does.

Partials

@use 'tokens' as *;

The leading underscore on _tokens.scss marks it a partial: Sass compiles it into whatever imports it rather than into a stylesheet of its own. as * drops its members into the importing file's namespace, so they can be written $pizza-red instead of tokens.$pizza-red. Note @use, not @import — the latter is deprecated in Sass and behaves differently, importing repeatedly rather than once.

Styling from the template

Most conditional styling never reaches a stylesheet at all. Class and style bindings do it inline:

<div
  class="modal-dialog modal-dialog-centered"
  [class.modal-lg]="size() === 'lg'"
  [class.modal-dialog-scrollable]="scrollable()"
>

In an app built on a utility-class framework like Bootstrap, this is where the styling lives. Almost no component in the pizza app has a styles block — the two chart components do, for the one :host rule, and that is all.

Reaching outside, when you must

::ng-deep pierces encapsulation to style a child component's internals. It is deprecated, it is still everywhere, and it is occasionally the only way to style a third-party widget. Always scope it — :host ::ng-deep .thing — or it becomes a global rule with a misleading name. Prefer a CSS custom property the child reads: that is a supported interface rather than a reach into someone's implementation.

What is next

The reactive core of modern Angular, and the thing this whole app is built on: signals.