avatarLee Norris

Summary

The provided content outlines a method for integrating Tailwind CSS with Angular Material themes in an Angular project, ensuring consistent theming across both frameworks.

Abstract

The article presents a comprehensive guide for Angular developers to seamlessly integrate Tailwind CSS with Angular Material themes. It emphasizes maintaining a single source of truth for theming while allowing for the creation of arbitrary themes as needed. The guide is structured into seven steps, starting with including Angular Material and Tailwind in an Angular project, defining SASS/SCSS variables for Material parameters, creating a custom Material theme using these variables, instantiating the theme with Material SCSS/SASS mixins, defining corresponding Tailwind variables in tailwind.config.js, importing the Material theme and CSS variables in the root SASS/SCSS file, and finally, using the created themes in Angular Material or Tailwind, or both. The author also provides examples of how to apply the themes to components and suggests further creative applications of the theming framework.

Opinions

  • The author values consistency in theming between Tailwind CSS and Angular Material, viewing it as essential for a cohesive user interface.
  • They advocate for the use of SASS/SCSS variables to maintain a single source of truth for color and theme definitions, which can be reused across components.
  • The author sees the benefit in defining custom themes that can be conditionally applied, such as a dark theme or a USA-specific theme, to cater to different user preferences or branding requirements.
  • They highlight the importance of being able to pass in theme values from consuming code, which is particularly useful for library development or embedding Angular apps into other applications.
  • The author suggests that the approach detailed in the article is just a starting point, encouraging developers to explore and apply their creativity to extend the theming capabilities further.
  • They recommend additional learning resources, including a YouTube video and the official Tailwind CSS and Angular Material documentation, indicating a belief in continuous learning and improvement.
  • The author endorses an AI service, ZAI.chat, as a cost-effective alternative to ChatGPT Plus (GPT-4), suggesting confidence in the service's performance and value for developers.

How to integrate Tailwind and Angular Material Themes

As an Angular developer, I find myself in a committed relationship with Angular Material. At the same time, I try to maximize my use of Tailwind CSS. Here are my guidelines for maintaining single-source-of-truth and consistent theming between the two frameworks while providing the extensibility to define completely arbitrary themes as needed.

Step #0: Include Angular Material and Tailwind in an Angular project or NX monorepo containing an Angular project

An Angular project with Tailwind configured is a prerequisite for this tutorial.

Step #1: Define SASS/SCSS variables corresponding to the relevant parameters exposed by Material

The purpose of creating these SASS/SCSS variables is primarily for use in component style definitions (think styles/styleUrls in an @Component invocation) so that custom components can reference the same colors Material is using without conventionally extracting the variables from the Material configuration via SASS/SCSS functions. Secondly, these variables provide a concrete compilation target (ie I get an error if I make a mistake).

The variable declaration should be something like this:

// name this file _colors.scss or _variables.scss or _whatever-you-want.scss

// this is my base text color and a subtle-text counterpart
// i'm not passing this into material... but could if desired
$base: var(--base);
$base-muted: var(--base-muted);

// this is my contrast text color and a subtle-text counterpart
// i'm not passing this into material... but could if desired
$contrast: var(--contrast);
$contrast-muted: var(--contrast-muted);

// these 3 pairs of color variables are a minimalistic representation of conventional material color variables
$accent: var(--accent);
$accent-contrast: var(--accent-contrast);

$primary: var(--primary);
$primary-contrast: var(--primary-contrast);

$warn: var(--warn);
$warn-contrast: var(--warn-contrast);

Step #2: Define a custom Material theme comprised strictly of the SASS/SCSS variables created in the previous step

The Material theme should look like this:

// name this file _themes.scss or something like that
@use 'sass:map';
@use '@angular/material' as mat;
@import '../common/_colors';

$primary-palette: (
  mat.define-palette(
    (
      100: $primary,
      500: $primary,
      700: $primary,
      contrast: (
        100: $primary-contrast,
        500: $primary-contrast,
        700: $primary-contrast,
      ),
    )
  )
);

$accent-palette: (
  mat.define-palette(
    (
      100: $accent,
      500: $accent,
      700: $accent,
      contrast: (
        100: $accent-contrast,
        500: $accent-contrast,
        700: $accent-contrast,
      ),
    )
  )
);

$warn-palette: (
  mat.define-palette(
    (
      100: $warn,
      500: $warn,
      700: $warn,
      contrast: (
        100: $warn-contrast,
        500: $warn-contrast,
        700: $warn-contrast,
      ),
    )
  )
);

$theme: (
  color: (
    primary: $primary-palette,
    accent: $accent-palette,
    warn: $warn-palette,
    foreground: mat.$light-theme-foreground-palette,
    background: mat.$light-theme-background-palette
  ),
);

$dark-theme: (
  color: (
    primary: $primary-palette,
    accent: $accent-palette,
    warn: $warn-palette,
    foreground: mat.$dark-theme-foreground-palette,
    background: mat.$dark-theme-background-palette
  ),
);

Step #3: Instantiate the Material theme(s) using the Material SCSS/SASS mixins

// name this file something like _material.scss
@use 'sass:map';
@use '@angular/material' as mat;
@include mat.core();

@import '../common/_colors'; // or whatever you named this
@import 'themes'; // or whatever you named this

// force material to use theme colors for raised/flat button text color
a,
button {
  &[mat-raised-button], &[mat-flat-button] {
    &[color=primary] {
      color: $primary-contrast !important;
    }

    &[color=accent] {
      color: $accent-contrast !important;
    }

    &[color=warn] {
      color: $warn-contrast !important;
    }
  }
}

// now we add the two themes we created

// default theme
@include mat.all-component-themes($theme);

// conditionally apply theme via a CSS class
.dark-theme {
    @include mat.all-component-themes($dark-theme);
}

Step #4: Define corresponding Tailwind variables in tailwind.config.js

The tailwind.config.js file should contain similarly named variables that pull from the same CSS variable as the SCSS equivalent defined in Step #1. This allows us to use the Tailwind utility classes for color and background color. For the example I’m providing, the config should look like this:

const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
const { join } = require('path');

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
    ...createGlobPatternsForDependencies(__dirname),
  ],
  theme: {
    extend: {
      colors: {
        'base': 'var(--base)',
        'base-muted': 'var(--base-muted)',
        'contrast': 'var(--contrast)',
        'contrast-muted': 'var(--contrast-muted)',
        'accent': 'var(--accent)',
        'accent-contrast': 'var(--accent-contrast)',
        'primary': 'var(--primary)',
        'primary-contrast': 'var(--primary-contrast)',
        'warn': 'var(--warn)',
        'warn-contrast': 'var(--warn-contrast)'
      }
    },
  },
  plugins: [],
};

Step #5: Import the Material theme and define CSS variables in the root SASS/SCSS file of your Angular project (usually styles.scss)

This is where you use everything defined in the previous steps. This works for a single Angular app or multiple Angular apps in a given project. Note that the configurations from previous steps are only defined once and re-used whether consumed in one app or multiple apps. However, this step will need to be completed for each app (unless you want them all to share the same themes in which case you would only define this file once in a common directory and re-import the file into each app’s styles.scss file). The styles.scss should contain the following:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
    :root {
        --base: rgba(0, 0, 0, 0.8);
        --base-muted: rgba(0, 0, 0, 0.54);

        --contrast: rgba(255, 255, 255, 0.8);
        --contrast-muted: rgba(255, 255, 255, 0.54);

        --accent: rgb(250, 160, 64);
        --accent-contrast: #ffffff;

        --primary: rgb(7, 108, 145);
        --primary-contrast: #ffffff;

        --warn: rgb(205, 62, 71);
        --warn-contrast: #ffffff;

        color: var(--base);

        .dark-theme {
            --contrast: rgba(0, 0, 0, 0.8);
            --contrast-muted: rgba(0, 0, 0, 0.54);

            --base: rgba(255, 255, 255, 0.8);
            --base-muted: rgba(255, 255, 255, 0.54);

            --primary: hotpink;
            --accent: limegreen;

            color: var(--base);
        }

        .usa-theme {
            --base: #002868;
            --base-muted: #0028689c;

            --contrast: rgba(255, 255, 255, 0.8);
            --contrast-muted: rgba(255, 255, 255, 0.54);

            --accent: #BF0A30;
            --accent-contrast: #002868;

            --primary: #002868;
            --primary-contrast: #ffffff;

            --warn: #BF0A30;
            --warn-contrast: #ffffff;

            color: var(--base);
        }
    }
}

// import the file created in step #3 (named something like _material.scss)
// it would probably look like this for a single Angular app scenario
@import 'material';

// ... or for multi-app projects using a common config...
// just use the full path from the app to the other lib with the file
// @import '../../../../libs/ui/common/src/lib/styles/_material.scss';

Step #6: Use the created themes in Angular Material or Tailwind… or BOTH!

Given the following test component template that uses both Tailwind utility color classes (bg-blah or text-blah or border-blah where “blah” is a defined color variable in tailwind.config.js):

<mat-card class="p-4 flex flex-col gap-8 mat-elevation-z8">
    <div class="p-2">This is a component with some Material buttons and Tailwind text.</div>

    <div class="p-2 mat-elevation-z4 rounded text-lg font-bold bg-primary-contrast text-primary border-solid border-4 border-primary">primary - this uses tailwind utility classes</div>
    <div class="flex flex-row gap-2 py-4">
        <button mat-button color="primary">button</button>
        <button mat-raised-button color="primary">raised</button>
        <button mat-flat-button color="primary">flat</button>
        <button mat-stroked-button color="primary">stroked</button>
    </div>

    <div class="p-2 mat-elevation-z4 rounded text-lg font-bold bg-accent-contrast text-accent border-solid border-4 border-accent">accent - this uses tailwind utility classes</div>
    <div class="flex flex-row gap-2 py-4">
        <button mat-button color="accent">button</button>
        <button mat-raised-button color="accent">raised</button>
        <button mat-flat-button color="accent">flat</button>
        <button mat-stroked-button color="accent">stroked</button>
    </div>

    <div class="p-2 mat-elevation-z4 rounded text-lg font-bold bg-warn-contrast text-warn border-solid border-4 border-warn">warn - this uses tailwind utility classes</div>
    <div class="flex flex-row gap-2 py-4">
        <button mat-button color="warn">button</button>
        <button mat-raised-button color="warn">raised</button>
        <button mat-flat-button color="warn">flat</button>
        <button mat-stroked-button color="warn">stroked</button>
    </div>

    <mat-form-field>
        <mat-label>Do you love it?</mat-label>
        <input matInput type="text" />
    </mat-form-field>
</mat-card>

And given the following invocations of that component with our theming classes:

<div class="flex flex-row justify-around">
    <test></test>
    <test class="dark-theme"></test>
    <test class="usa-theme"></test>
</div>

You should see the separate themes rendered with consistent coloring between Tailwind and Material:

Step #7: Theme all the things

This tutorial is only a superficial serving-suggestion. If you’re creative, you’ve probably already realized how many different variations could be applied to this conceptual framework detailed in this article. For example, you can modify CSS variables inside of individual components or create a component that encapsulates predefined or overrideable values for the CSS variables. Another example is being able to pass-in values for these variables from consuming code if you’re writing a library or creating Angular apps for embedding into other apps/pages (which is my use-case). The list goes on and on!

Step #8: Learn more

This video is pure excellence if you’d like to learn even more advanced techiniques for theming Tailwind:

https://www.youtube.com/watch?v=MAtaT8BZEAo&t=782s

Tailwind documentation: https://tailwindcss.com/docs

Material Source Code: https://github.com/angular/components

Recommended from ReadMedium