Manual Setup
If you can't use CLI, follow these steps to manually setup Angular Material and Tailwind CSS in your project.
> If you want to follow CLI setup, go to [CLI Setup](/docs/cli-setup) page.
## 1. Setup styles
To easily distinguish between your styles and blocks styles, we recommend to keep them in separate folders, for example: keep below files in `📁src/app/ngm-dev-blocks` folder:
### 1.2. Setup styles in angular.json
Once all the styles are in place, you can setup them in your angular.json file.
```json
"styles": [
"src/app/ngm-dev-blocks/styles/ngm-dev-blocks-styles.scss"
]
```
## 2. 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. 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
```
For nx projects, you can use the following commands:
```bash
npm i @angular/material
```
```bash
npx nx g @angular/material:ng-add --theme custom --typography --defaults --project PROJECT_NAME --skip-confirmation --non-interactive
```
## 5. Add Material Symbols
### 5.1 Add fonts in index.html
```html
```
### 5.2 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
> ```
## 6. Setup `ng2-charts`
Some blocks use `ng2-charts` to render charts. If you want to use it in your project, follow the steps below.
### 6.1 Install dependencies
```bash
npm install ng2-charts chart.js
```
### 6.2 Add providers in app.config.ts
```angular-ts app.config.ts
import { provideCharts, withDefaultRegisterables } from "ng2-charts";
export const appConfig: ApplicationConfig = {
providers: [provideCharts(withDefaultRegisterables())],
};
```
styles/themes/_dark.scss
**CLI (recommended)**:
```bash
npx @ngm-dev/cli add styles/themes
```
**Manual**:
```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;
}
```
styles/themes/_warn_.scss
**CLI (recommended)**:
```bash
npx @ngm-dev/cli add styles/themes
```
**Manual**:
```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),
)
);
}
```
styles/vendors/_tailwind.css
**CLI (recommended)**:
```bash
npx @ngm-dev/cli add styles/vendors
```
**Manual**:
```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;
}
/* `.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);
/* 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(
--mdc-outlined-card-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)));
}
}
}
/* #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 `@use '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.
styles/_base.scss
**CLI (recommended)**:
```bash
npx @ngm-dev/cli add styles/_base
```
**Manual**:
```scss
html {
background-color: var(--mat-sys-background);
color: var(--mat-sys-on-background);
}
```
styles/_dialogs.scss
**CLI (recommended)**:
```bash
npx @ngm-dev/cli add styles/_dialogs
```
**Manual**:
```scss
@use '@angular/material' as mat;
// In responsive mode, the dialog can be full screen.
// usage example:
// const options: MatDialogConfig = {
// maxWidth: isHandset ? '100dvw' : '1024px',
// panelClass: 'full-screen-dialog',
// };
// if (isHandset) {
// options.minWidth = '100dvw';
// options.minHeight = '100dvh';
// }
// this.dialog.open(AddApplicationDialogComponent, options);
.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);
}
}
}
// As per [Material Design guidelines](https://m3.material.io/components/dialogs/specs),
// dialog container should have a surface-container-high color.
html {
@include mat.dialog-overrides(
(
container-color: var(--mat-sys-surface-container-high),
)
);
}
```
styles/_sizes.scss
**CLI (recommended)**:
```bash
npx @ngm-dev/cli add styles/_sizes
```
**Manual**:
```scss
@use '@angular/material' as mat;
$densities: 1, 2, 3, 4, 5;
// Density is a utility that is used to set the density of the components.
@mixin sizes() {
@each $density in $densities {
.density-#{$density} {
@include mat.theme(
(
density: -$density,
)
);
mat-slide-toggle button {
transform: scale(1 - calc($density / 10));
}
}
}
}
html {
@include sizes();
}
```
styles/_tabs.scss
**CLI (recommended)**:
```bash
npx @ngm-dev/cli add styles/_tabs
```
**Manual**:
```scss
@use '@angular/material' as mat;
// As per [Material Design guidelines](https://m3.material.io/components/tabs/specs),
// primary tabs should have a 3px height and a 3px 3px 0 0 shape.
[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,
)
);
}
}
```
styles/ngm-dev-blocks-styles.scss
**CLI (recommended)**:
```bash
npx @ngm-dev/cli add styles/ngm-dev-blocks-styles
```
**Manual**:
```scss
// this file should be part of your styles array in angular.json
@forward './themes/dark'; // get it using `@ngm-dev/cli add styles/theme`
@forward './themes/warn'; // get it using `@ngm-dev/cli add styles/theme`
@forward './base'; // get it using `@ngm-dev/cli add styles/_base`
@forward './dialogs'; // get it using `@ngm-dev/cli add styles/_dialogs`
@forward './sizes'; // get it using `@ngm-dev/cli add styles/_sizes`
@forward './tabs'; // get it using `@ngm-dev/cli add styles/_tabs`
@forward './vendors/tailwind'; // get it using `@ngm-dev/cli add styles/vendors`
```