> If you want to follow CLI setup, go to [CLI Setup](/docs/cli/setup) page.
## 1. Install `@angular/material`
Run the following command to install `@angular/material`:
> Replace `PROJECT_NAME` with your project name.
```bash
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`:
```bash
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:
```json
// .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:
```css
@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;
}
/* `.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:
```scss
/*
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:
```scss
@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:
```scss
@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:
```scss
@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:
```scss
@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
.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:
```scss
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:
```scss
@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:
```json
"styles": [
"src/styles/index.scss",
"src/styles/vendors/tailwind.css"
]
```
## 7. Add material symbols
We are using new [Material Symbols](https://fonts.google.com/icons) in our project. So, we need to add the following link to our `index.html` file:
```html
```
### 7.1. Update app.config.ts to use Material Symbols
```angular-ts
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.
>
> ```html
> home
> ```
## 8. Setup `ng2-charts`
Some blocks use `ng2-charts` to render charts. Run below command to install `ng2-charts`:
```bash
ng add ng2-charts
```
### 8.1 Add providers in app.config.ts
In case CLI does not add providers, you can add them manually:
```angular-ts
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](/docs/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 ``.
Found a bug? Let us know →