diff --git a/guides/theming.md b/guides/theming.md
index c9610de48f1b..038f87981c2f 100644
--- a/guides/theming.md
+++ b/guides/theming.md
@@ -48,6 +48,73 @@ html {
}
```
+### CSS cascade layers
+
+Angular Material ships all component CSS inside the `angular-material`
+[cascade layer](https://www.w3.org/TR/css-cascade-5/#layering). This lets
+applications control where Material sits in the cascade relative to resets,
+CDK styles, and utility frameworks such as Tailwind — without resorting to
+extra specificity or `!important`. See also
+[MDN: Cascade layers](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer).
+
+Component styles are layered automatically at build time — no action is
+needed for component CSS. Prebuilt themes are also shipped pre-layered.
+
+**Layer order.** In Sass, `@use` must come before any other rules. After your
+`@use` lines, declare the order of named layers:
+
+```scss
+@use '@angular/material' as mat;
+
+@layer base, cdk-resets, cdk-overlay, angular-material, components, utilities;
+```
+
+If your app uses CDK overlay or drag-drop resets, include their published
+layer names (`cdk-overlay`, `cdk-resets`) so their priority is predictable.
+
+**Tailwind CSS.** Placing `angular-material` before `utilities` allows
+Tailwind utility classes to override Material styles at equal specificity.
+Put Material `@use` and your layer prelude before Tailwind’s `@tailwind`
+directives (Tailwind’s own docs describe layer setup for your stack):
+
+```scss
+@use '@angular/material' as mat;
+
+@layer base, cdk-resets, cdk-overlay, angular-material, components, utilities;
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+```
+
+**Theme output.** Wrap `mat.theme` in `mat.theme-layer` so the generated
+CSS custom properties land in the same `angular-material` layer as the
+component styles. You can also use the same mixin around other Angular
+Material theme output such as `all-component-themes` or individual
+component theme mixins:
+
+```scss
+@use '@angular/material' as mat;
+
+@layer base, cdk-resets, cdk-overlay, angular-material, components, utilities;
+
+@include mat.theme-layer {
+ html {
+ @include mat.theme((
+ color: mat.$violet-palette,
+ typography: Roboto,
+ density: 0,
+ ));
+ }
+}
+```
+
+**Cascade note.** In the CSS Cascade Level 5 model, unlayered author
+styles have higher priority than any named layer. Plain CSS rules can
+therefore override layered Material at equal specificity. Overrides that
+live inside named layers must appear after `angular-material` in the
+`@layer` order list, or use higher specificity / `!important` as usual.
+
You can use the following styles to apply the theme’s surface background and
on-surface text colors as a default across your application:
diff --git a/src/e2e-app/BUILD.bazel b/src/e2e-app/BUILD.bazel
index 51e091c66d12..75637beae19f 100644
--- a/src/e2e-app/BUILD.bazel
+++ b/src/e2e-app/BUILD.bazel
@@ -43,6 +43,7 @@ ng_project(
"//src/cdk/scrolling",
"//src/cdk/testing/tests:test_components",
"//src/components-examples/private",
+ "//src/material/button",
"//src/material/core",
"//src/material/slider",
],
diff --git a/src/e2e-app/components/e2e-app/e2e-app.ts b/src/e2e-app/components/e2e-app/e2e-app.ts
index 581b9550607b..79c49b98a9f7 100644
--- a/src/e2e-app/components/e2e-app/e2e-app.ts
+++ b/src/e2e-app/components/e2e-app/e2e-app.ts
@@ -16,6 +16,7 @@ export class E2eApp {
{path: 'block-scroll-strategy', title: 'Block Scroll Strategy'},
{path: 'component-harness', title: 'Component Harness'},
{path: 'slider', title: 'Slider'},
+ {path: 'tailwind-layer', title: 'Tailwind Layer'},
{path: 'virtual-scroll', title: 'Virtual Scroll'},
];
}
diff --git a/src/e2e-app/components/tailwind-layer-e2e.ts b/src/e2e-app/components/tailwind-layer-e2e.ts
new file mode 100644
index 000000000000..1618e9e12fab
--- /dev/null
+++ b/src/e2e-app/components/tailwind-layer-e2e.ts
@@ -0,0 +1,31 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {ChangeDetectionStrategy, Component} from '@angular/core';
+import {MatButtonModule} from '@angular/material/button';
+
+@Component({
+ selector: 'tailwind-layer-e2e',
+ template: `
+
+
+ `,
+ imports: [MatButtonModule],
+ changeDetection: ChangeDetectionStrategy.Eager,
+})
+export class TailwindLayerE2e {}
diff --git a/src/e2e-app/main.ts b/src/e2e-app/main.ts
index ee0d1e790bc5..dbd41ae89adb 100644
--- a/src/e2e-app/main.ts
+++ b/src/e2e-app/main.ts
@@ -7,6 +7,7 @@ import {ComponentHarnessE2e} from './components/component-harness-e2e';
import {E2eApp} from './components/e2e-app/e2e-app';
import {Home} from './components/home';
import {SliderE2e} from './components/slider-e2e';
+import {TailwindLayerE2e} from './components/tailwind-layer-e2e';
import {VirtualScrollE2E} from './components/virtual-scroll/virtual-scroll-e2e';
enableProdMode();
@@ -19,6 +20,7 @@ bootstrapApplication(E2eApp, {
{path: 'block-scroll-strategy', component: BlockScrollStrategyE2E},
{path: 'component-harness', component: ComponentHarnessE2e},
{path: 'slider', component: SliderE2e},
+ {path: 'tailwind-layer', component: TailwindLayerE2e},
{path: 'virtual-scroll', component: VirtualScrollE2E},
]),
provideZoneChangeDetection(),
diff --git a/src/e2e-app/theme.scss b/src/e2e-app/theme.scss
index d794f4750e68..f22617945611 100644
--- a/src/e2e-app/theme.scss
+++ b/src/e2e-app/theme.scss
@@ -1,5 +1,7 @@
@use '@angular/material' as mat;
+@layer base, angular-material, utilities;
+
$theme: mat.define-theme((
color: (
theme-type: light,
@@ -11,8 +13,21 @@ $theme: mat.define-theme((
)
));
-html {
- @include mat.all-component-themes($theme);
+@include mat.theme-layer {
+ html {
+ @include mat.all-component-themes($theme);
+ }
+
+ @include mat.typography-hierarchy($theme);
}
-@include mat.typography-hierarchy($theme);
+@layer utilities {
+ .tw-bg-lime-500 {
+ background-color: rgb(132, 204, 22);
+ }
+}
+
+// Control utility that is intentionally unlayered.
+.tw-bg-fuchsia-unlayered {
+ background-color: rgb(217, 70, 239);
+}
diff --git a/src/material-experimental/menubar/BUILD.bazel b/src/material-experimental/menubar/BUILD.bazel
index 8556339b26a2..6a90bd59f45f 100644
--- a/src/material-experimental/menubar/BUILD.bazel
+++ b/src/material-experimental/menubar/BUILD.bazel
@@ -32,11 +32,13 @@ sass_library(
sass_binary(
name = "menubar_scss",
src = "menubar.scss",
+ layer = "angular-material",
)
sass_binary(
name = "menubar_item_scss",
src = "menubar-item.scss",
+ layer = "angular-material",
)
ng_project(
diff --git a/src/material-experimental/selection/BUILD.bazel b/src/material-experimental/selection/BUILD.bazel
index 2cc414f9276d..15cec99036e3 100644
--- a/src/material-experimental/selection/BUILD.bazel
+++ b/src/material-experimental/selection/BUILD.bazel
@@ -27,4 +27,5 @@ sass_library(
sass_binary(
name = "selection_column_scss",
src = "selection-column.scss",
+ layer = "angular-material",
)
diff --git a/src/material/BUILD.bazel b/src/material/BUILD.bazel
index 4640784669a3..22bea3c42e8f 100644
--- a/src/material/BUILD.bazel
+++ b/src/material/BUILD.bazel
@@ -54,6 +54,7 @@ sass_library(
"//src/material/core/selection/pseudo-checkbox:_pseudo_checkbox_common",
"//src/material/core/selection/pseudo-checkbox:sass_theme",
"//src/material/core/style:_validation",
+ "//src/material/core/style:cascade_layers",
"//src/material/core/style:elevation",
"//src/material/core/style:private",
"//src/material/core/style:sass_utils",
@@ -130,6 +131,7 @@ ng_package(
"//src/material/core/selection/pseudo-checkbox:_pseudo_checkbox_common",
"//src/material/core/selection/pseudo-checkbox:sass_theme",
"//src/material/core/style:_validation",
+ "//src/material/core/style:cascade_layers",
"//src/material/core/style:elevation",
"//src/material/core/style:private",
"//src/material/core/style:sass_utils",
diff --git a/src/material/_index.scss b/src/material/_index.scss
index dfdf3738c594..76fa9ab9061e 100644
--- a/src/material/_index.scss
+++ b/src/material/_index.scss
@@ -33,6 +33,7 @@
$private-ease-in-out-curve-function, $private-swift-ease-out-duration, $private-xsmall;
@forward './core/style/sass-utils' as private-*;
@forward './core/style/validation' as private-*;
+@forward './core/style/cascade-layers' show theme-layer;
// Structural
@forward './core/core' show core, app-background, elevation-classes;
diff --git a/src/material/autocomplete/BUILD.bazel b/src/material/autocomplete/BUILD.bazel
index 44dea63a861f..482d8f6cebc9 100644
--- a/src/material/autocomplete/BUILD.bazel
+++ b/src/material/autocomplete/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "autocomplete.scss",
+ layer = "angular-material",
deps = [
":m2",
":m3",
diff --git a/src/material/badge/BUILD.bazel b/src/material/badge/BUILD.bazel
index 2943c37a634a..0a9cef7d097f 100644
--- a/src/material/badge/BUILD.bazel
+++ b/src/material/badge/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "badge_css",
src = "badge.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/bottom-sheet/BUILD.bazel b/src/material/bottom-sheet/BUILD.bazel
index e7c5e6a32b10..14b69fe72d09 100644
--- a/src/material/bottom-sheet/BUILD.bazel
+++ b/src/material/bottom-sheet/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "bottom-sheet-container.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/button-toggle/BUILD.bazel b/src/material/button-toggle/BUILD.bazel
index ef5093e44c5a..0f0beb307cc9 100644
--- a/src/material/button-toggle/BUILD.bazel
+++ b/src/material/button-toggle/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "button-toggle.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/button/BUILD.bazel b/src/material/button/BUILD.bazel
index 9671cabd541c..3bb1e6bf510b 100644
--- a/src/material/button/BUILD.bazel
+++ b/src/material/button/BUILD.bazel
@@ -84,6 +84,7 @@ sass_library(
sass_binary(
name = "icon_button_css",
src = "icon-button.scss",
+ layer = "angular-material",
deps = [
":base_lib",
":m2",
@@ -96,6 +97,7 @@ sass_binary(
sass_binary(
name = "fab_css",
src = "fab.scss",
+ layer = "angular-material",
deps = [
":base_lib",
":m2",
@@ -109,6 +111,7 @@ sass_binary(
sass_binary(
name = "button_high_contrast",
src = "button-high-contrast.scss",
+ layer = "angular-material",
deps = [
"//src/cdk:sass_lib",
],
@@ -128,6 +131,7 @@ sass_library(
sass_binary(
name = "css",
src = "button.scss",
+ layer = "angular-material",
deps = [
":base_lib",
":m2",
diff --git a/src/material/card/BUILD.bazel b/src/material/card/BUILD.bazel
index de75a8684e6a..20d2793c5954 100644
--- a/src/material/card/BUILD.bazel
+++ b/src/material/card/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "card.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/material/core/tokens:token_utils",
diff --git a/src/material/checkbox/BUILD.bazel b/src/material/checkbox/BUILD.bazel
index 5eb1f4c0f456..8476190c1abd 100644
--- a/src/material/checkbox/BUILD.bazel
+++ b/src/material/checkbox/BUILD.bazel
@@ -64,6 +64,7 @@ sass_library(
sass_binary(
name = "css",
src = "checkbox.scss",
+ layer = "angular-material",
deps = [
":checkbox_common",
":m2",
diff --git a/src/material/chips/BUILD.bazel b/src/material/chips/BUILD.bazel
index 0146ffcfaa3e..b4e9553979b5 100644
--- a/src/material/chips/BUILD.bazel
+++ b/src/material/chips/BUILD.bazel
@@ -52,6 +52,7 @@ sass_library(
sass_binary(
name = "chip_css",
src = "chip.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
@@ -65,6 +66,7 @@ sass_binary(
sass_binary(
name = "chip_set_css",
src = "chip-set.scss",
+ layer = "angular-material",
deps = ["//src/material/core/style:vendor_prefixes"],
)
diff --git a/src/material/core/BUILD.bazel b/src/material/core/BUILD.bazel
index 87aaf7d74341..2d5b9e44b727 100644
--- a/src/material/core/BUILD.bazel
+++ b/src/material/core/BUILD.bazel
@@ -203,6 +203,7 @@ sass_library(
sass_binary(
name = "ripple_structure",
src = "ripple/ripple-structure.scss",
+ layer = "angular-material",
deps = [
":ripple_sass",
],
diff --git a/src/material/core/focus-indicators/BUILD.bazel b/src/material/core/focus-indicators/BUILD.bazel
index 17495d4bbdaf..c8ddaa5b997c 100644
--- a/src/material/core/focus-indicators/BUILD.bazel
+++ b/src/material/core/focus-indicators/BUILD.bazel
@@ -25,5 +25,6 @@ ng_project(
sass_binary(
name = "structural_styles_css",
src = "structural-styles.scss",
+ layer = "angular-material",
deps = [":focus-indicators"],
)
diff --git a/src/material/core/internal-form-field/BUILD.bazel b/src/material/core/internal-form-field/BUILD.bazel
index 032b2f748a53..cab3abc55c8d 100644
--- a/src/material/core/internal-form-field/BUILD.bazel
+++ b/src/material/core/internal-form-field/BUILD.bazel
@@ -16,5 +16,6 @@ ng_project(
sass_binary(
name = "internal_form_field_css",
src = "internal-form-field.scss",
+ layer = "angular-material",
deps = ["//src/material/core/style:vendor_prefixes"],
)
diff --git a/src/material/core/option/BUILD.bazel b/src/material/core/option/BUILD.bazel
index db193878f52d..ffd610e153b3 100644
--- a/src/material/core/option/BUILD.bazel
+++ b/src/material/core/option/BUILD.bazel
@@ -77,6 +77,7 @@ ng_project(
sass_binary(
name = "option_css",
src = "option.scss",
+ layer = "angular-material",
deps = [
"//src/cdk:sass_lib",
"//src/material/core/style:layout_common",
@@ -88,6 +89,7 @@ sass_binary(
sass_binary(
name = "optgroup_css",
src = "optgroup.scss",
+ layer = "angular-material",
deps = [
"//src/material/core/tokens:token_utils",
],
diff --git a/src/material/core/selection/pseudo-checkbox/BUILD.bazel b/src/material/core/selection/pseudo-checkbox/BUILD.bazel
index 744a69374472..cc3fe7923ab5 100644
--- a/src/material/core/selection/pseudo-checkbox/BUILD.bazel
+++ b/src/material/core/selection/pseudo-checkbox/BUILD.bazel
@@ -66,6 +66,7 @@ sass_library(
sass_binary(
name = "css",
src = "pseudo-checkbox.scss",
+ layer = "angular-material",
deps = [
":_pseudo_checkbox_common",
"//src/material/core/style:checkbox_common",
diff --git a/src/material/core/style/BUILD.bazel b/src/material/core/style/BUILD.bazel
index 959dc5a7c22f..895ca3083288 100644
--- a/src/material/core/style/BUILD.bazel
+++ b/src/material/core/style/BUILD.bazel
@@ -67,6 +67,11 @@ sass_library(
srcs = ["_sass-utils.scss"],
)
+sass_library(
+ name = "cascade_layers",
+ srcs = ["_cascade-layers.scss"],
+)
+
sass_library(
name = "_validation",
srcs = ["_validation.scss"],
diff --git a/src/material/core/style/_cascade-layers.scss b/src/material/core/style/_cascade-layers.scss
new file mode 100644
index 000000000000..7b21a6f9f4ba
--- /dev/null
+++ b/src/material/core/style/_cascade-layers.scss
@@ -0,0 +1,14 @@
+// Angular Material uses a fixed layer name so shipped component CSS and Sass-emitted theme
+// output can participate in the same app-authored @layer ordering.
+$private-layer-name: angular-material;
+
+/// Wraps emitted CSS in Angular Material's cascade layer.
+///
+/// Use this around Angular Material theme mixins such as `mat.theme`,
+/// `mat.all-component-themes`, or individual component theme mixins when you want their
+/// output to align with the layer used by Angular Material's shipped component CSS.
+@mixin theme-layer {
+ @layer #{$private-layer-name} {
+ @content;
+ }
+}
diff --git a/src/material/core/theming/prebuilt/BUILD.bazel b/src/material/core/theming/prebuilt/BUILD.bazel
index f631700a2031..eb3158f6061e 100644
--- a/src/material/core/theming/prebuilt/BUILD.bazel
+++ b/src/material/core/theming/prebuilt/BUILD.bazel
@@ -5,6 +5,7 @@ package(default_visibility = ["//visibility:public"])
sass_binary(
name = "indigo-pink-theme",
src = "indigo-pink.scss",
+ layer = "angular-material",
deps = [
"//src/material/core:core_sass",
"//src/material/core/m2:m2_sass",
@@ -16,6 +17,7 @@ sass_binary(
sass_binary(
name = "deeppurple-amber-theme",
src = "deeppurple-amber.scss",
+ layer = "angular-material",
deps = [
"//src/material/core:core_sass",
"//src/material/core/m2:m2_sass",
@@ -27,6 +29,7 @@ sass_binary(
sass_binary(
name = "pink-bluegrey-theme",
src = "pink-bluegrey.scss",
+ layer = "angular-material",
deps = [
"//src/material/core:core_sass",
"//src/material/core/m2:m2_sass",
@@ -38,6 +41,7 @@ sass_binary(
sass_binary(
name = "purple-green-theme",
src = "purple-green.scss",
+ layer = "angular-material",
deps = [
"//src/material/core:core_sass",
"//src/material/core/m2:m2_sass",
@@ -49,6 +53,7 @@ sass_binary(
sass_binary(
name = "rose_red",
src = "rose-red.scss",
+ layer = "angular-material",
deps = [
"//src/material/core/theming:_palettes",
"//src/material/core/tokens:system",
@@ -58,6 +63,7 @@ sass_binary(
sass_binary(
name = "azure_blue",
src = "azure-blue.scss",
+ layer = "angular-material",
deps = [
"//src/material/core/theming:_palettes",
"//src/material/core/tokens:system",
@@ -67,6 +73,7 @@ sass_binary(
sass_binary(
name = "cyan_orange",
src = "cyan-orange.scss",
+ layer = "angular-material",
deps = [
"//src/material/core/theming:_palettes",
"//src/material/core/tokens:system",
@@ -76,6 +83,7 @@ sass_binary(
sass_binary(
name = "magenta_violet",
src = "magenta-violet.scss",
+ layer = "angular-material",
deps = [
"//src/material/core/theming:_palettes",
"//src/material/core/tokens:system",
diff --git a/src/material/core/theming/tests/BUILD.bazel b/src/material/core/theming/tests/BUILD.bazel
index 49dd08d74b0b..1b52996817ba 100644
--- a/src/material/core/theming/tests/BUILD.bazel
+++ b/src/material/core/theming/tests/BUILD.bazel
@@ -52,6 +52,13 @@ sass_binary(
deps = ["//src/material:sass_lib"],
)
+sass_binary(
+ name = "test-cascade-layers",
+ testonly = True,
+ src = "test-cascade-layers.scss",
+ deps = ["//src/material:sass_lib"],
+)
+
build_test(
name = "sass_compile_tests",
targets = [
@@ -59,6 +66,7 @@ build_test(
":test-theming-api",
":test-theming-bundle",
":test-typography-font-family",
+ ":test-cascade-layers",
],
)
@@ -83,6 +91,8 @@ jasmine_test(
":unit_test_lib",
"//src/material:sass_lib",
"//src/material-experimental:sass_lib",
+ "//src/material/button:css",
+ "//src/material/prebuilt-themes:azure-blue",
],
no_copy_to_bin = [
"//src/material:sass_lib",
diff --git a/src/material/core/theming/tests/cascade-layers.spec.ts b/src/material/core/theming/tests/cascade-layers.spec.ts
new file mode 100644
index 000000000000..935cd2172469
--- /dev/null
+++ b/src/material/core/theming/tests/cascade-layers.spec.ts
@@ -0,0 +1,79 @@
+import {runfiles} from '@bazel/runfiles';
+import * as fs from 'fs';
+import * as path from 'path';
+import {compileString} from 'sass';
+
+import {createLocalAngularPackageImporter} from '../../../../../tools/sass/local-sass-importer.js';
+
+const testDir = path.join(runfiles.resolvePackageRelative('../_all-theme.scss'), '../tests');
+const packagesDir = path.join(runfiles.resolveWorkspaceRelative('src/cdk/_index.scss'), '../..');
+const localPackageSassImporter = createLocalAngularPackageImporter(packagesDir);
+
+function transpile(content: string) {
+ return compileString(
+ `
+ @use '../../../index' as mat;
+
+ $theme: mat.define-theme();
+
+ ${content}
+ `,
+ {
+ loadPaths: [testDir],
+ importers: [localPackageSassImporter],
+ },
+ ).css.toString();
+}
+
+describe('CSS cascade layers', () => {
+ it('wraps mat.theme with mat.theme-layer', () => {
+ const css = transpile(`
+ @include mat.theme-layer {
+ html {
+ @include mat.theme((
+ color: (
+ theme-type: light,
+ primary: mat.$violet-palette,
+ ),
+ typography: Roboto,
+ density: 0,
+ ));
+ }
+ }
+ `);
+ expect(css).toContain('@layer angular-material');
+ });
+
+ it('does not emit @layer when theme-layer is omitted', () => {
+ const css = transpile(`
+ html {
+ @include mat.theme((
+ color: (
+ theme-type: light,
+ primary: mat.$violet-palette,
+ ),
+ typography: Roboto,
+ density: 0,
+ ));
+ }
+ `);
+ expect(css).not.toContain('@layer angular-material');
+ });
+
+ it('wraps compiled component CSS in the angular-material layer', () => {
+ const css = fs.readFileSync(
+ runfiles.resolveWorkspaceRelative('src/material/button/button.css'),
+ 'utf8',
+ );
+ expect(css).toMatch(/^@layer angular-material \{/);
+ expect(css).toContain('.mat-mdc-button-base');
+ });
+
+ it('wraps prebuilt theme CSS in the angular-material layer', () => {
+ const css = fs.readFileSync(
+ runfiles.resolveWorkspaceRelative('src/material/prebuilt-themes/azure-blue.css'),
+ 'utf8',
+ );
+ expect(css).toMatch(/^@layer angular-material \{/);
+ });
+});
diff --git a/src/material/core/theming/tests/test-cascade-layers.scss b/src/material/core/theming/tests/test-cascade-layers.scss
new file mode 100644
index 000000000000..4cd6fe7b4975
--- /dev/null
+++ b/src/material/core/theming/tests/test-cascade-layers.scss
@@ -0,0 +1,16 @@
+@use '../../../index' as mat;
+
+$theme: mat.define-theme();
+
+@include mat.theme-layer {
+ html {
+ @include mat.theme((
+ color: (
+ theme-type: light,
+ primary: mat.$violet-palette,
+ ),
+ typography: Roboto,
+ density: 0,
+ ));
+ }
+}
diff --git a/src/material/datepicker/BUILD.bazel b/src/material/datepicker/BUILD.bazel
index af9de2eed2e7..62da920a6365 100644
--- a/src/material/datepicker/BUILD.bazel
+++ b/src/material/datepicker/BUILD.bazel
@@ -63,6 +63,7 @@ sass_library(
sass_binary(
name = "datepicker_content_css",
src = "datepicker-content.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/material/core/tokens:token_utils",
@@ -72,6 +73,7 @@ sass_binary(
sass_binary(
name = "calendar_css",
src = "calendar.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
@@ -83,6 +85,7 @@ sass_binary(
sass_binary(
name = "calendar_body_css",
src = "calendar-body.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
@@ -94,6 +97,7 @@ sass_binary(
sass_binary(
name = "datepicker_toggle_css",
src = "datepicker-toggle.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
@@ -104,11 +108,13 @@ sass_binary(
sass_binary(
name = "datepicker_actions_css",
src = "datepicker-actions.scss",
+ layer = "angular-material",
)
sass_binary(
name = "date_range_input_css",
src = "date-range-input.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/dialog/BUILD.bazel b/src/material/dialog/BUILD.bazel
index fdeb092c536a..5a2c1e6fc8bc 100644
--- a/src/material/dialog/BUILD.bazel
+++ b/src/material/dialog/BUILD.bazel
@@ -60,6 +60,7 @@ sass_library(
sass_binary(
name = "css",
src = "dialog.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/divider/BUILD.bazel b/src/material/divider/BUILD.bazel
index 30ef52c68430..4c7d8009ea50 100644
--- a/src/material/divider/BUILD.bazel
+++ b/src/material/divider/BUILD.bazel
@@ -55,6 +55,7 @@ sass_library(
sass_binary(
name = "css",
src = "divider.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/material/core/tokens:token_utils",
diff --git a/src/material/expansion/BUILD.bazel b/src/material/expansion/BUILD.bazel
index 7c3ad0c29e93..f092626f0c01 100644
--- a/src/material/expansion/BUILD.bazel
+++ b/src/material/expansion/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "expansion-panel.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
@@ -65,6 +66,7 @@ sass_binary(
sass_binary(
name = "header_css",
src = "expansion-panel-header.scss",
+ layer = "angular-material",
deps = [
":m2",
":variables",
diff --git a/src/material/form-field/BUILD.bazel b/src/material/form-field/BUILD.bazel
index 9d2bbecd71f1..69e494e19fdd 100644
--- a/src/material/form-field/BUILD.bazel
+++ b/src/material/form-field/BUILD.bazel
@@ -52,6 +52,7 @@ sass_library(
sass_binary(
name = "css",
src = "form-field.scss",
+ layer = "angular-material",
deps = [
":form_field_partials",
"//src/material/core/style:vendor_prefixes",
diff --git a/src/material/grid-list/BUILD.bazel b/src/material/grid-list/BUILD.bazel
index 7e32331c766d..cd285ba24405 100644
--- a/src/material/grid-list/BUILD.bazel
+++ b/src/material/grid-list/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "grid-list.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/material/core/style:layout_common",
diff --git a/src/material/icon/BUILD.bazel b/src/material/icon/BUILD.bazel
index 200496244769..c571a00d94af 100644
--- a/src/material/icon/BUILD.bazel
+++ b/src/material/icon/BUILD.bazel
@@ -52,6 +52,7 @@ sass_library(
sass_binary(
name = "css",
src = "icon.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/material/core/style:vendor_prefixes",
diff --git a/src/material/list/BUILD.bazel b/src/material/list/BUILD.bazel
index 7f574e3e9b66..b18f8fe99a0b 100644
--- a/src/material/list/BUILD.bazel
+++ b/src/material/list/BUILD.bazel
@@ -55,6 +55,7 @@ sass_library(
sass_binary(
name = "css",
src = "list.scss",
+ layer = "angular-material",
deps = [
":list_inherited_structure",
":list_item_hcm_indicator",
@@ -83,6 +84,7 @@ sass_library(
sass_binary(
name = "option_css",
src = "list-option.scss",
+ layer = "angular-material",
deps = [
":list_item_hcm_indicator",
"//src/material/checkbox:checkbox_common",
diff --git a/src/material/menu/BUILD.bazel b/src/material/menu/BUILD.bazel
index 9b8ab63420f1..039b139fb79b 100644
--- a/src/material/menu/BUILD.bazel
+++ b/src/material/menu/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "menu.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/paginator/BUILD.bazel b/src/material/paginator/BUILD.bazel
index a685da54f71e..9185c5511a3f 100644
--- a/src/material/paginator/BUILD.bazel
+++ b/src/material/paginator/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "paginator.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/progress-bar/BUILD.bazel b/src/material/progress-bar/BUILD.bazel
index 7cb4f1c4b4e0..3f43b0dd1860 100644
--- a/src/material/progress-bar/BUILD.bazel
+++ b/src/material/progress-bar/BUILD.bazel
@@ -52,6 +52,7 @@ sass_library(
sass_binary(
name = "css",
src = "progress-bar.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/progress-spinner/BUILD.bazel b/src/material/progress-spinner/BUILD.bazel
index d079b0679711..495609ceb435 100644
--- a/src/material/progress-spinner/BUILD.bazel
+++ b/src/material/progress-spinner/BUILD.bazel
@@ -52,6 +52,7 @@ sass_library(
sass_binary(
name = "css",
src = "progress-spinner.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/radio/BUILD.bazel b/src/material/radio/BUILD.bazel
index 72a82b87f39e..f280f3e9248c 100644
--- a/src/material/radio/BUILD.bazel
+++ b/src/material/radio/BUILD.bazel
@@ -63,6 +63,7 @@ sass_library(
sass_binary(
name = "css",
src = "radio.scss",
+ layer = "angular-material",
deps = [
":m2",
":radio_common",
diff --git a/src/material/schematics/ng-add/index.spec.ts b/src/material/schematics/ng-add/index.spec.ts
index 58977ad01a32..0db6f472183f 100644
--- a/src/material/schematics/ng-add/index.spec.ts
+++ b/src/material/schematics/ng-add/index.spec.ts
@@ -107,6 +107,8 @@ describe('ng-add schematic', () => {
expect(themeContent).toContain(`@use '@angular/material' as mat;`);
expect(themeContent).toContain(`@include mat.theme((`);
+ expect(themeContent).toContain(`@layer base, cdk-resets, cdk-overlay, angular-material`);
+ expect(themeContent).toContain(`@include mat.theme-layer`);
});
it('should create a custom theme file if no SCSS file could be found', async () => {
diff --git a/src/material/schematics/ng-add/theming/create-theme.ts b/src/material/schematics/ng-add/theming/create-theme.ts
index fb25eb8eb010..70555a145f92 100644
--- a/src/material/schematics/ng-add/theming/create-theme.ts
+++ b/src/material/schematics/ng-add/theming/create-theme.ts
@@ -16,22 +16,29 @@ export function createTheme(userPaletteChoice: string): string {
]);
return `
// Include theming for Angular Material with \`mat.theme()\`.
-// This Sass mixin will define CSS variables that are used for styling Angular Material
-// components according to the Material 3 design spec.
-// Learn more about theming and how to use it for your application's
-// custom components at https://material.angular.dev/guide/theming
+// \`@use\` must come before any other rules (including \`@layer\`).
@use '@angular/material' as mat;
-html {
- height: 100%;
- @include mat.theme((
- color: (
- primary: mat.$${colorPalettes.get(userPaletteChoice)!.primary}-palette,
- tertiary: mat.$${colorPalettes.get(userPaletteChoice)!.tertiary}-palette,
- ),
- typography: Roboto,
- density: 0,
- ));
+// Cascade layer ordering. Angular Material component styles ship in the
+// \`angular-material\` layer. Declaring layer order here makes overrides
+// predictable alongside CDK styles and utility frameworks.
+// Learn more: https://material.angular.dev/guide/theming#css-cascade-layers
+@layer base, cdk-resets, cdk-overlay, angular-material, components, utilities;
+
+// Wrapping \`mat.theme()\` in \`mat.theme-layer\` places generated CSS
+// custom properties in the same \`angular-material\` layer as component styles.
+@include mat.theme-layer {
+ html {
+ height: 100%;
+ @include mat.theme((
+ color: (
+ primary: mat.$${colorPalettes.get(userPaletteChoice)!.primary}-palette,
+ tertiary: mat.$${colorPalettes.get(userPaletteChoice)!.tertiary}-palette,
+ ),
+ typography: Roboto,
+ density: 0,
+ ));
+ }
}
body {
diff --git a/src/material/select/BUILD.bazel b/src/material/select/BUILD.bazel
index a0322c99128f..6930fd7daad7 100644
--- a/src/material/select/BUILD.bazel
+++ b/src/material/select/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "select.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/sidenav/BUILD.bazel b/src/material/sidenav/BUILD.bazel
index 12b246015cc9..7d12c7587bca 100644
--- a/src/material/sidenav/BUILD.bazel
+++ b/src/material/sidenav/BUILD.bazel
@@ -52,6 +52,7 @@ sass_library(
sass_binary(
name = "css",
src = "drawer.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/slide-toggle/BUILD.bazel b/src/material/slide-toggle/BUILD.bazel
index 670051f1243c..8a762d192ba3 100644
--- a/src/material/slide-toggle/BUILD.bazel
+++ b/src/material/slide-toggle/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "slide-toggle.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/slider/BUILD.bazel b/src/material/slider/BUILD.bazel
index e8cefdaac781..67b8cfbb0ee2 100644
--- a/src/material/slider/BUILD.bazel
+++ b/src/material/slider/BUILD.bazel
@@ -55,6 +55,7 @@ sass_library(
sass_binary(
name = "slider-css",
src = "slider.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
@@ -66,6 +67,7 @@ sass_binary(
sass_binary(
name = "slider-thumb-css",
src = "slider-thumb.scss",
+ layer = "angular-material",
)
ng_project(
diff --git a/src/material/slider/tailwind-layer.e2e.spec.ts b/src/material/slider/tailwind-layer.e2e.spec.ts
new file mode 100644
index 000000000000..0c7b64c2231c
--- /dev/null
+++ b/src/material/slider/tailwind-layer.e2e.spec.ts
@@ -0,0 +1,35 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {browser, by, element} from 'protractor';
+
+describe('Tailwind layer + Angular Material E2E', () => {
+ beforeEach(async () => await browser.get('/tailwind-layer'));
+
+ it('allows utility layer styles to override Angular Material styles', async () => {
+ const button = element(by.id('tailwind-utility-button'));
+ const backgroundColor = await browser.executeScript(
+ 'return getComputedStyle(arguments[0]).backgroundColor;',
+ button.getWebElement(),
+ );
+
+ // Tailwind's lime-500 utility value.
+ expect(backgroundColor).toBe('rgb(132, 204, 22)');
+ });
+
+ it('also allows unlayered utility styles to override layered Material styles', async () => {
+ const button = element(by.id('unlayered-utility-button'));
+ const backgroundColor = await browser.executeScript(
+ 'return getComputedStyle(arguments[0]).backgroundColor;',
+ button.getWebElement(),
+ );
+
+ // Unlayered author styles outrank named layers in Cascade 5.
+ expect(backgroundColor).toBe('rgb(217, 70, 239)');
+ });
+});
diff --git a/src/material/snack-bar/BUILD.bazel b/src/material/snack-bar/BUILD.bazel
index 9908896fdc62..87307ff005cd 100644
--- a/src/material/snack-bar/BUILD.bazel
+++ b/src/material/snack-bar/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "container_css",
src = "snack-bar-container.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
@@ -64,6 +65,7 @@ sass_binary(
sass_binary(
name = "css",
src = "simple-snack-bar.scss",
+ layer = "angular-material",
)
ng_project(
diff --git a/src/material/sort/BUILD.bazel b/src/material/sort/BUILD.bazel
index f7508bf87e58..e3a568b6a49a 100644
--- a/src/material/sort/BUILD.bazel
+++ b/src/material/sort/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "sort-header.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/material/core/focus-indicators",
diff --git a/src/material/stepper/BUILD.bazel b/src/material/stepper/BUILD.bazel
index 362a7aa08934..777ea52765de 100644
--- a/src/material/stepper/BUILD.bazel
+++ b/src/material/stepper/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "stepper.scss",
+ layer = "angular-material",
deps = [
":m2",
":variables",
@@ -64,6 +65,7 @@ sass_binary(
sass_binary(
name = "header_css",
src = "step-header.scss",
+ layer = "angular-material",
deps = [
":m2",
":variables",
diff --git a/src/material/table/BUILD.bazel b/src/material/table/BUILD.bazel
index 67b096982285..c2c2577c94ed 100644
--- a/src/material/table/BUILD.bazel
+++ b/src/material/table/BUILD.bazel
@@ -60,6 +60,7 @@ sass_library(
sass_binary(
name = "css",
src = "table.scss",
+ layer = "angular-material",
deps = [
":flex_sass",
":m2",
diff --git a/src/material/tabs/BUILD.bazel b/src/material/tabs/BUILD.bazel
index 8096c7c9d398..271f84b7e565 100644
--- a/src/material/tabs/BUILD.bazel
+++ b/src/material/tabs/BUILD.bazel
@@ -65,6 +65,7 @@ sass_library(
sass_binary(
name = "tab_group_css",
src = "tab-group.scss",
+ layer = "angular-material",
deps = [
":m2",
":sass",
@@ -76,6 +77,7 @@ sass_binary(
sass_binary(
name = "tab_nav_bar_css",
src = "tab-nav-bar/tab-nav-bar.scss",
+ layer = "angular-material",
deps = [
":sass",
],
@@ -84,6 +86,7 @@ sass_binary(
sass_binary(
name = "tab_link_css",
src = "tab-nav-bar/tab-link.scss",
+ layer = "angular-material",
deps = [
":sass",
"//src/material/core/style:variables",
@@ -93,6 +96,7 @@ sass_binary(
sass_binary(
name = "tab_header_css",
src = "tab-header.scss",
+ layer = "angular-material",
deps = [
":sass",
"//src/cdk:sass_lib",
@@ -102,6 +106,7 @@ sass_binary(
sass_binary(
name = "tab_body_css",
src = "tab-body.scss",
+ layer = "angular-material",
deps = ["//src/material/core/style:layout_common"],
)
diff --git a/src/material/timepicker/BUILD.bazel b/src/material/timepicker/BUILD.bazel
index 1e8bcf2457ad..3e4a157f898d 100644
--- a/src/material/timepicker/BUILD.bazel
+++ b/src/material/timepicker/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "timepicker_css",
src = "timepicker.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/toolbar/BUILD.bazel b/src/material/toolbar/BUILD.bazel
index d52db6a50a6b..d5340b6cb7df 100644
--- a/src/material/toolbar/BUILD.bazel
+++ b/src/material/toolbar/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "toolbar.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
diff --git a/src/material/tooltip/BUILD.bazel b/src/material/tooltip/BUILD.bazel
index 5ff2539e1993..de4daf8af6b9 100644
--- a/src/material/tooltip/BUILD.bazel
+++ b/src/material/tooltip/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "tooltip.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/material/core/tokens:token_utils",
diff --git a/src/material/tree/BUILD.bazel b/src/material/tree/BUILD.bazel
index d727de28c820..b599d88379fa 100644
--- a/src/material/tree/BUILD.bazel
+++ b/src/material/tree/BUILD.bazel
@@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "tree.scss",
+ layer = "angular-material",
deps = [
":m2",
"//src/material/core/tokens:token_utils",
diff --git a/tools/defaults.bzl b/tools/defaults.bzl
index 232d63d84443..87215a851ff3 100644
--- a/tools/defaults.bzl
+++ b/tools/defaults.bzl
@@ -29,17 +29,61 @@ spec_bundle = _spec_bundle
http_server = _http_server
ng_examples_db = _ng_examples_db
-def sass_binary(sourcemap = False, include_paths = [], **kwargs):
- _sass_binary(
- sourcemap = sourcemap,
- include_paths = include_paths,
- module_mappings = {
- "@angular/cdk": "/".join([".."] * (native.package_name().count("/") + 1)) + "/src/cdk",
- "@angular/material": "/".join([".."] * (native.package_name().count("/") + 1)) + "/src/material",
- "@angular/material-experimental": "/".join([".."] * (native.package_name().count("/") + 1)) + "/src/material-experimental",
- },
- **kwargs
- )
+def sass_binary(sourcemap = False, include_paths = [], layer = None, **kwargs):
+ _module_mappings = {
+ "@angular/cdk": "/".join([".."] * (native.package_name().count("/") + 1)) + "/src/cdk",
+ "@angular/material": "/".join([".."] * (native.package_name().count("/") + 1)) + "/src/material",
+ "@angular/material-experimental": "/".join([".."] * (native.package_name().count("/") + 1)) + "/src/material-experimental",
+ }
+
+ if layer:
+ if sourcemap:
+ fail("sass_binary(layer=...) does not support sourcemap because wrapping the CSS in @layer invalidates the generated mappings.")
+ if kwargs.get("output_name") or kwargs.get("output_dir"):
+ fail("sass_binary(layer=...) does not support output_name or output_dir.")
+
+ name = kwargs.pop("name")
+ visibility = kwargs.pop("visibility", None)
+ testonly = kwargs.get("testonly", False)
+ src = kwargs.get("src", "")
+ out_css = src.rsplit(".", 1)[0] + ".css"
+
+ # Prefix only the filename to avoid creating spurious directories.
+ if "/" in out_css:
+ out_dir, out_file = out_css.rsplit("/", 1)
+ raw_css = out_dir + "/_layer_" + out_file
+ else:
+ raw_css = "_layer_" + out_css
+
+ # Compile to an intermediate CSS file, then wrap the compiled output in a named @layer
+ # while preserving the final filename expected by component `styleUrl`s.
+ _sass_binary(
+ name = name + "_pre_layer",
+ sourcemap = False,
+ include_paths = include_paths,
+ output_name = raw_css,
+ module_mappings = _module_mappings,
+ visibility = ["//visibility:private"],
+ **kwargs
+ )
+
+ native.genrule(
+ name = name,
+ srcs = [":" + name + "_pre_layer"],
+ outs = [out_css],
+ cmd = ("printf '@layer %s {\\n' > $@ " +
+ "&& cat $< >> $@ " +
+ "&& printf '\\n}\\n' >> $@") % layer,
+ testonly = testonly,
+ visibility = visibility,
+ )
+ else:
+ _sass_binary(
+ sourcemap = sourcemap,
+ include_paths = include_paths,
+ module_mappings = _module_mappings,
+ **kwargs
+ )
def sass_library(**kwargs):
_sass_library(**kwargs)