Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions guides/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
1 change: 1 addition & 0 deletions src/e2e-app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
Expand Down
1 change: 1 addition & 0 deletions src/e2e-app/components/e2e-app/e2e-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'},
];
}
31 changes: 31 additions & 0 deletions src/e2e-app/components/tailwind-layer-e2e.ts
Original file line number Diff line number Diff line change
@@ -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: `
<button
id="tailwind-utility-button"
class="tw-bg-lime-500"
mat-flat-button>
Tailwind utility should win
</button>
<button
id="unlayered-utility-button"
class="tw-bg-fuchsia-unlayered"
mat-flat-button>
Unlayered utility control
</button>
`,
imports: [MatButtonModule],
changeDetection: ChangeDetectionStrategy.Eager,
})
export class TailwindLayerE2e {}
2 changes: 2 additions & 0 deletions src/e2e-app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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(),
Expand Down
21 changes: 18 additions & 3 deletions src/e2e-app/theme.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@use '@angular/material' as mat;

@layer base, angular-material, utilities;

$theme: mat.define-theme((
color: (
theme-type: light,
Expand All @@ -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);
}
2 changes: 2 additions & 0 deletions src/material-experimental/menubar/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions src/material-experimental/selection/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ sass_library(
sass_binary(
name = "selection_column_scss",
src = "selection-column.scss",
layer = "angular-material",
)
2 changes: 2 additions & 0 deletions src/material/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/material/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/material/autocomplete/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "autocomplete.scss",
layer = "angular-material",
deps = [
":m2",
":m3",
Expand Down
1 change: 1 addition & 0 deletions src/material/badge/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "badge_css",
src = "badge.scss",
layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
Expand Down
1 change: 1 addition & 0 deletions src/material/bottom-sheet/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "bottom-sheet-container.scss",
layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
Expand Down
1 change: 1 addition & 0 deletions src/material/button-toggle/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "button-toggle.scss",
layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
Expand Down
4 changes: 4 additions & 0 deletions src/material/button/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ sass_library(
sass_binary(
name = "icon_button_css",
src = "icon-button.scss",
layer = "angular-material",
deps = [
":base_lib",
":m2",
Expand All @@ -96,6 +97,7 @@ sass_binary(
sass_binary(
name = "fab_css",
src = "fab.scss",
layer = "angular-material",
deps = [
":base_lib",
":m2",
Expand All @@ -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",
],
Expand All @@ -128,6 +131,7 @@ sass_library(
sass_binary(
name = "css",
src = "button.scss",
layer = "angular-material",
deps = [
":base_lib",
":m2",
Expand Down
1 change: 1 addition & 0 deletions src/material/card/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ sass_library(
sass_binary(
name = "css",
src = "card.scss",
layer = "angular-material",
deps = [
":m2",
"//src/material/core/tokens:token_utils",
Expand Down
1 change: 1 addition & 0 deletions src/material/checkbox/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ sass_library(
sass_binary(
name = "css",
src = "checkbox.scss",
layer = "angular-material",
deps = [
":checkbox_common",
":m2",
Expand Down
2 changes: 2 additions & 0 deletions src/material/chips/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ sass_library(
sass_binary(
name = "chip_css",
src = "chip.scss",
layer = "angular-material",
deps = [
":m2",
"//src/cdk:sass_lib",
Expand All @@ -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"],
)

Expand Down
1 change: 1 addition & 0 deletions src/material/core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ sass_library(
sass_binary(
name = "ripple_structure",
src = "ripple/ripple-structure.scss",
layer = "angular-material",
deps = [
":ripple_sass",
],
Expand Down
1 change: 1 addition & 0 deletions src/material/core/focus-indicators/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ ng_project(
sass_binary(
name = "structural_styles_css",
src = "structural-styles.scss",
layer = "angular-material",
deps = [":focus-indicators"],
)
1 change: 1 addition & 0 deletions src/material/core/internal-form-field/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
)
2 changes: 2 additions & 0 deletions src/material/core/option/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -88,6 +89,7 @@ sass_binary(
sass_binary(
name = "optgroup_css",
src = "optgroup.scss",
layer = "angular-material",
deps = [
"//src/material/core/tokens:token_utils",
],
Expand Down
1 change: 1 addition & 0 deletions src/material/core/selection/pseudo-checkbox/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions src/material/core/style/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
14 changes: 14 additions & 0 deletions src/material/core/style/_cascade-layers.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading
Loading