diff --git a/src/dev-app/theme.scss b/src/dev-app/theme.scss
index dec5dd5d0ac3..2a01adba5396 100644
--- a/src/dev-app/theme.scss
+++ b/src/dev-app/theme.scss
@@ -1,5 +1,6 @@
@import '../material/core/theming/all-theme';
@import '../material/core/focus-indicator/focus-indicator';
+@import '../material-experimental/mdc-helpers/mdc-helpers';
@import '../material-experimental/mdc-slider/mdc-slider';
@import '../material-experimental/mdc-theming/all-theme';
@import '../material-experimental/mdc-typography/all-typography';
@@ -17,6 +18,7 @@
// Include base styles for strong focus indicators.
.demo-strong-focus {
@include mat-strong-focus-indicators();
+ @include mat-mdc-strong-focus-indicators();
}
// Define the default theme (same as the example above).
@@ -39,6 +41,7 @@ $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
// Include the default theme for focus indicators.
.demo-strong-focus {
@include mat-strong-focus-indicators-theme($candy-app-theme);
+ @include mat-mdc-strong-focus-indicators-theme($candy-app-theme);
}
// Include the alternative theme styles inside of a block with a CSS class. You can make this
@@ -55,4 +58,5 @@ $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
// Include the dark theme for focus indicators.
.demo-unicorn-dark-theme.demo-strong-focus {
@include mat-strong-focus-indicators-theme($dark-theme);
+ @include mat-mdc-strong-focus-indicators-theme($dark-theme);
}
diff --git a/src/material-experimental/mdc-button/button-base.ts b/src/material-experimental/mdc-button/button-base.ts
index 2b16d20e53b9..b1968daf8bb1 100644
--- a/src/material-experimental/mdc-button/button-base.ts
+++ b/src/material-experimental/mdc-button/button-base.ts
@@ -35,6 +35,7 @@ export const MAT_BUTTON_HOST = {
// an unthemed version. If color is undefined, apply a CSS class that makes it easy to
// select and style this "theme".
'[class.mat-unthemed]': '!color',
+ 'class': 'mat-mdc-focus-indicator',
};
/** List of classes to add to buttons instances based on host attribute selector. */
@@ -147,6 +148,7 @@ export const MAT_ANCHOR_HOST = {
// an unthemed version. If color is undefined, apply a CSS class that makes it easy to
// select and style this "theme".
'[class.mat-unthemed]': '!color',
+ 'class': 'mat-mdc-focus-indicator',
};
/**
diff --git a/src/material-experimental/mdc-button/button.spec.ts b/src/material-experimental/mdc-button/button.spec.ts
index e18a27ae72d4..da0daeea423d 100644
--- a/src/material-experimental/mdc-button/button.spec.ts
+++ b/src/material-experimental/mdc-button/button.spec.ts
@@ -257,6 +257,15 @@ describe('MDC-based MatButton', () => {
);
});
});
+
+ it('should have a focus indicator', () => {
+ const fixture = TestBed.createComponent(TestApp);
+ const buttonNativeElements =
+ [...fixture.debugElement.nativeElement.querySelectorAll('a, button')];
+
+ expect(buttonNativeElements
+ .every(element => element.classList.contains('mat-mdc-focus-indicator'))).toBe(true);
+ });
});
/** Test component that contains an MatButton. */
diff --git a/src/material-experimental/mdc-button/fab.scss b/src/material-experimental/mdc-button/fab.scss
index a23a4d05aa04..f059b0882f46 100644
--- a/src/material-experimental/mdc-button/fab.scss
+++ b/src/material-experimental/mdc-button/fab.scss
@@ -9,9 +9,13 @@
.mat-mdc-fab, .mat-mdc-mini-fab {
@include _mat-button-interactive();
@include _mat-button-disabled();
-}
-.mat-mdc-fab, .mat-mdc-mini-fab {
+ // MDC adds some styles to fab and mini-fab that conflict with some of our focus indicator
+ // styles and don't actually do anything. This undoes those conflicting styles.
+ &:not(.mdc-ripple-upgraded):focus::before {
+ background: transparent;
+ opacity: 1;
+ }
}
// MDC expects the fab icon to contain this HTML content:
diff --git a/src/material-experimental/mdc-button/icon-button.scss b/src/material-experimental/mdc-button/icon-button.scss
index ac18a6c91571..c16dbe908a40 100644
--- a/src/material-experimental/mdc-button/icon-button.scss
+++ b/src/material-experimental/mdc-button/icon-button.scss
@@ -13,4 +13,16 @@
border-radius: 50%;
@include _mat-button-disabled();
+
+ // MDC adds some styles to icon buttons that conflict with some of our focus indicator styles
+ // and don't actually do anything. This undoes those conflicting styles.
+ &.mat-unthemed,
+ &.mat-primary,
+ &.mat-accent,
+ &.mat-warn {
+ &:not(.mdc-ripple-upgraded):focus::before {
+ background: transparent;
+ opacity: 1;
+ }
+ }
}
diff --git a/src/material-experimental/mdc-checkbox/checkbox.html b/src/material-experimental/mdc-checkbox/checkbox.html
index 28f63297fa18..20b55ada8721 100644
--- a/src/material-experimental/mdc-checkbox/checkbox.html
+++ b/src/material-experimental/mdc-checkbox/checkbox.html
@@ -27,7 +27,7 @@
{
expect(inputElement.indeterminate).toBe(true, 'indeterminate should not change');
}));
});
+
+ it('should have a focus indicator', () => {
+ const checkboxRippleNativeElement =
+ checkboxNativeElement.querySelector('.mat-mdc-checkbox-ripple')!;
+
+ expect(checkboxRippleNativeElement.classList.contains('mat-mdc-focus-indicator')).toBe(true);
+ });
});
describe('with change event and no initial value', () => {
diff --git a/src/material-experimental/mdc-helpers/_mdc-helpers.scss b/src/material-experimental/mdc-helpers/_mdc-helpers.scss
index d84a77dbfdcf..632edcc5a65c 100644
--- a/src/material-experimental/mdc-helpers/_mdc-helpers.scss
+++ b/src/material-experimental/mdc-helpers/_mdc-helpers.scss
@@ -6,6 +6,7 @@
@import '@material/theme/functions.import';
@import '@material/theme/variables.import';
@import '@material/typography/variables.import';
+@import '../../material/core/style/layout-common';
@import '../../material/core/theming/theming';
@import '../../material/core/typography/typography';
@@ -211,3 +212,74 @@ $mat-typography-level-mappings: (
// Reset the original values.
$mdc-typography-styles: $orig-mdc-typography-styles !global;
}
+
+/// Mixin that turns on strong focus indicators.
+///
+/// @example
+/// .my-app {
+/// @include mat-mdc-strong-focus-indicators();
+/// }
+@mixin mat-mdc-strong-focus-indicators() {
+ // Base styles for the focus indicators.
+ .mat-mdc-focus-indicator::before {
+ @include mat-fill();
+
+ border-radius: $mat-focus-indicator-border-radius;
+ border: $mat-focus-indicator-border-width $mat-focus-indicator-border-style transparent;
+ box-sizing: border-box;
+ pointer-events: none;
+ }
+
+ // By default, all focus indicators are flush with the bounding box of their
+ // host element. For particular elements (listed below), default inset/offset
+ // values are necessary to ensure that the focus indicator is sufficiently
+ // contrastive and renders appropriately.
+
+ .mat-mdc-focus-indicator.mdc-button::before,
+ .mat-mdc-focus-indicator.mdc-fab::before,
+ .mat-mdc-focus-indicator.mdc-icon-button::before {
+ margin: $mat-focus-indicator-border-width * -2;
+ }
+
+ .mat-mdc-focus-indicator.mat-mdc-tab::before,
+ .mat-mdc-focus-indicator.mat-mdc-tab-link::before {
+ margin: $mat-focus-indicator-border-width * 2;
+ }
+
+ // Render the focus indicator on focus. Defining a pseudo element's
+ // content will cause it to render.
+
+ // For checkboxes and slide toggles, render the focus indicator when we know the hidden input
+ // is focused (slightly different for each control).
+ .mdc-checkbox__native-control:focus ~ .mat-mdc-focus-indicator::before,
+ .mat-mdc-slide-toggle-focused .mat-mdc-focus-indicator::before,
+
+ // For all other components, render the focus indicator on focus.
+ .mat-mdc-focus-indicator:focus::before {
+ content: '';
+ }
+}
+
+/// Mixin that sets the color of the focus indicators.
+///
+/// @param {color|map} $themeOrMap
+/// If theme, focus indicators are set to the primary color of the theme. If
+/// color, focus indicators are set to that color.
+///
+/// @example
+/// .demo-dark-theme {
+/// @include mat-mdc-strong-focus-indicators-theme($darkThemeMap);
+/// }
+///
+/// @example
+/// .demo-red-theme {
+/// @include mat-mdc-strong-focus-indicators-theme(#F00);
+/// }
+@mixin mat-mdc-strong-focus-indicators-theme($themeOrColor) {
+ .mat-mdc-focus-indicator::before {
+ border-color: if(
+ type-of($themeOrColor) == 'map',
+ mat-color(map_get($themeOrColor, primary)),
+ $themeOrColor);
+ }
+}
diff --git a/src/material-experimental/mdc-menu/menu-item.ts b/src/material-experimental/mdc-menu/menu-item.ts
index 16358c8dff8e..fb6a9ceac883 100644
--- a/src/material-experimental/mdc-menu/menu-item.ts
+++ b/src/material-experimental/mdc-menu/menu-item.ts
@@ -18,10 +18,11 @@ import {MatMenuItem as BaseMatMenuItem} from '@angular/material/menu';
inputs: ['disabled', 'disableRipple'],
host: {
'[attr.role]': 'role',
- // The MatMenuItem parent class adds `mat-menu-item` to the CSS classlist, but this should
- // not be added for this MDC equivalent menu item.
+ // The MatMenuItem parent class adds `mat-menu-item` and `mat-focus-indicator` to the CSS
+ // classlist, but these should not be added for this MDC equivalent menu item.
'[class.mat-menu-item]': 'false',
- 'class': 'mat-mdc-menu-item',
+ '[class.mat-focus-indicator]': 'false',
+ 'class': 'mat-mdc-menu-item mat-mdc-focus-indicator',
'[class.mat-mdc-menu-item-highlighted]': '_highlighted',
'[class.mat-mdc-menu-item-submenu-trigger]': '_triggersSubmenu',
'[attr.tabindex]': '_getTabIndex()',
diff --git a/src/material-experimental/mdc-menu/menu.spec.ts b/src/material-experimental/mdc-menu/menu.spec.ts
index 83dfee6e0bbb..7d59a65e539e 100644
--- a/src/material-experimental/mdc-menu/menu.spec.ts
+++ b/src/material-experimental/mdc-menu/menu.spec.ts
@@ -2160,6 +2160,18 @@ describe('MDC-based MatMenu', () => {
});
+ it('should have a focus indicator', () => {
+ const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
+ fixture.detectChanges();
+ fixture.componentInstance.trigger.openMenu();
+ fixture.detectChanges();
+ const menuItemNativeElements =
+ Array.from(overlayContainerElement.querySelectorAll('.mat-mdc-menu-item'));
+
+ expect(menuItemNativeElements
+ .every(element => element.classList.contains('mat-mdc-focus-indicator'))).toBe(true);
+ });
+
});
describe('MatMenu default overrides', () => {
diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.html b/src/material-experimental/mdc-slide-toggle/slide-toggle.html
index 823510286e97..d99596f79a4e 100644
--- a/src/material-experimental/mdc-slide-toggle/slide-toggle.html
+++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.html
@@ -3,28 +3,29 @@
diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.scss b/src/material-experimental/mdc-slide-toggle/slide-toggle.scss
index 8d4f828bea6b..6c2eda116902 100644
--- a/src/material-experimental/mdc-slide-toggle/slide-toggle.scss
+++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.scss
@@ -16,6 +16,11 @@
// The ripple needs extra specificity so the base ripple styling doesn't override its `position`.
.mat-mdc-slide-toggle-ripple, .mdc-switch__thumb-underlay::before {
@include mat-fill;
+
+ // Disable pointer events for the ripple container so that it doesn't eat the mouse events meant
+ // for the input. Pointer events can be safely disabled because the ripple trigger element is
+ // the host element.
+ pointer-events: none;
}
// The MDC switch styles related to the hover state are intertwined with the MDC ripple styles.
diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts b/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts
index 4107a43eaf43..3e2272ee991f 100644
--- a/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts
+++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts
@@ -297,6 +297,14 @@ describe('MDC-based MatSlideToggle without forms', () => {
expect(slideToggleElement.querySelectorAll(rippleSelector).length).toBe(0);
});
+
+ it('should have a focus indicator', () => {
+ const slideToggleRippleNativeElement =
+ slideToggleElement.querySelector('.mat-mdc-slide-toggle-ripple')!;
+
+ expect(slideToggleRippleNativeElement.classList.contains('mat-mdc-focus-indicator'))
+ .toBe(true);
+ });
});
describe('custom template', () => {
diff --git a/src/material-experimental/mdc-slider/slider.spec.ts b/src/material-experimental/mdc-slider/slider.spec.ts
index d3b7d579e782..e046c86a5612 100644
--- a/src/material-experimental/mdc-slider/slider.spec.ts
+++ b/src/material-experimental/mdc-slider/slider.spec.ts
@@ -127,6 +127,10 @@ describe('MDC-based MatSlider', () => {
expect(sliderInstance.value).toBe(100);
});
+ it('should have a focus indicator', () => {
+ expect(sliderNativeElement.classList.contains('mat-mdc-focus-indicator')).toBe(true);
+ });
+
});
describe('disabled slider', () => {
diff --git a/src/material-experimental/mdc-slider/slider.ts b/src/material-experimental/mdc-slider/slider.ts
index a5b5f2126dcf..5e594e8546b8 100644
--- a/src/material-experimental/mdc-slider/slider.ts
+++ b/src/material-experimental/mdc-slider/slider.ts
@@ -81,7 +81,7 @@ export class MatSliderChange {
templateUrl: 'slider.html',
styleUrls: ['slider.css'],
host: {
- 'class': 'mat-mdc-slider mdc-slider',
+ 'class': 'mat-mdc-slider mdc-slider mat-mdc-focus-indicator',
'role': 'slider',
'aria-orientation': 'horizontal',
// The tabindex if the slider turns disabled is managed by the MDC foundation which
diff --git a/src/material-experimental/mdc-tabs/tab-group.html b/src/material-experimental/mdc-tabs/tab-group.html
index a89da9fb8108..e7369ef899fd 100644
--- a/src/material-experimental/mdc-tabs/tab-group.html
+++ b/src/material-experimental/mdc-tabs/tab-group.html
@@ -4,7 +4,7 @@
(indexFocused)="_focusChanged($event)"
(selectFocusedIndex)="selectedIndex = $event">
-
{
subscription.unsubscribe();
});
+ it('should have a focus indicator', () => {
+ const tabLabelNativeElements =
+ [...fixture.debugElement.nativeElement.querySelectorAll('.mat-mdc-tab')];
+
+ expect(tabLabelNativeElements.every(el => el.classList.contains('mat-mdc-focus-indicator')))
+ .toBe(true);
+ });
+
});
describe('aria labelling', () => {
diff --git a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.spec.ts b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.spec.ts
index ba848e4e1d0c..09ee03dbce92 100644
--- a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.spec.ts
+++ b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.spec.ts
@@ -322,6 +322,14 @@ describe('MDC-based MatTabNavBar', () => {
expect(fixture.componentInstance.tabLinks.toArray().every(tabLink => tabLink.rippleDisabled))
.toBe(true, 'Expected every tab link to have ripples disabled');
});
+
+ it('should have a focus indicator', () => {
+ const tabLinkNativeElements =
+ [...fixture.debugElement.nativeElement.querySelectorAll('.mat-mdc-tab-link')];
+
+ expect(tabLinkNativeElements
+ .every(element => element.classList.contains('mat-mdc-focus-indicator'))).toBe(true);
+ });
});
describe('with the ink bar fit to content', () => {
diff --git a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.ts b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.ts
index dae44809468e..3b5700354d0f 100644
--- a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.ts
+++ b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.ts
@@ -119,7 +119,7 @@ export class MatTabNav extends _MatTabNavBase implements AfterContentInit {
templateUrl: 'tab-link.html',
styleUrls: ['tab-link.css'],
host: {
- 'class': 'mdc-tab mat-mdc-tab-link',
+ 'class': 'mdc-tab mat-mdc-tab-link mat-mdc-focus-indicator',
'[attr.aria-current]': 'active ? "page" : null',
'[attr.aria-disabled]': 'disabled',
'[attr.tabIndex]': 'tabIndex',
diff --git a/src/material/button/button.spec.ts b/src/material/button/button.spec.ts
index 09fe3f0cb402..27afe9a4cc88 100644
--- a/src/material/button/button.spec.ts
+++ b/src/material/button/button.spec.ts
@@ -273,9 +273,11 @@ describe('MatButton', () => {
it('should have a focus indicator', () => {
const fixture = TestBed.createComponent(TestApp);
- const buttonNativeElement = fixture.debugElement.nativeElement.querySelector('button');
+ const buttonNativeElements =
+ [...fixture.debugElement.nativeElement.querySelectorAll('a, button')];
- expect(buttonNativeElement.classList.contains('mat-focus-indicator')).toBe(true);
+ expect(buttonNativeElements.every(element => element.classList.contains('mat-focus-indicator')))
+ .toBe(true);
});
});
diff --git a/src/material/core/focus-indicator/_focus-indicator.scss b/src/material/core/focus-indicator/_focus-indicator.scss
index 4ce5a8c0e4ac..043e6d319c34 100644
--- a/src/material/core/focus-indicator/_focus-indicator.scss
+++ b/src/material/core/focus-indicator/_focus-indicator.scss
@@ -1,4 +1,10 @@
@import '../theming/theming';
+@import '../style/layout-common';
+
+// Focus indicator styles.
+$mat-focus-indicator-border-radius: 4px;
+$mat-focus-indicator-border-width: 2px;
+$mat-focus-indicator-border-style: solid;
/// Mixin that turns on strong focus indicators.
///
@@ -7,20 +13,15 @@
/// @include mat-strong-focus-indicators();
/// }
@mixin mat-strong-focus-indicators() {
- // Border width of the focus indicators.
- $border-width: 2px;
// Base styles for the focus indicators.
.mat-focus-indicator::before {
- border-radius: 4px;
- border: $border-width solid transparent;
+ @include mat-fill();
+
+ border-radius: $mat-focus-indicator-border-radius;
+ border: $mat-focus-indicator-border-width $mat-focus-indicator-border-style transparent;
box-sizing: border-box;
- position: absolute;
pointer-events: none;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
}
// By default, all focus indicators are flush with the bounding box of their
@@ -28,21 +29,20 @@
// values are necessary to ensure that the focus indicator is sufficiently
// contrastive and renders appropriately.
- .mat-focus-indicator.mat-button-base::before,
.mat-focus-indicator.mat-button-base::before,
.mat-focus-indicator.mat-card::before,
.mat-focus-indicator.mat-chip::before,
.mat-focus-indicator.mat-sort-header-button::before {
- margin: $border-width * -2;
+ margin: $mat-focus-indicator-border-width * -2;
}
.mat-focus-indicator.mat-calendar-body-cell::before {
- margin: $border-width * -1;
+ margin: $mat-focus-indicator-border-width * -1;
}
.mat-focus-indicator.mat-tab-link::before,
.mat-focus-indicator.mat-tab-label::before {
- margin: $border-width * 2;
+ margin: $mat-focus-indicator-border-width * 2;
}
// Render the focus indicator on focus. Defining a pseudo element's