Skip to content

Commit

Permalink
feat(cdk/a11y): use native media query for high contrast detection (#…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
crisbeto authored Sep 3, 2024
1 parent f3df87b commit f4a02ad
Show file tree
Hide file tree
Showing 39 changed files with 79 additions and 136 deletions.
66 changes: 12 additions & 54 deletions src/cdk/a11y/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,63 +40,21 @@
@include a11y-visually-hidden;
}

/// Emits the mixin's content nested under `$selector-context` if `$selector-context`
/// is non-empty.
/// @param {String} selector-context The selector under which to nest the mixin's content.
@mixin _optionally-nest-content($selector-context) {
@if ($selector-context == '') {
@content;
}
@else {
#{$selector-context} {
@content;
}
}
}

/// Applies styles for users in high contrast mode. Note that this only applies
/// to Microsoft browsers. Chrome can be included by checking for the `html[hc]`
/// attribute, however Chrome handles high contrast differently.
/// Applies styles for users in high contrast mode.
///
/// @param {String} target Type of high contrast setting to target. Defaults to `active`, can be
/// `white-on-black` or `black-on-white`.
/// @param {String} encapsulation Whether to emit styles for view encapsulation. Values are:
/// * `on` - works for `Emulated`, `Native`, and `ShadowDom`
/// * `off` - works for `None`
/// * `any` - works for all encapsulation modes by emitting the CSS twice (default).
@mixin high-contrast($target: active, $encapsulation: 'any') {
@if ($target != 'active' and $target != 'black-on-white' and $target != 'white-on-black') {
/// @param {String} target Type of high contrast setting to target. Can be `active` or `none`.
/// Defaults to `active`.
/// @param {String} encapsulation No longer used and will be removed.
@mixin high-contrast($target: active, $encapsulation: null) {
// Historically we used to support `black-on-white` and `white-on-black` so we
// allow them here anyway. They'll be coerced to `active` below.
@if ($target != 'active' and $target != 'none' and $target != 'black-on-white' and
$target != 'white-on-black') {
@error 'Unknown cdk-high-contrast value "#{$target}" provided. ' +
'Allowed values are "active", "black-on-white", and "white-on-black"';
'Allowed values are "active" and "none"';
}

@if ($encapsulation != 'on' and $encapsulation != 'off' and $encapsulation != 'any') {
@error 'Unknown cdk-high-contrast encapsulation "#{$encapsulation}" provided. ' +
'Allowed values are "on", "off", and "any"';
}

// If the selector context has multiple parts, such as `.section, .region`, just doing
// `.cdk-high-contrast-xxx #{&}` will only apply the parent selector to the first part of the
// context. We address this by nesting the selector context under .cdk-high-contrast.
@at-root {
$selector-context: #{&};

@if ($encapsulation != 'on') {
// Note that if this selector is updated, the same change has to be made inside
// `_overlay.scss` which can't depend on this mixin due to some infrastructure limitations.
.cdk-high-contrast-#{$target} {
@include _optionally-nest-content($selector-context) {
@content;
}
}
}

@if ($encapsulation != 'off') {
.cdk-high-contrast-#{$target} :host {
@include _optionally-nest-content($selector-context) {
@content;
}
}
}
@media (forced-colors: #{if($target == none, none, active)}) {
@content;
}
}
25 changes: 5 additions & 20 deletions src/cdk/a11y/a11y.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,36 +229,21 @@ system. Otherwise, you can include this mixin in a global stylesheet.

#### Targeting high contrast users

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

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

```scss
@use '@angular/cdk';

button {
@include cdk.high-contrast() {
@include cdk.high-contrast {
outline: solid 1px;
}
}
```

The `high-contrast` mixin accepts two optional parameters, `$target` and `$encapsulation`.

The `$target` parameter allows you to specify which variation of high contrast mode your style
targets. The accepted values are `active` (default), `black-on-white`, and `white-on-black`. These
values correspond to the supported values for the
[`-ms-high-contrast` media query][ms-high-contrast].

The `$encapsulation` parameter affects how the emitted styles interact with style encapsulation.
The supported values are `on`, `off`, and `any`. The default value is `any`, which works for any
encapsulation scenario by emitting two selectors. Specifying either `on` or `off` slightly reduces
the amount of CSS emitted by limiting the styles to components with encapsulation enabled or
disabled, respectively. The styles emitted for encapsulated components work for both Angular's
emulated style encapsulation and for native Shadow DOM encapsulation.

[Windows High Contrast Mode]: https://support.microsoft.com/en-us/windows/use-high-contrast-mode-in-windows-10-fedc744c-90ac-69df-aed5-c8a90125e696
[ms-high-contrast]: https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/
The `high-contrast` mixin accepts the optional `$target` parameter which allows you to specify
the value of the `forced-color` media query. Its value can be either `active` or `none`.
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
display: block;
padding: 16px 24px;

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
// Note that normally we use 1px for high contrast outline, however here we use 3,
// because the popover is rendered on top of a table which already has some borders
// and doesn't have a backdrop. The thicker outline makes it easier to differentiate.
Expand Down
2 changes: 1 addition & 1 deletion src/material/autocomplete/autocomplete.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ div.mat-mdc-autocomplete-panel {
@include token-utils.create-token-slot(background-color, background-color);
}

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

Expand Down
2 changes: 1 addition & 1 deletion src/material/badge/badge.scss
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ $large-size: $default-size + 6;
}
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
outline: solid 1px;
border-radius: 0;
}
Expand Down
2 changes: 1 addition & 1 deletion src/material/bottom-sheet/bottom-sheet-container.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ $container-horizontal-padding: 16px !default;
@include token-utils.create-token-slot(letter-spacing, container-text-tracking);
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
outline: 1px solid;
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/material/button-toggle/button-toggle.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ $_standard-tokens: (

@include elevation.overridable-elevation(2);

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
outline: solid 1px;
}
}
Expand All @@ -65,7 +65,7 @@ $_standard-tokens: (
box-shadow: none;
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
outline: 0;
}
}
Expand Down Expand Up @@ -255,7 +255,7 @@ $_standard-tokens: (
}
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
// Changing the background color for the selected item won't be visible in high contrast mode.
// We fall back to using the overlay to draw a brighter, semi-transparent tint on top instead.
// It uses a border, because the browser will render it using a brighter color.
Expand Down
4 changes: 2 additions & 2 deletions src/material/button/button-high-contrast.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
.mat-mdc-unelevated-button:not(.mdc-button--outlined),
.mat-mdc-raised-button:not(.mdc-button--outlined),
.mat-mdc-outlined-button:not(.mdc-button--outlined),
.mat-mdc-icon-button {
@include cdk.high-contrast(active, off) {
.mat-mdc-icon-button.mat-mdc-icon-button {
@include cdk.high-contrast {
outline: solid 1px;
}
}
8 changes: 4 additions & 4 deletions src/material/checkbox/_checkbox-common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ $_fallback-size: 40px;
cursor: default;
pointer-events: none;

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
opacity: 0.5;
}
}
Expand Down Expand Up @@ -177,7 +177,7 @@ $_fallback-size: 40px;
@include token-utils.create-token-slot(color, selected-checkmark-color);
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
color: CanvasText;
}
}
Expand All @@ -188,7 +188,7 @@ $_fallback-size: 40px;
.mdc-checkbox__checkmark {
@include token-utils.create-token-slot(color, disabled-selected-checkmark-color);

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
color: CanvasText;
}
}
Expand Down Expand Up @@ -220,7 +220,7 @@ $_fallback-size: 40px;
@include token-utils.create-token-slot(border-color, selected-checkmark-color);
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
margin: 0 1px;
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/material/chips/chip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ $_avatar-trailing-padding: 8px;
stroke-dashoffset: 0;
}

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

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
outline: solid 1px;
}
}
Expand Down Expand Up @@ -721,7 +721,7 @@ $_avatar-trailing-padding: 8px;
// Single-selection chips show their selected state using a background color which won't be visible
// in high contrast mode. This isn't necessary in multi-selection since there's a checkmark.
.mat-mdc-chip-selected:not(.mat-mdc-chip-multiple) {
@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
outline-width: 3px;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/material/core/focus-indicators/_private.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ $default-border-radius: 4px;
}

// Enable the indicator in high contrast mode.
@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
@include _customize-focus-indicators((display: block));
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/material/core/option/option.scss
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ $_side-padding: 16px;
}
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
// In single selection mode, the selected option is indicated by changing its
// background color, but that doesn't work in high contrast mode. We add an
// alternate indication by rendering out a circle.
Expand Down
2 changes: 1 addition & 1 deletion src/material/core/ripple/_ripple.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
}

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

Expand Down
2 changes: 1 addition & 1 deletion src/material/core/style/_menu-common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ $icon-margin: 16px !default;
}

// Fix for Chromium-based browsers blending in the `currentColor` with the background.
@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
fill: CanvasText;
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/material/datepicker/calendar-body.scss
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ $_tokens: (tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots(
// Fade out the disabled cells so that they can be distinguished from the enabled ones. Note that
// ideally we'd use `color: GreyText` here which is what the browser uses for disabled buttons,
// but we can't because Firefox doesn't recognize it.
@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
opacity: 0.5;
}
}
Expand Down Expand Up @@ -276,7 +276,7 @@ $_tokens: (tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots(
position: absolute;
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
border: none;
}
}
Expand Down Expand Up @@ -361,7 +361,7 @@ $_tokens: (tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots(
}
}

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

Expand Down
2 changes: 1 addition & 1 deletion src/material/datepicker/calendar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ $_tokens: tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots()
margin: 0 $calendar-arrow-size 0 0;
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
// Setting the fill to `currentColor` doesn't work on Chromium browsers.
fill: CanvasText;
}
Expand Down
2 changes: 1 addition & 1 deletion src/material/datepicker/date-range-input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ $_tokens: tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots()
-webkit-text-fill-color: transparent;
transition: none;

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
// In high contrast mode the browser will render the
// placeholder despite the `color: transparent` above.
opacity: 0;
Expand Down
2 changes: 1 addition & 1 deletion src/material/datepicker/datepicker-toggle.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ $_tokens: (tokens-mat-datepicker.$prefix, tokens-mat-datepicker.get-token-slots(
}
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
.mat-datepicker-toggle-default-icon {
// On Chromium-based browsers the icon doesn't appear to inherit the text color in high
// contrast mode so we have to set it explicitly. This is a no-op on IE and Firefox.
Expand Down
2 changes: 1 addition & 1 deletion src/material/dialog/dialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ $_emit-fallbacks: true;
@include token-utils.create-token-slot(justify-content, actions-alignment, $_emit-fallbacks);
}

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

Expand Down
2 changes: 1 addition & 1 deletion src/material/expansion/expansion-panel-header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
}
}

@include cdk.high-contrast(active, off) {
@include cdk.high-contrast {
.mat-expansion-panel-content {
border-top: 1px solid;
border-top-left-radius: 0;
Expand Down
2 changes: 1 addition & 1 deletion src/material/expansion/expansion-panel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
}
}

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

Expand Down
Loading

0 comments on commit f4a02ad

Please sign in to comment.