Manual setup for Angular Material Blocks.
If you want to follow CLI setup, go to
CLI Setup page.
@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
tailwindcss
Run the following command to install tailwindcss
:
npm install tailwindcss @tailwindcss/postcss postcss --force
Create a .postcssrc.json
file in the root of your project with below content:
// .postcssrc.json
{
"plugins": {
"@tailwindcss/postcss": {}
}
}
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:
@import 'tailwindcss';
directive imports the tailwindcss styles.@layer base
directive sets following:@utility form-field-error-icon
directive sets the styles for the form-field error icon.@utility icon-filled
directive sets the styles for the icon-filled class.@theme inline
directive sets the colors, font-families, radiuses to be the same as the ones in angular material.@custom-variant dark
directive sets the dark theme class._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;
}
_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),
)
);
}
_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);
}
}
}
_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();
_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%;
}
}
}
_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);
}
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';
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"
]
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" />
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>
ng2-charts
Some blocks use ng2-charts
to render charts. Run below command to install ng2-charts
:
ng add ng2-charts
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()),
],
};
Our components depend on a few utilities. You can read more about them in the
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">
.
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