In Angular, as in other frameworks and libraries, we aim to keep our components as lightweight as possible by breaking them down. This decomposition revolves around smaller, preferably generic, components intended for reuse, which helps us save development time.

Unfortunately, it's common for a component to require slightly different styling depending on the page where it appears.

How can we customize the style of such a component based on the pages that use it?

In Angular, several options are available to address this issue:

  • properties (@Input)
  • directives
  • Web Component CSS selectors
  • CSS variables

This article focuses on using Web Component CSS selectors and CSS variables within an Angular context.

The different encapsulations of an Angular component

Before diving straight into CSS selectors, it's essential to understand a key concept of an Angular component: encapsulation.

Angular offers three types of encapsulation for a component:

  • encapsulation.None
  • encapsulation.Emulated
  • encapsulation.ShadowDom

Encapsulation.None

This encapsulation system, as its name suggests, provides no CSS encapsulation at all. That means any styles defined for the component apply to every HTML element in the application, regardless of which host component they belong to.

In other words, the component's styles are shared across the entire page where it's used, which can lead to class overrides if you're not careful with how you name your classes.

Encapsulation.Emulated

This encapsulation system mimics the native Shadow DOM encapsulation behavior by adding a specific attribute to the component's host element as well as to all of the component's CSS selectors.
It's the default behavior of Angular.

Be aware that no actual Shadow DOM is created here; it's merely CSS encapsulation, so the component's styles are, by definition, not shared.

Picture to see how is create the attribute in DOM

_ngcontent-xyr-c12 is an example of an attribute that Angular adds to encapsulate the component's own CSS.

Encapsulation.ShadowDom

This encapsulation system uses the browser's native Shadow DOM API to encapsulate the component's style. Angular creates a Shadow Root attached to the component's host element, which is then used to encapsulate all of the component's styles.

Picture to see how is shadow dom created in Angular

Understanding the CSS Selectors

Three key CSS selectors are essential when it comes to styling Angular components:

  • :host
  • :host-context
  • ::slotted

Both the :host and :host-context selectors are only applicable when your component's encapsulation mode is set to either the default mode or the Shadow DOM mode.

The ::slotted selector, on the other hand, is limited to Shadow DOM mode exclusively.

The :host Selector

When a component is instantiated, it gets bound to an element that matches that component's selector. This element, into which the component's template is rendered, is referred to as the host element. The :host pseudo-class selector enables you to apply styles directly to this host element, as opposed to styling the elements found within its template.

Example

app-button {
  width: 30%;
}

In this scenario, the goal is to set the size of our AppButtonComponent. To achieve this, we need to target the host element directly. The proper way to size the component is by using the :host selector in its associated stylesheet, which is button.component.css.

:host {
 display: block;
 width: 100%
}

Tip: By default, a component is rendered with the CSS property display: inline. To properly size the component, you'll need to override this by setting the property to block or inline-block.

Caution: Any style applied directly on the component's selector tag will take precedence over and override the styles declared within the :host rule.

To target child elements with greater specificity, you can combine :host with other selectors.

:host span {}

This code snippet targets all span elements belonging to the AppButtonComponent.

The :host-context Selector

There are times when it's advantageous to style a component based on the context in which it's used.

For instance, if AppButtonComponent is placed inside a form with a class called 'disabled', the component should be able to detect this contextual environment and adapt its styling accordingly.

The :host-context pseudo-class selector provides a way to look up the component's ancestor elements to find a specific selector, like a class.

:host-context(.disable) {
  color: grey;
}

In the example above, if the component is used within an element that has the .disable class, the text color of the component will change to grey.

Naturally, these selectors can be combined for more complex scenarios.

:host-context(form.disable) {
  color: grey;
}

In this particular case, the text will turn grey only if the component is nested inside a form element that also carries the .disable class.

The ::slotted Selector

The purpose of the ::slotted selector is to target any element that has been placed inside a slot tag within an HTML template.

As noted earlier, this selector is only functional when the component's encapsulation mode is set to Shadow Dom.

When this encapsulation mode is activated, an Angular component operates exactly like a standard Web Component, relying on the browser's native APIs.

With this in mind, the AppButtonComponent can use the "template specification" and be written as:

<button>
  <slot></slot>
</button>

In this usage scenario, the slot tag behaves in the same way as the ng-content tag.
This implies that the AppButtonComponent would be used in the following manner:

<app-button>
  Click me
</app-button>

How do we ensure the content within our button maintains a consistent style?

This is achieved by using the ::slotted selector in the stylesheet of the AppButtonComponent.

::slotted(*) {
 color: red
}

In this case, all the content assigned to the slot will be rendered in red, which also means the text color of 'Click me' will be red.

Just like the other selectors, you can combine ::slotted with others to achieve a more targeted selection.

::slotted(span.red) {
color: red
}

With this rule, only a span element with the class red that is placed within the slot will be displayed in red.

CSS Custom Properties

CSS variables, or custom properties, are values defined by developers or users of a webpage that can be reused throughout the document. They are declared with the syntax --my_variable: value and accessed with the keyword var(--my_variable, defaultValue).

These custom properties are especially handy when a property needs to be adjusted depending on a particular context.

To pass a CSS variable into a custom component, such as AppButtonComponent, simply define it on the component's selector:

app-button {
 --text-color: green;
}
Enter fullscreen mode Exit fullscreen mode

Once that's in place, in the component's stylesheet — the CSS file for AppButtonComponent — grab the value using var:

:host {
  color: var(--text-color, blue);
}
Enter fullscreen mode Exit fullscreen mode

In the example above, the text color is set to the value of the text-color variable when it's supplied; otherwise, the default value — blue in this case — applies.

Wrapping Up

Pseudo-class selectors are a powerful tool — they let you restyle a custom component quickly and with minimal fuss.

However, when a component needs more flexible styling, or its logic is expected to grow, turning to a directive-based approach is the smarter move.