Skip to content

Angular + Stitches: How to use CSS-in-JS to style your Angular Components

Published at
Updated at
Reading time
5m

CSS-in-JS is a pretty nice way to get encapsulated component styling. It's popular in React but can be used with Angular too. We'll use Stitches together with Angular to style components.

Angular Styling

Angular supports encapsulated component styling out of the box. Components can be styled using CSS or SCSS.

// Your code ...
.danger {
  color: red;
}

//  ... gets compiled into this
.danger[_ngcontent-pwx-c41] {
  color: red;
}

The component styles are part of the JS bundle (no separate CSS file except for your global styles) and are added into the head of your document as soon as your component is rendered.

However, there are some drawbacks:

  • Another language: You need to learn SCSS with variables, mixins, loops and more.

  • Global class names: Angular does not change your class names. If you integrate your application into a Micro Frontend with global styles, this may affect your components.

  • No type-safety: All your TypeScript code is type-safe. Your component templates are type-safe. Your CSS classes are ... just a bunch of untyped strings. Nothing stops you from using an invalid class name in your template.

So there are good reasons for using CSS-in-JS. Let's see how we can integrate it with Angular.

CSS-in-JS with Stitches

Stitches is a new CSS-in-JS library, currently in beta (update september 2021: Stitches is now out of beta and version 1.1 was released recently). It's primarily made for React but the core is framework-agnostic. We will use the core library and integrate it into Angular.

Add Stitches to Angular

Let's first create a new Angular application with ng new angular-stitches and then install Stitches:

npm i @stitches/core

Create a theme

We then create a global theme. The createCss function accepts tokens for colors, fonts, font sizes, box shadows and much more. In addition you can define media queries for responsive designs and other configurations like a prefix for all class names.

We keep it simple for now and just add some tokens.

// ./src/styles.ts
import { createCss } from "@stitches/core";

export const { css } = createCss({
  theme: {
    colors: {
      text: "#333",
      primary: "blue",
      onPrimary: "white",
    },
    fontSizes: {
      1: "0.875rem",
      2: "1rem",
      3: "1.125rem",
      4: "1.5rem",
      5: "2rem",
    },
    space: {
      1: "0.25rem",
      2: "0.5rem",
      3: "0.75rem",
      4: "1rem",
      5: "2rem",
    },
  },
});

Calling createCss returns functions to create encapsulated styles, global styles, keyframes and more. For now we just re-export the css function to create our component styles.

Build a basic component

Now let's add some basic styling to our component. Stitches creates custom properties for our tokens. We can use our tokens by prefixing them with $ and Stitches will replace them with var(...) to use the corresponding custom property.

import { css } from "src/styles";

const styledTitle = css({
  margin: "0 0 $5 0",
  fontSize: "$5",
  color: "$primary",
});

const styledIntro = css({
  margin: 0,
  fontSize: "$3",
});

Stitches will not add any CSS to our document until we call the function that css returned. Let's add our two styles as a property to our component and use it in our template.

const styledTitle = css({ ... });
const styledIntro = css({ ... });

@Component({
  selector: "app-root",
  template: `  
    <h1 [ngClass]="cn.title">Angular + Stitches</h1>
    <p [ngClass]="cn.intro">Lorem ipsum ...</p>
  `,
})
export class AppComponent {
  cn = {
    title: styledTitle().className,
    intro: styledIntro().className,
  };
}

You can then inspect the DOM and you will see that Stitches generated unique class names for our two styles:

Generated HTML elements with unique class names.

Add a variant

Now let's add a variant to our title. A variant allows us to style an element different depending on input properties.

const styledTitle = css({
  ...

  variants: {
    color: {
      text: {
        color: "$text",
      },
      primary: {
        color: "$primary",
      },
    },
  },
});

This creates a variant named color that accepts either text or primary. We then need to pass the variant to the styledTitle function.

export class AppComponent {
  readonly cn = {
    title: styledTitle({ color: "primary" }).className,
    intro: styledIntro().className,
  };
}

Now we add a toggle button and change the styling dynamically:

export class DemoOneComponent implements OnInit {
  cn = {
    title: "",
    intro: "",
  };

  private titleIsPrimary = false;

  ngOnInit(): void {
    this.buildStyles();
  }

  toggleTitleColor(): void {
    this.titleIsPrimary = !this.titleIsPrimary;
    this.buildStyles();
  }

  private buildStyles(): void {
    this.cn = {
      title: styledTitle({ 
        color: this.titleIsPrimary ? "primary" : "text" 
      }).className,
      intro: styledIntro().className,
    };
  }
}

Make it reactive

In a real application your styling may depend on some observables and you probably want to use the OnPush change detection strategy for better performance. Let's improve our component to work with streams.

We change our title property from a string into an observable (and rename it to title$ because it's a stream now). We then wrap our object into a classNames utility function that will return the className property of each style rule (more on that later).

export class DemoTwoComponent {
  private titleIsPrimary = new BehaviorSubject(false);

  cn = classNames({
    title$: this.titleIsPrimary.pipe(
      map((isPrimary) => styledTitle({ color: isPrimary ? "primary" : "text" }))
    ),
    intro: styledIntro,
  });

  toggleTitleColor(): void {
    this.titleIsPrimary.next(!this.titleIsPrimary.value);
  }
}

We then use the async pipe to bind the class name. If you have strictTemplates enabled you will get an error because async may return null and the ngClass directive does not accept null (see this issue on GitHub). You can either bind directly to class or use a custom pipe that returns an empty string for null values.

<h1 [ngClass]="cn.title$ | async | notNull">Angular + Stitches</h1>

Now we can set changeDetection to onPush. Working with observables not just improves the performance but also makes the code more readable.

@Component({
  ...
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DemoTwoComponent  {
  // ...
}

classNames utility

And here is our classNames utility function. It is just a small helper function that returns the className property of each styled expression.

function classNames<T extends StyleMap>(obj: T): ClassNameMap<T> {
  const mapped = Object.entries(obj).map(([key, value]) => [
    key,
    isObservable(value) 
      ? value.pipe(map((value) => value.className)) 
      : value.className,
  ]);
  return Object.fromEntries(mapped);
}

Demo

The demo project is available on GitHub: @rothsandro/angular-stitches