Skip to content

Commit f4a02ad

Browse files
authoredSep 3, 2024··
feat(cdk/a11y): use native media query for high contrast detection (#29678)
Changes the `cdk.high-contrast` mixin to use a native media query instead of a custom CSS classes. The advantage is that we no longer need to depend on the `HighContrastModeDetector` to add a class that we can target. BREAKING CHANGE: * Since `cdk.high-contrast` targets a media query instead of a class, the specificity of the styles it emits is lower than before.
1 parent f3df87b commit f4a02ad

39 files changed

+79
-136
lines changed
 

‎src/cdk/a11y/_index.scss

+12-54
Original file line numberDiff line numberDiff line change
@@ -40,63 +40,21 @@
4040
@include a11y-visually-hidden;
4141
}
4242

43-
/// Emits the mixin's content nested under `$selector-context` if `$selector-context`
44-
/// is non-empty.
45-
/// @param {String} selector-context The selector under which to nest the mixin's content.
46-
@mixin _optionally-nest-content($selector-context) {
47-
@if ($selector-context == '') {
48-
@content;
49-
}
50-
@else {
51-
#{$selector-context} {
52-
@content;
53-
}
54-
}
55-
}
56-
57-
/// Applies styles for users in high contrast mode. Note that this only applies
58-
/// to Microsoft browsers. Chrome can be included by checking for the `html[hc]`
59-
/// attribute, however Chrome handles high contrast differently.
43+
/// Applies styles for users in high contrast mode.
6044
///
61-
/// @param {String} target Type of high contrast setting to target. Defaults to `active`, can be
62-
/// `white-on-black` or `black-on-white`.
63-
/// @param {String} encapsulation Whether to emit styles for view encapsulation. Values are:
64-
/// * `on` - works for `Emulated`, `Native`, and `ShadowDom`
65-
/// * `off` - works for `None`
66-
/// * `any` - works for all encapsulation modes by emitting the CSS twice (default).
67-
@mixin high-contrast($target: active, $encapsulation: 'any') {
68-
@if ($target != 'active' and $target != 'black-on-white' and $target != 'white-on-black') {
45+
/// @param {String} target Type of high contrast setting to target. Can be `active` or `none`.
46+
/// Defaults to `active`.
47+
/// @param {String} encapsulation No longer used and will be removed.
48+
@mixin high-contrast($target: active, $encapsulation: null) {
49+
// Historically we used to support `black-on-white` and `white-on-black` so we
50+
// allow them here anyway. They'll be coerced to `active` below.
51+
@if ($target != 'active' and $target != 'none' and $target != 'black-on-white' and
52+
$target != 'white-on-black') {
6953
@error 'Unknown cdk-high-contrast value "#{$target}" provided. ' +
70-
'Allowed values are "active", "black-on-white", and "white-on-black"';
54+
'Allowed values are "active" and "none"';
7155
}
7256

73-
@if ($encapsulation != 'on' and $encapsulation != 'off' and $encapsulation != 'any') {
74-
@error 'Unknown cdk-high-contrast encapsulation "#{$encapsulation}" provided. ' +
75-
'Allowed values are "on", "off", and "any"';
76-
}
77-
78-
// If the selector context has multiple parts, such as `.section, .region`, just doing
79-
// `.cdk-high-contrast-xxx #{&}` will only apply the parent selector to the first part of the
80-
// context. We address this by nesting the selector context under .cdk-high-contrast.
81-
@at-root {
82-
$selector-context: #{&};
83-
84-
@if ($encapsulation != 'on') {
85-
// Note that if this selector is updated, the same change has to be made inside
86-
// `_overlay.scss` which can't depend on this mixin due to some infrastructure limitations.
87-
.cdk-high-contrast-#{$target} {
88-
@include _optionally-nest-content($selector-context) {
89-
@content;
90-
}
91-
}
92-
}
93-
94-
@if ($encapsulation != 'off') {
95-
.cdk-high-contrast-#{$target} :host {
96-
@include _optionally-nest-content($selector-context) {
97-
@content;
98-
}
99-
}
100-
}
57+
@media (forced-colors: #{if($target == none, none, active)}) {
58+
@content;
10159
}
10260
}

‎src/cdk/a11y/a11y.md

+5-20
Original file line numberDiff line numberDiff line change
@@ -229,36 +229,21 @@ system. Otherwise, you can include this mixin in a global stylesheet.
229229

230230
#### Targeting high contrast users
231231

232-
Microsoft Windows includes an accessibility feature called [Windows High Contrast Mode][]. The
232+
Some operating systems include an accessibility feature called High Contrast Mode. The
233233
`cdk/a11y` package provides a Sass mixin that lets you define styles that only apply in high
234234
contrast mode. To create a high contrast style, define your style inside the `high-contrast` mixin.
235235

236-
The mixin works by targeting a CSS class which is added to the `body` by the CDK when high contrast
237-
mode is detected at runtime, via the `HighContrastModeDetector` service.
236+
The mixin works by targeting the `forced-colors` media query.
238237

239238
```scss
240239
@use '@angular/cdk';
241240

242241
button {
243-
@include cdk.high-contrast() {
242+
@include cdk.high-contrast {
244243
outline: solid 1px;
245244
}
246245
}
247246
```
248247

249-
The `high-contrast` mixin accepts two optional parameters, `$target` and `$encapsulation`.
250-
251-
The `$target` parameter allows you to specify which variation of high contrast mode your style
252-
targets. The accepted values are `active` (default), `black-on-white`, and `white-on-black`. These
253-
values correspond to the supported values for the
254-
[`-ms-high-contrast` media query][ms-high-contrast].
255-
256-
The `$encapsulation` parameter affects how the emitted styles interact with style encapsulation.
257-
The supported values are `on`, `off`, and `any`. The default value is `any`, which works for any
258-
encapsulation scenario by emitting two selectors. Specifying either `on` or `off` slightly reduces
259-
the amount of CSS emitted by limiting the styles to components with encapsulation enabled or
260-
disabled, respectively. The styles emitted for encapsulated components work for both Angular's
261-
emulated style encapsulation and for native Shadow DOM encapsulation.
262-
263-
[Windows High Contrast Mode]: https://support.microsoft.com/en-us/windows/use-high-contrast-mode-in-windows-10-fedc744c-90ac-69df-aed5-c8a90125e696
264-
[ms-high-contrast]: https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/
248+
The `high-contrast` mixin accepts the optional `$target` parameter which allows you to specify
249+
the value of the `forced-color` media query. Its value can be either `active` or `none`.

‎src/material-experimental/popover-edit/_popover-edit-theme.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
display: block;
8383
padding: 16px 24px;
8484

85-
@include cdk.high-contrast(active, off) {
85+
@include cdk.high-contrast {
8686
// Note that normally we use 1px for high contrast outline, however here we use 3,
8787
// because the popover is rendered on top of a table which already has some borders
8888
// and doesn't have a backdrop. The thicker outline makes it easier to differentiate.

‎src/material/autocomplete/autocomplete.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ div.mat-mdc-autocomplete-panel {
2424
@include token-utils.create-token-slot(background-color, background-color);
2525
}
2626

27-
@include cdk.high-contrast(active, off) {
27+
@include cdk.high-contrast {
2828
outline: solid 1px;
2929
}
3030

‎src/material/badge/badge.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ $large-size: $default-size + 6;
9797
}
9898
}
9999

100-
@include cdk.high-contrast(active, off) {
100+
@include cdk.high-contrast {
101101
outline: solid 1px;
102102
border-radius: 0;
103103
}

‎src/material/bottom-sheet/bottom-sheet-container.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ $container-horizontal-padding: 16px !default;
3232
@include token-utils.create-token-slot(letter-spacing, container-text-tracking);
3333
}
3434

35-
@include cdk.high-contrast(active, off) {
35+
@include cdk.high-contrast {
3636
outline: 1px solid;
3737
}
3838
}

‎src/material/button-toggle/button-toggle.scss

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ $_standard-tokens: (
4444

4545
@include elevation.overridable-elevation(2);
4646

47-
@include cdk.high-contrast(active, off) {
47+
@include cdk.high-contrast {
4848
outline: solid 1px;
4949
}
5050
}
@@ -65,7 +65,7 @@ $_standard-tokens: (
6565
box-shadow: none;
6666
}
6767

68-
@include cdk.high-contrast(active, off) {
68+
@include cdk.high-contrast {
6969
outline: 0;
7070
}
7171
}
@@ -255,7 +255,7 @@ $_standard-tokens: (
255255
}
256256
}
257257

258-
@include cdk.high-contrast(active, off) {
258+
@include cdk.high-contrast {
259259
// Changing the background color for the selected item won't be visible in high contrast mode.
260260
// We fall back to using the overlay to draw a brighter, semi-transparent tint on top instead.
261261
// It uses a border, because the browser will render it using a brighter color.

‎src/material/button/button-high-contrast.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
.mat-mdc-unelevated-button:not(.mdc-button--outlined),
77
.mat-mdc-raised-button:not(.mdc-button--outlined),
88
.mat-mdc-outlined-button:not(.mdc-button--outlined),
9-
.mat-mdc-icon-button {
10-
@include cdk.high-contrast(active, off) {
9+
.mat-mdc-icon-button.mat-mdc-icon-button {
10+
@include cdk.high-contrast {
1111
outline: solid 1px;
1212
}
1313
}

‎src/material/checkbox/_checkbox-common.scss

+4-4
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ $_fallback-size: 40px;
6666
cursor: default;
6767
pointer-events: none;
6868

69-
@include cdk.high-contrast(active, off) {
69+
@include cdk.high-contrast {
7070
opacity: 0.5;
7171
}
7272
}
@@ -177,7 +177,7 @@ $_fallback-size: 40px;
177177
@include token-utils.create-token-slot(color, selected-checkmark-color);
178178
}
179179

180-
@include cdk.high-contrast(active, off) {
180+
@include cdk.high-contrast {
181181
color: CanvasText;
182182
}
183183
}
@@ -188,7 +188,7 @@ $_fallback-size: 40px;
188188
.mdc-checkbox__checkmark {
189189
@include token-utils.create-token-slot(color, disabled-selected-checkmark-color);
190190

191-
@include cdk.high-contrast(active, off) {
191+
@include cdk.high-contrast {
192192
color: CanvasText;
193193
}
194194
}
@@ -220,7 +220,7 @@ $_fallback-size: 40px;
220220
@include token-utils.create-token-slot(border-color, selected-checkmark-color);
221221
}
222222

223-
@include cdk.high-contrast(active, off) {
223+
@include cdk.high-contrast {
224224
margin: 0 1px;
225225
}
226226
}

‎src/material/chips/chip.scss

+3-3
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ $_avatar-trailing-padding: 8px;
351351
stroke-dashoffset: 0;
352352
}
353353

354-
@include cdk.high-contrast(active, off) {
354+
@include cdk.high-contrast {
355355
// SVG colors won't be changed in high contrast mode and since the checkmark is white
356356
// by default, it'll blend in with the background in black-on-white mode. Override the
357357
// color to ensure that it's visible. We need !important, because the theme styles are
@@ -411,7 +411,7 @@ $_avatar-trailing-padding: 8px;
411411
}
412412
}
413413

414-
@include cdk.high-contrast(active, off) {
414+
@include cdk.high-contrast {
415415
outline: solid 1px;
416416
}
417417
}
@@ -721,7 +721,7 @@ $_avatar-trailing-padding: 8px;
721721
// Single-selection chips show their selected state using a background color which won't be visible
722722
// in high contrast mode. This isn't necessary in multi-selection since there's a checkmark.
723723
.mat-mdc-chip-selected:not(.mat-mdc-chip-multiple) {
724-
@include cdk.high-contrast(active, off) {
724+
@include cdk.high-contrast {
725725
outline-width: 3px;
726726
}
727727
}

‎src/material/core/focus-indicators/_private.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ $default-border-radius: 4px;
3535
}
3636

3737
// Enable the indicator in high contrast mode.
38-
@include cdk.high-contrast(active, off) {
38+
@include cdk.high-contrast {
3939
@include _customize-focus-indicators((display: block));
4040
}
4141
}

‎src/material/core/option/option.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ $_side-padding: 16px;
153153
}
154154
}
155155

156-
@include cdk.high-contrast(active, off) {
156+
@include cdk.high-contrast {
157157
// In single selection mode, the selected option is indicated by changing its
158158
// background color, but that doesn't work in high contrast mode. We add an
159159
// alternate indication by rendering out a circle.

‎src/material/core/ripple/_ripple.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
}
4545

4646
// In high contrast mode the ripple is opaque, causing it to obstruct the content.
47-
@include cdk.high-contrast(active, off) {
47+
@include cdk.high-contrast {
4848
display: none;
4949
}
5050

‎src/material/core/style/_menu-common.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ $icon-margin: 16px !default;
7979
}
8080

8181
// Fix for Chromium-based browsers blending in the `currentColor` with the background.
82-
@include cdk.high-contrast(active, off) {
82+
@include cdk.high-contrast {
8383
fill: CanvasText;
8484
}
8585
}

‎src/material/datepicker/calendar-body.scss

+3-3
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ $_tokens: (tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots(
237237
// Fade out the disabled cells so that they can be distinguished from the enabled ones. Note that
238238
// ideally we'd use `color: GreyText` here which is what the browser uses for disabled buttons,
239239
// but we can't because Firefox doesn't recognize it.
240-
@include cdk.high-contrast(active, off) {
240+
@include cdk.high-contrast {
241241
opacity: 0.5;
242242
}
243243
}
@@ -276,7 +276,7 @@ $_tokens: (tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots(
276276
position: absolute;
277277
}
278278

279-
@include cdk.high-contrast(active, off) {
279+
@include cdk.high-contrast {
280280
border: none;
281281
}
282282
}
@@ -361,7 +361,7 @@ $_tokens: (tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots(
361361
}
362362
}
363363

364-
@include cdk.high-contrast(active, off) {
364+
@include cdk.high-contrast {
365365
$main-range-border: solid 1px;
366366
$comparison-range-border: dashed 1px;
367367

‎src/material/datepicker/calendar.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ $_tokens: tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots()
8383
margin: 0 $calendar-arrow-size 0 0;
8484
}
8585

86-
@include cdk.high-contrast(active, off) {
86+
@include cdk.high-contrast {
8787
// Setting the fill to `currentColor` doesn't work on Chromium browsers.
8888
fill: CanvasText;
8989
}

‎src/material/datepicker/date-range-input.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ $_tokens: tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots()
121121
-webkit-text-fill-color: transparent;
122122
transition: none;
123123

124-
@include cdk.high-contrast(active, off) {
124+
@include cdk.high-contrast {
125125
// In high contrast mode the browser will render the
126126
// placeholder despite the `color: transparent` above.
127127
opacity: 0;

‎src/material/datepicker/datepicker-toggle.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ $_tokens: (tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots(
2020
}
2121
}
2222

23-
@include cdk.high-contrast(active, off) {
23+
@include cdk.high-contrast {
2424
.mat-datepicker-toggle-default-icon {
2525
// On Chromium-based browsers the icon doesn't appear to inherit the text color in high
2626
// contrast mode so we have to set it explicitly. This is a no-op on IE and Firefox.

‎src/material/dialog/dialog.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ $_emit-fallbacks: true;
254254
@include token-utils.create-token-slot(justify-content, actions-alignment, $_emit-fallbacks);
255255
}
256256

257-
@include cdk.high-contrast(active, off) {
257+
@include cdk.high-contrast {
258258
border-top-color: CanvasText;
259259
}
260260

‎src/material/expansion/expansion-panel-header.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@
178178
}
179179
}
180180

181-
@include cdk.high-contrast(active, off) {
181+
@include cdk.high-contrast {
182182
.mat-expansion-panel-content {
183183
border-top: 1px solid;
184184
border-top-left-radius: 0;

‎src/material/expansion/expansion-panel.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
}
4646
}
4747

48-
@include cdk.high-contrast(active, off) {
48+
@include cdk.high-contrast {
4949
outline: solid 1px;
5050
}
5151

0 commit comments

Comments
 (0)
Please sign in to comment.