Using Tailwind to Theme Your Angular App
Tags: angular, tailwindcss
Published: 2021-02-17 16:19:52

---
title: Using Tailwind to Theme Your Angular App
published: true
date: 2021-02-15 00:00:00 UTC
tags: angular, tailwind
canonical_url: https://medium.com/ngconf/using-tailwind-to-theme-your-angular-app-6393ae1d2f02
---

## [tldr;](#tldr)

One of Tailwind's best features is the ability to extend its color palette and other utility classes for your application. This functionality can be used to be able to dynamically change the theme of your Angular application. In this article, you'll look at how to create an Angular service that loads a theme as your app bootstraps.

## [Before You Start](#before-you-start)

Before you jump in to this article, it will be helpful for you to take a look at [this article](/blog/angular-and-tailwind) which covers how to use Tailwind in your Angular application. This article will not cover how to do that. If you already know how to set up Tailwind in your project, you should be good to go.

Also, I want to thank [Chau Tran](https://twitter.com/Nartc1410) for [his gist](https://gist.github.com/nartc/f6757f6ad9037436270cb93734c196ac), part of which I used in my service and utility functions.

## [Extending Tailwind's Color Palette](#extending-tailwinds-color-palette)

The key to being able to theme your application will be to extend Tailwind's color palette. This is one of the coolest features of Tailwind, and you can [read about it in their docs](https://tailwindcss.com/docs/customizing-colors#extending-the-defaults). To do so, you need to alter the `tailwind.config.js` file. The file can include a `theme` attribute, inside of which you can add an `extend` attribute. The items you add to `extend` will be added as utility classes that you can use in your HTML and SCSS files. Here's an example config file where the `colors` attribute is extended.

```js
// tailwind.config.js

module.exports = {
  theme: {
    extend: {
      colors: {
        // Here is where you will add our new colors
      }
    }
  }
}
```

Now that you know how to extend the colors, you'll need to decide how many colors can be changed in each of your themes. For this article, there are three colors: primary, secondary, and accent. When the application loads, three color values will need to be provided. The extended color palette will be built with those colors. We'll look at that shortly.

Now, There's another cool part of extending Tailwind's utility classes, and that is that the value for an extended class can be a [CSS custom property](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties), otherwise known as CSS variables. That's how you will be able to change the theme of the app, is by setting the color value of the classes to a CSS custom property. Then we'll use JavaScript to update those custom properties. Here's the full `tailwind.config.js` file, with all the classes for each color set up and ready to use.

```js
// tailwind.config.js

module.exports = {
  theme: {
    extend: {
      colors: {
        'primary-color': {
          DEFAULT: 'var(--primary-color-500'),
          50: 'var(--primary-color-50'),
          100: 'var(--primary-color-100'),
          200: 'var(--primary-color-200'),
          300: 'var(--primary-color-300'),
          400: 'var(--primary-color-400'),
          500: 'var(--primary-color-500'),
          600: 'var(--primary-color-600'),
          700: 'var(--primary-color-700'),
          800: 'var(--primary-color-800'),
          900: 'var(--primary-color-900'),
        },
        'secondary-color': {
          DEFAULT: 'var(--secondary-color-500'),
          50: 'var(--secondary-color-50'),
          100: 'var(--secondary-color-100'),
          200: 'var(--secondary-color-200'),
          300: 'var(--secondary-color-300'),
          400: 'var(--secondary-color-400'),
          500: 'var(--secondary-color-500'),
          600: 'var(--secondary-color-600'),
          700: 'var(--secondary-color-700'),
          800: 'var(--secondary-color-800'),
          900: 'var(--secondary-color-900'),
        },
        'accent-color': {
          DEFAULT: 'var(--accent-color-500'),
          50: 'var(--accent-color-50'),
          100: 'var(--accent-color-100'),
          200: 'var(--accent-color-200'),
          300: 'var(--accent-color-300'),
          400: 'var(--accent-color-400'),
          500: 'var(--accent-color-500'),
          600: 'var(--accent-color-600'),
          700: 'var(--accent-color-700'),
          800: 'var(--accent-color-800'),
          900: 'var(--accent-color-900'),
        }
      }
    }
  }
}
```

For each of the three colors of the theme — primary, secondary, and accent — there are 11 values. This is what Tailwind provides on their palette, thus you should do the same. Normally, `DEFAULT` is the middle color, `500`, but technically you can set it to what you want. What you will have now in your templates is the ability to turn a button's background to the primary color (any of the primary color values) with the class `.bg-primary-color-500`. You could also change the text color with `.text-primary-color-400`. This will be natural to you, as if you were using any of the provided colors directly from Tailwind. The values of each of these colors come from CSS custom properties. We'll set those properties momentarily.

## [Loading the Theme When the App Loads](#loading-the-theme-when-the-app-loads)

There are many ways to load the theme when the app bootstraps. In this example, the theme will be loaded using the `APP_INITIALIZER` token when the application bootstraps. This will allow you to change the theme whenever you want, depending on where you store the theme, because the configuration is runtime and not build time. You can read more about [the distinction between these types of configuration in this article](/blog/loading-app-config-in-app-initializer). Loading the configuration when the app bootstraps takes several parts working in tandem.

The first step, after creating a `TailwindThemeModule`, is to create a `class` that will be passed in to the module that will load the theme when it's imported. Here's the class:

```ts
// tailwind-theme-config.class.ts
export class TailwindThemeConfig {
  configUrl: string;

  constructor(obj: any = {}) {
    this.configUrl = obj.configUrl || './assets/tailwind-theme.config.json';
  }
}
```

We'll come back to this class momentarily. Next up is the service that will load the configuration file on bootstrap and initialize the theme:

```ts
// tailwind-theme.service.ts
import { Inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DOCUMENT } from '@angular/common';
import { TailwindTheme, updateThemeVariables } from '../tailwind-util';
import { TailwindThemeConfig } from '../tailwind-theme-config.class';
import { switchMap } from 'rxjs/operators';

@Injectable()
export class TailwindThemeService {
  constructor(
    private _http: HttpClient,
    @Inject(DOCUMENT) private readonly document: Document,
    private config: TailwindThemeConfig,
  ) {}

  loadConfig(): Promise<any> {
    const configUrl = this.config.configUrl || './assets/tailwind-theme.config.js';
    return this._http
      .get(`${configUrl}`)
      .pipe(
        switchMap((configObject: { themeUrl: string }) => {
          return this._http.get(configObject.themeUrl);
        }),
      )
      .toPromise()
      .then((themeData: TailwindTheme) => {
        updateThemeVariables(themeData, this.document);
      })
      .catch((err: any) => {
        console.error('There was an error while loading the Tailwind Theme.');
      });
  }
}
```

You will use this `loadConfig` method in a minute, and the `tailwind-util` files will be provided below as well. The class that we created before is injected into this service through the constructor. The `loadConfig` method uses that config object to know how to get the Tailwind theme configuration object. This should not be confused with the `tailwind.config.js` file that Tailwind proper uses; this is a custom configuration object that has information needed to load the theme. The first `http.get` gets that config object from a file. That file contains a `themeUrl` attribute that points to either another file or an API endpoint. The file can be local to the app or remote. The second `http.get` loads the theme information. This is where you'll get the primary, secondary, and accent colors for the theme. The return value needs to be converted to a `Promise`, and that's due to requirements of the `APP_INITIALIZER` token. The `.then` block gets the theme data, represented by the `TailwindTheme` interface and is passed to the `updateThemeVariables` function. Finally, errors are caught in the `.catch` block.

here's the `TailwindTheme` interface, and a `Color` interface as well:

```ts
// tailwind-theme.interface.ts
export interface Color {
  name: string;
  hex: string;
  isDarkContrast: boolean;
}

export interface TailwindTheme {
  'primary-color': string;
  'secondary-color': string;
  'accent-color': string;
}
```

If your theme uses more or less colors than primary, secondary, and accent, you'll want to add them to this interface. The next piece you need is the utility file that houses a couple useful functions for creating the color palette:

```ts
// tailwind-util.ts
import * as tinycolor from 'tinycolor2';
import { Color, TailwindTheme } from './tailwind-theme.interface';

export function computeColorPalette(hex: string): Color[] {
  return [
    getColorObject(tinycolor(hex).lighten(45), '50'),
    getColorObject(tinycolor(hex).lighten(40), '100'),
    getColorObject(tinycolor(hex).lighten(30), '200'),
    getColorObject(tinycolor(hex).lighten(20), '300'),
    getColorObject(tinycolor(hex).lighten(10), '400'),
    getColorObject(tinycolor(hex), '500'),
    getColorObject(tinycolor(hex).darken(10), '600'),
    getColorObject(tinycolor(hex).darken(20), '700'),
    getColorObject(tinycolor(hex).darken(30), '800'),
    getColorObject(tinycolor(hex).darken(40), '900'),
  ];
}

export function getColorObject(value: tinycolor.Instance, name: string): Color {
  const c = tinycolor(value);
  return {
    name,
    hex: c.toHexString(),
    isDarkContrast: c.isLight(),
  };
}

export function updateThemeVariables(theme: TailwindTheme, document: Document) {
  for (const [name, color] of Object.entries(theme)) {
    const palette = computeColorPalette(color);
    for (const variant of palette) {
      document.documentElement.style.setProperty(`--${name}-${variant.name}`, variant.hex);
    }
  }
}
```

The `updateThemeVariables` function takes in your `theme` interface object and loops over the keys in the object (`primary-color`, `secondary-color`, and `accent-color`) and creates a palette for each of them using the `computeColorPalette` function. Each of those color variants are looped over, and a CSS custom property is either created or updated for each value. Remember, your `tailwind.config.js` file uses those CSS custom property values for the extended color classes.

The last step in the `TailwindThemeModule` is the actual module file.

```ts
// tailwind-theme.module.ts
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TailwindThemeService } from './tailwind-theme/tailwind-theme.service';
import { TailwindThemeConfig } from './tailwind-theme-config.class';

export function initTailwindThemeConfig(tailwindThemeSvc: TailwindThemeService) {
  return () => tailwindThemeSvc.loadConfig();
}

@NgModule({
  imports: [CommonModule],
  providers: [
    TailwindThemeService,
    {
      provide: APP_INITIALIZER,
      useFactory: initTailwindThemeConfig,
      deps: [TailwindThemeService],
      multi: true,
    },
  ],
})
export class TailwindThemeModule {
  static forRoot(config: TailwindThemeConfig): ModuleWithProviders<TailwindThemeModule> {
    return {
      ngModule: TailwindThemeModule,
      providers: [
        {
          provide: TailwindThemeConfig,
          useValue: config,
        },
        TailwindThemeService,
      ],
    };
  }
}
```

This module file registers the `TailwindThemeService` and uses the `APP_INITIALIZER` token to call the `loadConfig` method. This function will be called when the app is imported and when the application is being initialized. In addition, the module allows for passing the `TailwindThemeConfig` object in to the module when importing it. That is what the `static forRoot` method is for.

This is all you need to create your Tailwind theme service. You can find all these files in full at [this gist](https://gist.github.com/pjlamb12/2ad54d7f1ab20c6f7a88429f31c7f27b).

## [Importing the `TailwindThemeModule`](#importing-the-tailwind-theme-module)

After you create your `TailwindThemeModule`, it needs to be imported into the main `AppModule` to be used. Here's one way to do this:

```ts
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { TailwindThemeModule } from './tailwind-theme';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    TailwindThemeModule.forRoot({ configUrl: './assets/config/tailwind-theme.config.json' }),
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

The module is imported, and an object provided in the `forRoot` method. That object has the `configUrl` that is required on the `TailwindThemeConfig` class. The value can be a string literal like this, but then that config (which, again, provides the location of the Tailwind theme colors) needs to be located in the same place in every environment. You could also use the `environment` file and change the location based on environment.

## [Running Your Application](#running-your-application)

Now that the `TailwindThemeModule` is imported, you can start your application. Serve it locally, and then open the `app.component.html` file. Add a few paragraphs of text to the component, each with a different Tailwind class. You could do something like this:

```html
<!-- app.component.html -->
<p class="text-primary-color-500">This will be the primary color.</p>
<p class="text-secondary-color-500">This will be the secondary color.</p>
<p class="text-accent-color-500">This will be the accent color.</p>
```

In addition to the `500` color value, you should have the `50` and `100`-`900` values.

## [Conclusion](#conclusion)

At this point, you now have the ability to theme your application with three different colors: primary, secondary, and accent. As long as the colors in the design can be used based off these three colors, you should be good to go. If not, you can add more accent colors in the same manner as you did above. Also, this article just shows you how to change the theme when the application bootstraps, but using the `updateThemeVariables` function would allow you to change the theme at any time. Again, this is because the extended colors values are based on CSS custom properties.