How to Set Up Angular Material with Tailwind CSS in Angular 21 (Clean & Correct Way)
Technology Enthusiast and voracious reader with a demonstrated history of working in the computer software industry. Skilled in PHP, JavaScript, NodeJS, Angular, MySQL, MongoDB, Web3, Product Development, Project Management, and Teamwork.
Angular Material and Tailwind CSS solve different problems:
Angular Material provides:
Accessibility
Interaction behavior
Battle-tested UI components
Tailwind CSS provides:
Rapid layout
Utility-first styling
Design consistency at scale
In Angular 21, combining them incorrectly leads to confusing Sass errors due to Material theming API changes.
This guide shows the only clean, future-proof way to integrate both — without hacks, legacy APIs, or compiler conflicts.
Core Principle (Very Important)
To avoid all known issues:
Material → SCSS
Tailwind → CSS
Connect them ONLY via CSS variables
No Tailwind inside SCSS.
No Sass inside CSS.
No legacy Material APIs.
Why Most Tutorials Break on Angular 21
Starting from Angular Material v17 and fully enforced in v21:
Legacy Material theming APIs were removed
M2 theming APIs were namespaced
Palette exports changed
Sass import paths changed
❌ What no longer works
@import '@angular/material/theming';
mat.define-palette(...)
$blue-palette
✅ What works in Angular 21
mat.m2-define-palette(...)
mat.$m2-blue-palette
Step 1: Install Dependencies
ng add @angular/material
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
Ensure you are on:
Angular 21
Angular Material 21
Tailwind 3+
Step 2: Configure angular.json
You must include two style files, in this order:
"styles": [
"src/material-theme.scss",
"src/styles.css"
]
Why order matters:
Material defines CSS variables first
Tailwind consumes them later
Step 3: Angular Material Theme (SCSS Only)
📁 src/material-theme.scss
This file:
Uses Material v21 M2 APIs
Defines palettes
Exposes CSS variables
Contains zero Tailwind
@use '@angular/material' as mat;
/* Material core */
@include mat.core();
/* M2 palettes (Angular Material v21 compatible) */
$primary-palette: mat.m2-define-palette(mat.$m2-blue-palette);
$accent-palette: mat.m2-define-palette(mat.$m2-teal-palette);
$warn-palette: mat.m2-define-palette(mat.$m2-red-palette);
/* Light theme */
$light-theme: mat.m2-define-light-theme((
color: (
primary: $primary-palette,
accent: $accent-palette,
warn: $warn-palette,
),
));
/* Apply Material styles */
@include mat.all-component-themes($light-theme);
/* Expose colors as CSS variables */
:root {
--color-primary: #{mat.m2-get-color-from-palette($primary-palette)};
--color-primary-contrast: #{mat.m2-get-color-from-palette($primary-palette, default-contrast)};
--color-accent: #{mat.m2-get-color-from-palette($accent-palette)};
--color-accent-contrast: #{mat.m2-get-color-from-palette($accent-palette, default-contrast)};
--color-warn: #{mat.m2-get-color-from-palette($warn-palette)};
/* App tokens */
--color-background: 255 255 255;
--color-surface: 249 250 251;
--color-border: 229 231 235;
--color-text-primary: 0 0 0;
--color-text-secondary: 75 85 99;
}
Why CSS Variables?
They are:
Framework-agnostic
Runtime-switchable
Safe for Tailwind
Safe for Material
Step 4: Tailwind Configuration (Consumes Variables)
📁 tailwind.config.ts
Tailwind never defines colors directly.
It reads from Material tokens.
import type { Config } from 'tailwindcss';
const config: Config = {
content: ['./src/**/*.{html,ts}'],
theme: {
extend: {
colors: {
primary: 'rgb(var(--color-primary) / <alpha-value>)',
primaryContrast: 'rgb(var(--color-primary-contrast) / <alpha-value>)',
accent: 'rgb(var(--color-accent) / <alpha-value>)',
accentContrast: 'rgb(var(--color-accent-contrast) / <alpha-value>)',
warn: 'rgb(var(--color-warn) / <alpha-value>)',
background: 'rgb(var(--color-background) / <alpha-value>)',
surface: 'rgb(var(--color-surface) / <alpha-value>)',
border: 'rgb(var(--color-border) / <alpha-value>)',
textPrimary: 'rgb(var(--color-text-primary) / <alpha-value>)',
textSecondary: 'rgb(var(--color-text-secondary) / <alpha-value>)',
},
},
},
plugins: [],
};
export default config;
This enables:
bg-primarytext-accentborder-borderOpacity utilities like
bg-primary/80
Step 5: Tailwind Styles (CSS Only)
📁 src/styles.css
This file contains:
Tailwind directives
Plain CSS
No Sass
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Global styles */
html,
body {
height: 100%;
}
body {
margin: 0;
background-color: rgb(var(--color-background));
color: rgb(var(--color-text-primary));
font-family: Roboto, "Helvetica Neue", sans-serif;
}
Step 6: Usage Examples
Material + Tailwind Together
<button
mat-raised-button
color="primary"
class="px-6 py-2 rounded-lg"
>
Save
</button>
Tailwind-Only Component (Still Material Colors)
<div class="bg-surface border border-border text-textPrimary p-4 rounded-xl">
Dashboard Card
</div>
Result:
Perfect color consistency
No overrides
No conflicts
Common Mistakes to Avoid
| Mistake | Why it breaks |
| Using legacy theming APIs | Removed in v21 |
| Importing Material in CSS | Sass not allowed |
| Importing Tailwind in SCSS | PostCSS conflict |
| Defining colors twice | Design drift |
| Overriding Material internals | Upgrade risk |
Why This Architecture Is Enterprise-Safe
Clear ownership of responsibilities
Upgrade-safe for Angular 22+
Works with SSR and hydration
Easy to document and scale
Ideal for internal design systems
This pattern is used in large Angular codebases for a reason.
Next Enhancements You Can Add Safely
Dark mode (via CSS variable switching)
Typography token alignment
Material elevation → Tailwind shadows
Theme switch service
Nx-based UI library extraction
Storybook integration
Final Thoughts
If you are on Angular 21, this is the correct way to combine Angular Material and Tailwind CSS.
Anything else is either:
Legacy
Fragile
Or already deprecated
This setup will remain stable for years.