Getting Started

Manual Setup

Manual setup for Angular Material Blocks.

If you want to follow CLI setup, go to CLI Setup page.

1. Install @angular/material

Run the following command to install @angular/material:

Replace PROJECT_NAME with your project name.

ng add @angular/material --theme custom --typography --defaults --project PROJECT_NAME --skip-confirmation --non-interactive

2. Install tailwindcss

Run the following command to install tailwindcss:

npm install tailwindcss @tailwindcss/postcss postcss --force

3. Create PostCSS config file

Create a .postcssrc.json file in the root of your project with below content:

// .postcssrc.json
{
  "plugins": {
    "@tailwindcss/postcss": {}
  }
}

4. Customize tailwindcss

We are going to modify tailwindcss theme config in such a way that it allows utilities to use @angular/material's CSS variables.

Create a file at src/styles/vendors/tailwind.css with below content:

@import 'tailwindcss';

/* base for setting the font-families */
@layer base {
  body {
    /* same as `plain-family` in angular material */
    font-family: var(--mat-sys-body-large-font), sans-serif;

    /* apply @angular/material's background and foreground colors */
    background-color: var(--mat-sys-surface);
    color: var(--mat-sys-on-surface);
  }
  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    /* same as `brand-family` in angular material */
    font-family: var(--mat-sys-display-large-font), sans-serif;
  }
}

@utility form-field-error-icon {
  vertical-align: bottom;
  font-variation-settings: 'FILL' 1;
  height: 16px !important;
  width: 16px !important;
  font-size: 16px !important;
}

@utility icon-filled {
  font-variation-settings: 'FILL' 1;
}

@utility scrollbar-gutter-stable {
  scrollbar-gutter: stable;
}

@utility line-t {
  @apply relative before:absolute before:top-0 before:-left-[100vw] before:h-px before:w-[200vw] before:bg-gray-950/5 dark:before:bg-white/10;
}

@utility line-b {
  @apply relative after:absolute after:bottom-0 after:-left-[100vw] after:h-px after:w-[200vw] after:bg-gray-950/5 dark:after:bg-white/10;
}

@utility line-y {
  @apply relative;
  @apply before:absolute before:top-0 before:-left-[100vw] before:h-px before:w-[200vw] before:bg-gray-950/5 dark:before:bg-white/10;
  @apply after:absolute after:bottom-0 after:-left-[100vw] after:h-px after:w-[200vw] after:bg-gray-950/5 dark:after:bg-white/10;
}

@utility line-t/half {
  @apply relative before:absolute before:top-0 before:right-0 before:h-px before:w-screen before:bg-gray-950/5 dark:before:bg-white/10;
}

@utility line-b/half {
  @apply relative after:absolute after:right-0 after:bottom-0 after:h-px after:w-screen after:bg-gray-950/5 dark:after:bg-white/10;
}

@utility line-y/half {
  @apply relative;
  @apply before:absolute before:top-0 before:right-0 before:h-px before:w-screen before:bg-gray-950/5 dark:before:bg-white/10;
  @apply after:absolute after:right-0 after:bottom-0 after:h-px after:w-screen after:bg-gray-950/5 dark:after:bg-white/10;
}

/* `.dark-theme` is the class name to enable dark mode in our application */
@custom-variant dark (&:where(.dark-theme, .dark-theme *));

/* `inline` is needed to use the CSS variables in tailwind's @theme */
@theme inline {
  --color-primary: var(--mat-sys-primary);
  --color-on-primary: var(--mat-sys-on-primary);
  --color-primary-container: var(--mat-sys-primary-container);
  --color-on-primary-container: var(--mat-sys-on-primary-container);
  --color-secondary: var(--mat-sys-secondary);
  --color-on-secondary: var(--mat-sys-on-secondary);
  --color-secondary-container: var(--mat-sys-secondary-container);
  --color-on-secondary-container: var(--mat-sys-on-secondary-container);
  --color-tertiary: var(--mat-sys-tertiary);
  --color-on-tertiary: var(--mat-sys-on-tertiary);
  --color-tertiary-container: var(--mat-sys-tertiary-container);
  --color-on-tertiary-container: var(--mat-sys-on-tertiary-container);
  --color-error: var(--mat-sys-error);
  --color-on-error: var(--mat-sys-on-error);
  --color-error-container: var(--mat-sys-error-container);
  --color-on-error-container: var(--mat-sys-on-error-container);
  --color-outline: var(--mat-sys-outline);
  --color-outline-variant: var(--mat-sys-outline-variant);
  --color-surface: var(--mat-sys-surface);
  --color-surface-container-highest: var(--mat-sys-surface-container-highest);
  --color-surface-container-high: var(--mat-sys-surface-container-high);
  --color-surface-container-medium: var(--mat-sys-surface-container-medium);
  --color-surface-container-low: var(--mat-sys-surface-container-low);
  --color-surface-container-lowest: var(--mat-sys-surface-container-lowest);
  --color-surface-container: var(--mat-sys-surface-container);
  --color-surface-variant: var(--mat-sys-surface-variant);
  --color-on-surface-variant: var(--mat-sys-on-surface-variant);
  --color-on-surface: var(--mat-sys-on-surface);
  --color-inverse-surface: var(--mat-sys-inverse-surface);
  --color-inverse-on-surface: var(--mat-sys-inverse-on-surface);
  --color-inverse-primary: var(--mat-sys-inverse-primary);

  /* same as `plain-family` in angular material */
  --font-sans: var(--mat-sys-body-large-font), sans-serif;
  --font-mono:
    'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    Liberation Mono, Courier New, monospace;
  --font-display: var(--mat-sys-display-large-font), sans-serif;

  /* angular material radiuses */
  --radius-corner-extra-large: var(--mat-sys-corner-extra-large);
  --radius-corner-extra-small: var(--mat-sys-corner-extra-small);
  --radius-corner-full: var(--mat-sys-corner-full);
  --radius-corner-large: var(--mat-sys-corner-large);
  --radius-corner-medium: var(--mat-sys-corner-medium);
  --radius-corner-small: var(--mat-sys-corner-small);
  --radius-mat-card: var(
    --mat-card-outlined-container-shape,
    var(--mat-sys-corner-medium)
  );

  /* angular material font sizes */
  /* body */
  --text-body-small: var(--mat-sys-body-small-size);
  --text-body-small--line-height: var(--mat-sys-body-small-line-height);
  --text-body-small--letter-spacing: var(--mat-sys-body-small-tracking);
  --text-body-small--font-weight: var(--mat-sys-body-small-weight);
  --font-body-small: var(--mat-sys-body-small-font);
  --text-body-medium: var(--mat-sys-body-medium-size);
  --text-body-medium--line-height: var(--mat-sys-body-medium-line-height);
  --text-body-medium--letter-spacing: var(--mat-sys-body-medium-tracking);
  --text-body-medium--font-weight: var(--mat-sys-body-medium-weight);
  --font-body-medium: var(--mat-sys-body-medium-font);
  --text-body-large: var(--mat-sys-body-large-size);
  --text-body-large--line-height: var(--mat-sys-body-large-line-height);
  --text-body-large--letter-spacing: var(--mat-sys-body-large-tracking);
  --text-body-large--font-weight: var(--mat-sys-body-large-weight);
  --font-body-large: var(--mat-sys-body-large-font);

  /* display */
  --text-display-small: var(--mat-sys-display-small-size);
  --text-display-small--line-height: var(--mat-sys-display-small-line-height);
  --text-display-small--letter-spacing: var(--mat-sys-display-small-tracking);
  --text-display-small--font-weight: var(--mat-sys-display-small-weight);
  --font-display-small: var(--mat-sys-display-small-font);
  --text-display-medium: var(--mat-sys-display-medium-size);
  --text-display-medium--line-height: var(--mat-sys-display-medium-line-height);
  --text-display-medium--letter-spacing: var(--mat-sys-display-medium-tracking);
  --text-display-medium--font-weight: var(--mat-sys-display-medium-weight);
  --font-display-medium: var(--mat-sys-display-medium-font);
  --text-display-large: var(--mat-sys-display-large-size);
  --text-display-large--line-height: var(--mat-sys-display-large-line-height);
  --text-display-large--letter-spacing: var(--mat-sys-display-large-tracking);
  --text-display-large--font-weight: var(--mat-sys-display-large-weight);
  --font-display-large: var(--mat-sys-display-large-font);

  /* headline */
  --text-headline-small: var(--mat-sys-headline-small-size);
  --text-headline-small--line-height: var(--mat-sys-headline-small-line-height);
  --text-headline-small--letter-spacing: var(--mat-sys-headline-small-tracking);
  --text-headline-small--font-weight: var(--mat-sys-headline-small-weight);
  --font-headline-small: var(--mat-sys-headline-small-font);
  --text-headline-medium: var(--mat-sys-headline-medium-size);
  --text-headline-medium--line-height: var(
    --mat-sys-headline-medium-line-height
  );
  --text-headline-medium--letter-spacing: var(
    --mat-sys-headline-medium-tracking
  );
  --text-headline-medium--font-weight: var(--mat-sys-headline-medium-weight);
  --font-headline-medium: var(--mat-sys-headline-medium-font);
  --text-headline-large: var(--mat-sys-headline-large-size);
  --text-headline-large--line-height: var(--mat-sys-headline-large-line-height);
  --text-headline-large--letter-spacing: var(--mat-sys-headline-large-tracking);
  --text-headline-large--font-weight: var(--mat-sys-headline-large-weight);
  --font-headline-large: var(--mat-sys-headline-large-font);

  /* title */
  --text-title-small: var(--mat-sys-title-small-size);
  --text-title-small--line-height: var(--mat-sys-title-small-line-height);
  --text-title-small--letter-spacing: var(--mat-sys-title-small-tracking);
  --text-title-small--font-weight: var(--mat-sys-title-small-weight);
  --font-title-small: var(--mat-sys-title-small-font);
  --text-title-medium: var(--mat-sys-title-medium-size);
  --text-title-medium--line-height: var(--mat-sys-title-medium-line-height);
  --text-title-medium--letter-spacing: var(--mat-sys-title-medium-tracking);
  --text-title-medium--font-weight: var(--mat-sys-title-medium-weight);
  --font-title-medium: var(--mat-sys-title-medium-font);
  --text-title-large: var(--mat-sys-title-large-size);
  --text-title-large--line-height: var(--mat-sys-title-large-line-height);
  --text-title-large--letter-spacing: var(--mat-sys-title-large-tracking);
  --text-title-large--font-weight: var(--mat-sys-title-large-weight);
  --font-title-large: var(--mat-sys-title-large-font);

  /* label */
  --text-label-small: var(--mat-sys-label-small-size);
  --text-label-small--line-height: var(--mat-sys-label-small-line-height);
  --text-label-small--letter-spacing: var(--mat-sys-label-small-tracking);
  --text-label-small--font-weight: var(--mat-sys-label-small-weight);
  --font-label-small: var(--mat-sys-label-small-font);
  --text-label-medium: var(--mat-sys-label-medium-size);
  --text-label-medium--line-height: var(--mat-sys-label-medium-line-height);
  --text-label-medium--letter-spacing: var(--mat-sys-label-medium-tracking);
  --text-label-medium--font-weight: var(--mat-sys-label-medium-weight);
  --font-label-medium: var(--mat-sys-label-medium-font);
  --text-label-large: var(--mat-sys-label-large-size);
  --text-label-large--line-height: var(--mat-sys-label-large-line-height);
  --text-label-large--letter-spacing: var(--mat-sys-label-large-tracking);
  --text-label-large--font-weight: var(--mat-sys-label-large-weight);
  --font-label-large: var(--mat-sys-label-large-font);

  /* animations */
  /* fade-in */
  --animate-fadeIn: fadeIn 300ms ease-in-out forwards
    var(--animation-delay, 0ms);
  @keyframes fadeIn {
    0% {
      opacity: 0;
      transform: translateY(-5px);
    }
    100% {
      opacity: 1;
      transform: translateY(0);
    }
  }

  /* marquee */
  --animate-marquee: marquee var(--duration, 40s) linear infinite;

  @keyframes marquee {
    0% {
      transform: translateX(0);
    }
    100% {
      transform: translateX(calc(-100% - var(--gap, 1rem)));
    }
  }

  --animate-marquee-vertical: marquee-vertical var(--duration, 40s) linear
    infinite;

  @keyframes marquee-vertical {
    0% {
      transform: translateY(0);
    }
    100% {
      transform: translateY(calc(-100% - var(--gap, 1rem)));
    }
  }

  /* blink */
  --animate-blink: blink var(--duration, 500ms) linear infinite;

  @keyframes blink {
    100% {
      opacity: 0;
    }
  }
}

/* #region: Tailwind X Angular Material fix */
.mdc-notched-outline__notch {
  border-style: none;
}
.mat-mdc-icon-button {
  line-height: 1;
}
/* #endregion */

Let's break down the file:

  1. The @import 'tailwindcss'; directive imports the tailwindcss styles.
  2. The @layer base directive sets following:
    • font-families for the body and heading elements to be the same as the ones in angular material.
    • background and foreground colors for the body element to be the same as the ones in angular material.
  3. The @utility form-field-error-icon directive sets the styles for the form-field error icon.
  4. The @utility icon-filled directive sets the styles for the icon-filled class.
  5. The @theme inline directive sets the colors, font-families, radiuses to be the same as the ones in angular material.
  6. The @custom-variant dark directive sets the dark theme class.
  7. Few styles are overridden to fix the styles for the Angular Material components.

5. Add more styles

5.1 Add _dark.scss

Create a file at src/styles/themes/_dark.scss with below content:

/*
  To make `color-scheme: dark;` work make sure to have
  `color-scheme: light;` just before `@include mat.theme`
  and not `theme-type` in `color` property of `mat.theme` mixin
  in your @angular/material theme file
*/

.dark-theme {
  color-scheme: dark;
}

5.2 Add _warn.scss

Create a file at src/styles/themes/_warn.scss with below content:

@use '@angular/material' as mat;

// In mat-button and some other components, color="warn" is used to indicate urgent or error state.
[color='warn'],
.warn-theme {
  @include mat.theme-overrides(
    (
      primary: var(--mat-sys-error),
      primary-container: var(--mat-sys-error-container),
      on-primary: var(--mat-sys-on-error),
      on-primary-container: var(--mat-sys-on-error-container),
    )
  );
}

5.3 Add _dialogs.scss

Create a file at src/styles/_dialogs.scss with below content:

@use '@angular/material' as mat;

.side-dialog {
  position: absolute !important;
  top: 0;
  bottom: 0;
  right: 0;

  &-lg {
    --mat-dialog-container-max-width: 80vw;
    width: var(--mat-dialog-container-max-width);
  }

  .mat-mdc-dialog-content {
    max-height: 100%;
  }

  .mat-mdc-dialog-surface {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
  }
}

.full-screen-dialog {
  @media (width < 40rem) {
    @include mat.dialog-overrides(
      (
        container-shape: 0,
      )
    );
    .mat-mdc-dialog-content {
      // device height - header height -actions height
      max-height: calc(100dvh - 68px);
    }
  }
}

.dialog-outlined {
  .dark-theme & {
    .mat-mdc-dialog-surface {
      border: 1px solid var(--mat-sys-outline);
    }
  }
}

5.4 Add _sizes.scss

Create a file at src/styles/_sizes.scss with below content:

@use '@angular/material' as mat;

$densities: 1, 2, 3, 4, 5;

@mixin sizes() {
  @each $density in $densities {
    .density-#{$density} {
      @include mat.theme(
        (
          density: -$density,
        )
      );

      mat-slide-toggle button {
        transform: scale(1 - calc($density / 10));
      }
    }
  }
}

@include sizes();

5.5 Add _tabs.scss

Create a file at src/styles/_tabs.scss with below content:

@use '@angular/material' as mat;

.m3-primary-tab-group > .mat-mdc-tab-header {
  .mdc-tab {
    mat-icon {
      font-variation-settings: 'FILL' 0;
      transition: font-variation-settings
        var(--mat-tab-animation-duration, 250ms) cubic-bezier(0.35, 0, 0.25, 1);
    }
    &--active {
      mat-icon {
        font-variation-settings: 'FILL' 1;
      }
    }
  }
}

[fitInkBarToContent],
[ng-reflect-fit-ink-bar-to-content='true'] {
  > .mat-mdc-tab-header {
    @include mat.tabs-overrides(
      (
        active-indicator-shape: 3px 3px 0 0,
        active-indicator-height: 3px,
      )
    );
  }
}

// apply below class to <mat-tab-group />
.pill-tab-group {
  --active-indicator-color: var(--mat-sys-primary-container);
  --active-indicator-height: 32px;
  --active-indicator-shape: var(--mat-sys-corner-small);

  // set this to transparent if you don't want ripple to be visible
  --ripple-color: var(--mat-sys-on-surface);

  @include mat.tabs-overrides(
    (
      active-indicator-height: var(--active-indicator-height),
      active-indicator-color: var(--active-indicator-color),
      active-focus-indicator-color: var(--active-indicator-color),
      active-hover-indicator-color: var(--active-indicator-color),
      active-indicator-shape: var(--active-indicator-shape),
      active-ripple-color: var(--ripple-color),
      inactive-ripple-color: var(--ripple-color),
    )
  );

  .mdc-tab {
    /**
    /* need to keep the z-index high so that
    /* active indicator of next tab does not overlap label
    */
    &:not(.mdc-tab--active) {
      z-index: 2;
    }

    /**
    /* keep active indicator vertically center aligned
    */
    .mdc-tab-indicator {
      top: 50%;
      transform: translateY(-50%);
      height: var(--active-indicator-height);
      border-radius: var(--active-indicator-shape);
    }

    /**
    /* keep ripple vertically & horizontally center aligned
    */
    .mat-ripple,
    .mdc-tab__ripple::before {
      top: 50%;
      transform: translateY(-50%) translateX(-50%);
      height: var(--active-indicator-height);
      border-radius: var(--active-indicator-shape);
      left: 50%;

      // reduced width to visually show gap when hovered
      width: 90%;
    }
  }
}

5.6 Add _base.scss

Create a file at src/styles/_base.scss with below content:

html {
  background-color: var(--mat-sys-background);
  color: var(--mat-sys-on-background);
}

5.7 Add index.scss

Create a file at src/styles/index.scss with below content:

@forward './themes/dark';
@forward './themes/warn';
@forward './dialogs';
@forward './sizes';
@forward './tabs';
@forward './base';

6. Setup styles in angular.json

Make sure to add src/styles/index.scss & src/styles/vendors/tailwind.css to your styles array in angular.json file:

"styles": [
  "src/styles/index.scss",
  "src/styles/vendors/tailwind.css"
]

7. Add material symbols

We are using new Material Symbols in our project. So, we need to add the following link to our index.html file:

<link
  rel="stylesheet"
  href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200" />

7.1. Update app.config.ts to use Material Symbols

import { MAT_ICON_DEFAULT_OPTIONS } from "@angular/material/icon";

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: MAT_ICON_DEFAULT_OPTIONS,
      useValue: {
        fontSet: "material-symbols-outlined",
      },
    },
  ],
};

Now you can also use filled variants of the icons by adding icon-filled to the class. We have already added this class to _tailwind.css file.

<mat-icon class="icon-filled">home</mat-icon>

8. Setup ng2-charts

Some blocks use ng2-charts to render charts. Run below command to install ng2-charts:

ng add ng2-charts

8.1 Add providers in app.config.ts

In case CLI does not add providers, you can add them manually:

import { provideCharts, withDefaultRegisterables } from "ng2-charts";

export const appConfig: ApplicationConfig = {
  providers: [
    provideCharts(withDefaultRegisterables()),
  ],
};

9. Add utilities & helpers

Our components depend on a few utilities. You can read more about them in the Utilities section.

10. Font smoothing (antialiasing)

On our website, we apply font smoothing and recommend you do the same. Simply add the antialiased utility to the HTML tag <html className="antialiased">.

Found a bug? Let us know →

Angular Material Blocks Logo

Angular Material Dev UI (or Angular Material Blocks) is one place stop for developers to explore components and blocks for their Angular Material and Tailwind CSS based applications.

Find us on X (Twitter), LinkedIn, Instagram & Threads