Getting Started
Building reusable components requires a thoughtful approach to flexibility, since those components may appear across different parts of an application or in entirely separate projects, each with their own layout and color requirements.
Take, for instance, a contact list component. One deployment might render contacts as cards featuring a photo, name, and supporting details. Another context could call for a list style where images have thick borders, a gray and white palette dominates, and photos are hidden entirely by default.
The goal is to support new layouts and color schemes down the road without rewriting component internals — applying a different look should be as straightforward as adding a class or swapping a variable.
Kickoff
This approach leans on three core tools: the :host() pseudo-class, Angular's component styling system, and CSS custom properties.
Building the Layout
We start by scaffolding an app-contact-component with the markup necessary to display contact information.
ng g c contact
Following the BEM naming convention, each element gets its own dedicated class, which keeps selector specificity flat and manageable.
<div class="contact">
<h1 class="contact__firstname">{{contact.first_name}}</h1>
<p class="contact__lastname">{{contact.last_name}}</p>
<img class="contact__avatar" [src]="contact.avatar"/>
</div>
With the classes in place, the contact component's Sass file is updated to define the default visual appearance for each block and element.
.contact {
background: grey;
font-family: monospace;
border: 1px solid black;
border-radius: 5px;
margin: 10px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
&__firstname {
font-size: 1.5em;
color: whitesmoke;
}
&__lastname {
font-size: 1.5em;
color: whitesmoke;
}
&__avatar {
display: none;
border: 1px solid black;
background-color: lightblue;
}
}
At this stage, the default layout is fully operational.
While the layout works, it exposes a couple of design flaws: the color values are hardcoded directly into the styles, and layout rules are mixed together with color rules within the same file.
Separating Themes and Colors
To clean things up, we split these concerns into distinct files. A dedicated theme directory houses the following:
- winter.scss
- winter-colors.scss
- summer.scss
- summer-colors.scss
Leveraging :host and CSS Custom Properties
The :host pseudo-class is a powerful tool for scoping styles to a specific component instance — it applies rules only when the component matches a given CSS class selector.
Explore
:host()further in the Angular documentation: https://angular.io/guide/component-styles#host
CSS Custom Properties, or variables, give us a way to store values for reuse throughout stylesheets, much like Sass variables but with the added benefit of being resolvable at runtime.
Dive deeper into CSS Custom Properties on MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties
Using these custom properties, the winter color palette is defined in winter-colors.scss. Each color is assigned to a semantic variable name.
:host(.contact-winter-colors) {
--background-color: #424b68;
--primary-color: rgb(220, 59, 226);
--secondary-color: rgb(80, 245, 65);
--avatar-background: rgb(48, 109, 78);
}
The winter layout file, winter.scss, then consumes these custom properties for color assignments and introduces any layout adjustments needed for the themed presentation.
:host(.contact-winter) {
.contact {
background: var(--background-color);
font-family: monospace;
border: 1px solid black;
border-radius: 5px;
width: -moz-fit-content;
min-width: 150px;
flex-direction: column;
padding: 10px;
text-align: center;
display: table-cell;
&__firstname {
font-size: 1.5em;
color: var(--primary-color);
}
&__lastname {
font-size: 1.5em;
color: var(--secondary-color);
}
&__avatar {
display: block;
border: 1px solid black;
border-radius: 50%;
background-color: var(--avatar-background);
}
}
}
The same pattern is applied to create the summer.scss and summer-colors.scss files.
Wiring Up Themes and Colors
Inside contact.component.scss, both the layout and color theme files are imported.
/*layouts*/
@import './themes/summer';
@import './themes/winter';
/*colors themes*/
@import './themes/summer-colors';
@import './themes/winter-colors';
Find out more about Sass imports: https://sass-lang.com/documentation/at-rules/import
The component's own Sass now holds default rule values alongside the imports that provide thematic overrides. To swap in the summer or winter scheme, we rely on the fallback mechanism built into CSS Custom Properties.
See how fallback values operate: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties#custom_property_fallback_values
The fallback logic works like this: if a variable like --background-color has been assigned a value, that value takes precedence; otherwise, a fallback like grey is applied.
background: var(--background-color, grey);
With this setup, the default stylesheet is ready to adopt any value coming from the CSS custom properties, using hardcoded fallbacks as the starting point.
Applying Themes with ngClass and :host
To make colors and layout respond to user interaction, the Angular ngClass directive can bind a class to the component. Once applied, the :host pseudo-class selector can style the component based on that class.
<app-contact [ngClass]="theme" *ngFor="let contact of contacts" [contact]="contact">
</app-contact>
For a dynamic experience, maintain a theme variable in the component class. Update its value through the methods changeLayoutColor, addColor, and reset to toggle between different visual states.
<div class="actions">
<button (click)="changeLayoutColor()">change</button>
<button (click)="addColor('contact-winter-colors')">Winter</button>
<button (click)="addColor('contact-summer-colors')">Summer</button>
<button (click)="theme = ''">reset</button>
<p>
current theme: {{theme}}
</p>
</div>
theme = '';
changeLayoutColor() {
this.theme = this.theme === 'contact-winter' ? 'contact-summer' : 'contact-winter';
}
addColor(color:string) {
this.theme += ` ${color}`
}
The :host pseudo-class works by matching the component's root element only when it also carries the specified class, allowing the styles to be applied selectively.
You can explore the live example here: https://theme-angular-components.surge.sh/
Wrap-Up
That covers it. This should serve as a practical starting point for styling and theming your components dynamically. If you found it useful, feel free to pass it along.



