diff --git a/core/.stylelintrc.yml b/core/.stylelintrc.yml index ce59b8ca208..94500aef447 100644 --- a/core/.stylelintrc.yml +++ b/core/.stylelintrc.yml @@ -11,6 +11,8 @@ ignoreFiles: - src/themes/ionic.mixins.scss - src/themes/ionic.functions.color.scss - src/themes/ionic.functions.string.scss + - src/themes/ionic.theme.default.scss + - src/css/themes/*.scss indentation: 2 diff --git a/core/src/components/action-sheet/action-sheet.ios.vars.scss b/core/src/components/action-sheet/action-sheet.ios.vars.scss index 3581c601a86..7aa19b4404b 100644 --- a/core/src/components/action-sheet/action-sheet.ios.vars.scss +++ b/core/src/components/action-sheet/action-sheet.ios.vars.scss @@ -111,7 +111,7 @@ $action-sheet-ios-button-background: linear-gradien $action-sheet-ios-button-background-activated: $text-color !default; /// @prop - Background color of the selected action sheet button -$action-sheet-ios-button-background-selected: var(--ion-color-step-150, $background-color) !default; +$action-sheet-ios-button-background-selected: var(--ion-color-step-150, var(--ion-background-color-step-150, $background-color)) !default; /// @prop - Destructive text color of the action sheet button $action-sheet-ios-button-destructive-text-color: ion-color(danger, base) !default; diff --git a/core/src/components/alert/alert.md.vars.scss b/core/src/components/alert/alert.md.vars.scss index 725f708f7b2..86965476ff3 100644 --- a/core/src/components/alert/alert.md.vars.scss +++ b/core/src/components/alert/alert.md.vars.scss @@ -208,7 +208,7 @@ $alert-md-radio-border-style: solid !default; $alert-md-radio-border-radius: 50% !default; /// @prop - Border color of the alert radio when off -$alert-md-radio-border-color-off: $text-color-step-450 !default; +$alert-md-radio-border-color-off: $background-color-step-550 !default; /// @prop - Border color of the alert radio when on $alert-md-radio-border-color-on: $alert-md-button-text-color !default; @@ -280,7 +280,7 @@ $alert-md-checkbox-border-style: solid !default; $alert-md-checkbox-border-radius: 2px !default; /// @prop - Border color of the checkbox in the alert when off -$alert-md-checkbox-border-color-off: $text-color-step-450 !default; +$alert-md-checkbox-border-color-off: $background-color-step-550 !default; /// @prop - Border color of the checkbox in the alert when on $alert-md-checkbox-border-color-on: $alert-md-button-text-color !default; diff --git a/core/src/components/breadcrumb/breadcrumb.ios.vars.scss b/core/src/components/breadcrumb/breadcrumb.ios.vars.scss index 3b495309db9..97a91a74ccf 100644 --- a/core/src/components/breadcrumb/breadcrumb.ios.vars.scss +++ b/core/src/components/breadcrumb/breadcrumb.ios.vars.scss @@ -4,22 +4,22 @@ // -------------------------------------------------- /// @prop - Color of the breadcrumb -$breadcrumb-ios-color: var(--ion-color-step-850, #2d4665) !default; +$breadcrumb-ios-color: var(--ion-color-step-850, var(--ion-text-color-step-150, #2d4665)) !default; /// @prop - Color of the active breadcrumb $breadcrumb-ios-color-active: var(--ion-text-color, #03060b) !default; /// @prop - Background color of the focused breadcrumb -$breadcrumb-ios-background-focused: var(--ion-color-step-50, rgba(233, 237, 243, 0.7)) !default; +$breadcrumb-ios-background-focused: var(--ion-color-step-50, var(--ion-background-color-step-50, rgba(233, 237, 243, 0.7))) !default; /// @prop - Color of the breadcrumb icon -$breadcrumb-ios-icon-color: var(--ion-color-step-400, #92a0b3) !default; +$breadcrumb-ios-icon-color: var(--ion-color-step-400, var(--ion-text-color-step-600, #92a0b3)) !default; /// @prop - Color of the breadcrumb icon when active -$breadcrumb-ios-icon-color-active: var(--ion-color-step-850, #242d39) !default; +$breadcrumb-ios-icon-color-active: var(--ion-color-step-850, var(--ion-text-color-step-150, #242d39)) !default; /// @prop - Color of the breadcrumb icon when focused -$breadcrumb-ios-icon-color-focused: var(--ion-color-step-750, #445b78) !default; +$breadcrumb-ios-icon-color-focused: var(--ion-color-step-750, var(--ion-text-color-step-250, #445b78)) !default; /// @prop - Color of the breadcrumb separator $breadcrumb-ios-separator-color: $breadcrumb-separator-color !default; @@ -28,7 +28,7 @@ $breadcrumb-ios-separator-color: $breadcrumb-separator-color ! $breadcrumb-ios-indicator-color: $breadcrumb-ios-separator-color !default; /// @prop - Background color of the breadcrumb indicator -$breadcrumb-ios-indicator-background: var(--ion-color-step-100, #e9edf3) !default; +$breadcrumb-ios-indicator-background: var(--ion-color-step-100, var(--ion-background-color-step-100, #e9edf3)) !default; /// @prop - Background color of the breadcrumb indicator when focused -$breadcrumb-ios-indicator-background-focused: var(--ion-color-step-150, #d9e0ea) !default; +$breadcrumb-ios-indicator-background-focused: var(--ion-color-step-150, var(--ion-background-color-step-150, #d9e0ea)) !default; diff --git a/core/src/components/breadcrumb/breadcrumb.md.vars.scss b/core/src/components/breadcrumb/breadcrumb.md.vars.scss index 81fc146e489..d638047353d 100644 --- a/core/src/components/breadcrumb/breadcrumb.md.vars.scss +++ b/core/src/components/breadcrumb/breadcrumb.md.vars.scss @@ -4,22 +4,22 @@ // -------------------------------------------------- /// @prop - Color of the breadcrumb -$breadcrumb-md-color: var(--ion-color-step-600, #677483) !default; +$breadcrumb-md-color: var(--ion-color-step-600, var(--ion-text-color-step-400, #677483)) !default; /// @prop - Color of the active breadcrumb $breadcrumb-md-color-active: var(--ion-text-color, #03060b) !default; /// @prop - Color of the focused breadcrumb -$breadcrumb-md-color-focused: var(--ion-color-step-800, #35404e) !default; +$breadcrumb-md-color-focused: var(--ion-color-step-800, var(--ion-text-color-step-200, #35404e)) !default; /// @prop - Background color of the focused breadcrumb -$breadcrumb-md-background-focused: var(--ion-color-step-50, #fff) !default; +$breadcrumb-md-background-focused: var(--ion-color-step-50, var(--ion-background-color-step-50, #fff)) !default; /// @prop - Color of the breadcrumb icon -$breadcrumb-md-icon-color: var(--ion-color-step-550, #7d8894) !default; +$breadcrumb-md-icon-color: var(--ion-color-step-550, var(--ion-text-color-step-450, #7d8894)) !default; /// @prop - Color of the breadcrumb icon when active -$breadcrumb-md-icon-color-active: var(--ion-color-step-850, #222d3a) !default; +$breadcrumb-md-icon-color-active: var(--ion-color-step-850, var(--ion-text-color-step-150, #222d3a)) !default; /// @prop - Margin top of the breadcrumb separator $breadcrumb-md-separator-margin-top: -1px !default; @@ -40,7 +40,7 @@ $breadcrumb-md-separator-color: $breadcrumb-separator-color $breadcrumb-md-indicator-color: $breadcrumb-md-separator-color !default; /// @prop - Background color of the breadcrumb indicator -$breadcrumb-md-indicator-background: var(--ion-color-step-100, #eef1f3) !default; +$breadcrumb-md-indicator-background: var(--ion-color-step-100, var(--ion-background-color-step-100, #eef1f3)) !default; /// @prop - Background color of the breadcrumb indicator when focused -$breadcrumb-md-indicator-background-focused: var(--ion-color-step-150, #dfe5e8) !default; +$breadcrumb-md-indicator-background-focused: var(--ion-color-step-150, var(--ion-background-color-step-150, #dfe5e8)) !default; diff --git a/core/src/components/breadcrumb/breadcrumb.vars.scss b/core/src/components/breadcrumb/breadcrumb.vars.scss index 3322672a5bd..43129a30d9f 100644 --- a/core/src/components/breadcrumb/breadcrumb.vars.scss +++ b/core/src/components/breadcrumb/breadcrumb.vars.scss @@ -12,4 +12,4 @@ $breadcrumb-baseline-font-size: 16px !default; $breadcrumb-font-size: dynamic-font($breadcrumb-baseline-font-size) !default; /// @prop - Color of the breadcrumb separator -$breadcrumb-separator-color: var(--ion-color-step-550, #73849a) !default; +$breadcrumb-separator-color: var(--ion-color-step-550, var(--ion-text-color-step-450, #73849a)) !default; diff --git a/core/src/components/datetime-button/datetime-button.scss b/core/src/components/datetime-button/datetime-button.scss index 0a09ef67c88..1570cfc4c72 100644 --- a/core/src/components/datetime-button/datetime-button.scss +++ b/core/src/components/datetime-button/datetime-button.scss @@ -23,7 +23,7 @@ border: none; - background: var(--ion-color-step-300, #edeef0); + background: var(--ion-color-step-300, var(--ion-background-color-step-300, #edeef0)); color: $text-color; diff --git a/core/src/components/datetime/datetime.md.scss b/core/src/components/datetime/datetime.md.scss index 0e25f96784c..7b3d78f5e81 100644 --- a/core/src/components/datetime/datetime.md.scss +++ b/core/src/components/datetime/datetime.md.scss @@ -3,7 +3,7 @@ @import "../../themes/ionic.globals.md"; :host { - --background: var(--ion-color-step-100, #ffffff); + --background: var(--ion-color-step-100, var(--ion-background-color-step-100, #ffffff)); --title-color: #{current-color(contrast)}; } diff --git a/core/src/components/datetime/datetime.scss b/core/src/components/datetime/datetime.scss index ede4c23a544..9c7a4b0a2c8 100644 --- a/core/src/components/datetime/datetime.scss +++ b/core/src/components/datetime/datetime.scss @@ -401,7 +401,7 @@ border: none; - background: var(--ion-color-step-300, #edeef0); + background: var(--ion-color-step-300, var(--ion-background-color-step-300, #edeef0)); color: $text-color; diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index e2504b032a5..e8fae76c908 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -402,7 +402,7 @@ .input-bottom .helper-text { display: block; - color: #{$background-color-step-550}; + color: #{$text-color-step-450}; } :host(.ion-touched.ion-invalid) .input-bottom .error-text { @@ -424,7 +424,7 @@ */ @include margin-horizontal(auto, null); - color: #{$background-color-step-550}; + color: #{$text-color-step-450}; white-space: nowrap; diff --git a/core/src/components/item/item.ios.vars.scss b/core/src/components/item/item.ios.vars.scss index 6fb565fe69a..95997ab1f53 100644 --- a/core/src/components/item/item.ios.vars.scss +++ b/core/src/components/item/item.ios.vars.scss @@ -25,7 +25,7 @@ $item-ios-paragraph-margin-start: $item-ios-paragraph-margin-end !def $item-ios-paragraph-font-size: dynamic-font(14px) !default; /// @prop - Color of the item paragraph -$item-ios-paragraph-text-color: rgba($text-color-rgb, .4) !default; +$item-ios-paragraph-text-color: var(--ion-text-color-step-550, #a3a3a3) !default; /// @prop - Width of the avatar in the item $item-ios-avatar-width: 36px !default; diff --git a/core/src/components/modal/modal.scss b/core/src/components/modal/modal.scss index 6e8f7c6df36..02b7128c99c 100644 --- a/core/src/components/modal/modal.scss +++ b/core/src/components/modal/modal.scss @@ -129,7 +129,7 @@ ion-backdrop { border: 0; - background: var(--ion-color-step-350, #c0c0be); + background: var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be)); cursor: pointer; diff --git a/core/src/components/picker/picker.ios.scss b/core/src/components/picker/picker.ios.scss index e13e3eb1b99..76f99175c6d 100644 --- a/core/src/components/picker/picker.ios.scss +++ b/core/src/components/picker/picker.ios.scss @@ -10,5 +10,5 @@ } :host .picker-highlight { - background: var(--highlight-background, var(--ion-color-step-150, #eeeeef)); + background: var(--highlight-background, var(--ion-color-step-150, var(--ion-background-color-step-150, #eeeeef))); } diff --git a/core/src/components/range/range.ios.vars.scss b/core/src/components/range/range.ios.vars.scss index fe794984f5a..30c72b89a81 100644 --- a/core/src/components/range/range.ios.vars.scss +++ b/core/src/components/range/range.ios.vars.scss @@ -27,7 +27,7 @@ $range-ios-hit-height: $range-ios-slider-height !default; $range-ios-bar-height: 4px !default; /// @prop - Background of the range bar -$range-ios-bar-background-color: var(--ion-color-step-900, #e6e6e6) !default; +$range-ios-bar-background-color: var(--ion-color-step-900, var(--ion-background-color-step-900, #e6e6e6)) !default; /// @prop - Border radius of the range bar $range-ios-bar-border-radius: 2px !default; diff --git a/core/src/components/refresher/refresher.ios.vars.scss b/core/src/components/refresher/refresher.ios.vars.scss index fbc1f7c432a..bc4dc4ed9cd 100644 --- a/core/src/components/refresher/refresher.ios.vars.scss +++ b/core/src/components/refresher/refresher.ios.vars.scss @@ -7,7 +7,7 @@ $refresher-ios-icon-color: $text-color !default; $refresher-ios-text-color: $text-color !default; /// @prop - Color of the native refresher spinner -$refresher-ios-native-spinner-color: var(--ion-color-step-450, #747577) !default; +$refresher-ios-native-spinner-color: var(--ion-color-step-450, var(--ion-background-color-step-450, #747577)) !default; /// @prop - Width of the native refresher spinner $refresher-ios-native-spinner-width: 32px !default; diff --git a/core/src/components/refresher/refresher.md.vars.scss b/core/src/components/refresher/refresher.md.vars.scss index 7d45fb463f2..3e893b0ce86 100644 --- a/core/src/components/refresher/refresher.md.vars.scss +++ b/core/src/components/refresher/refresher.md.vars.scss @@ -10,10 +10,10 @@ $refresher-md-text-color: $text-color !default; $refresher-md-native-spinner-color: #{ion-color(primary, base)} !default; /// @prop - Border of the native refresher spinner -$refresher-md-native-spinner-border: 1px solid var(--ion-color-step-200, #ececec) !default; +$refresher-md-native-spinner-border: 1px solid var(--ion-color-step-200, var(--ion-background-color-step-200, #ececec)) !default; /// @prop - Background of the native refresher spinner -$refresher-md-native-spinner-background: var(--ion-color-step-250, #ffffff) !default; +$refresher-md-native-spinner-background: var(--ion-color-step-250, var(--ion-background-color-step-250, #ffffff)) !default; /// @prop - Box shadow of the native refresher spinner $refresher-md-native-spinner-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.1) !default; diff --git a/core/src/components/segment-button/segment-button.ios.vars.scss b/core/src/components/segment-button/segment-button.ios.vars.scss index c02544fc341..77dce9d42dc 100644 --- a/core/src/components/segment-button/segment-button.ios.vars.scss +++ b/core/src/components/segment-button/segment-button.ios.vars.scss @@ -13,7 +13,7 @@ $segment-button-ios-background-checked: $segment-button-ios-backgr $segment-button-ios-color: $text-color !default; /// @prop - Background of the checked segment button indicator -$segment-button-ios-indicator-color: var(--ion-color-step-350, $background-color) !default; +$segment-button-ios-indicator-color: var(--ion-color-step-350, var(--ion-background-color-step-350, $background-color)) !default; /// @prop - Margin of the segment button $segment-button-ios-margin: 2px !default; diff --git a/core/src/components/textarea/textarea.scss b/core/src/components/textarea/textarea.scss index 8cfcb10447c..6191cf6e195 100644 --- a/core/src/components/textarea/textarea.scss +++ b/core/src/components/textarea/textarea.scss @@ -434,7 +434,7 @@ .textarea-bottom .helper-text { display: block; - color: #{$background-color-step-550}; + color: #{$text-color-step-450}; } :host(.ion-touched.ion-invalid) .textarea-bottom .error-text { @@ -456,7 +456,7 @@ */ @include margin-horizontal(auto, null); - color: #{$background-color-step-550}; + color: #{$text-color-step-450}; white-space: nowrap; diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts b/core/src/components/toast/test/a11y/toast.e2e.ts index fc6875c7a3f..5b13767d3a3 100644 --- a/core/src/components/toast/test/a11y/toast.e2e.ts +++ b/core/src/components/toast/test/a11y/toast.e2e.ts @@ -230,3 +230,48 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { }); }); }); + +/** + * High contrast mode tests + */ +configs({ directions: ['ltr'], themes: ['high-contrast-dark', 'high-contrast'] }).forEach( + ({ title, config, screenshot }) => { + test.describe(title('toast: high contrast: buttons'), () => { + test.beforeEach(async ({ page }) => { + await page.setContent( + ` + + + `, + config + ); + }); + + test('should not have visual regressions', async ({ page }) => { + const toast = page.locator('ion-toast'); + + await expect(toast).toBeVisible(); + + const toastWrapper = toast.locator('.toast-wrapper'); + await expect(toastWrapper).toHaveScreenshot(screenshot(`toast-high-contrast-buttons`)); + }); + + test('should pass AAA guidelines', async ({ page }) => { + const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent'); + + await ionToastDidPresent.next(); + + const results = await new AxeBuilder({ page }) + .options({ rules: { 'color-contrast-enhanced': { enabled: true } } }) + .analyze(); + expect(results.violations).toEqual([]); + }); + }); + } +); diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-Mobile-Chrome-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..c8eb89be30f Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-Mobile-Firefox-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..6c7835bd44b Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-Mobile-Safari-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-Mobile-Safari-linux.png new file mode 100644 index 00000000000..08458017843 Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-dark-Mobile-Chrome-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-dark-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..6301ba1f54e Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-dark-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-dark-Mobile-Firefox-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-dark-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..73e79f4cc3b Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-dark-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-dark-Mobile-Safari-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-dark-Mobile-Safari-linux.png new file mode 100644 index 00000000000..7d39698b526 Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-ios-ltr-high-contrast-dark-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-Mobile-Chrome-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..fc9d3aa7d83 Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-Mobile-Firefox-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..278c2934999 Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-Mobile-Safari-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-Mobile-Safari-linux.png new file mode 100644 index 00000000000..5772656a515 Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-dark-Mobile-Chrome-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-dark-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..d3cfbfd22a5 Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-dark-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-dark-Mobile-Firefox-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-dark-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..d24de218c23 Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-dark-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-dark-Mobile-Safari-linux.png b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-dark-Mobile-Safari-linux.png new file mode 100644 index 00000000000..0f9611f267a Binary files /dev/null and b/core/src/components/toast/test/a11y/toast.e2e.ts-snapshots/toast-high-contrast-buttons-md-ltr-high-contrast-dark-Mobile-Safari-linux.png differ diff --git a/core/src/components/toast/toast.md.vars.scss b/core/src/components/toast/toast.md.vars.scss index a82f2154104..158815aec68 100644 --- a/core/src/components/toast/toast.md.vars.scss +++ b/core/src/components/toast/toast.md.vars.scss @@ -4,7 +4,7 @@ // -------------------------------------------------- /// @prop - Background of the toast -$toast-md-background: $text-color-step-200 !default; +$toast-md-background: $background-color-step-800 !default; /// @prop - Box shadow of the toast $toast-md-box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), @@ -14,7 +14,7 @@ $toast-md-box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, $toast-md-font-size: dynamic-font(14px) !default; /// @prop - Color of the toast -$toast-md-color: $background-color-step-50 !default; +$toast-md-color: $text-color-step-950 !default; /// @prop - Border radius of the toast wrapper $toast-md-border-radius: 4px !default; @@ -77,7 +77,7 @@ $toast-md-button-opacity-hover: 0.08 !default; $toast-md-button-background-color-hover: ion-color(primary, base, $toast-md-button-opacity-hover) !default; /// @prop - Text color of the cancel toast button -$toast-md-button-cancel-text-color: $background-color-step-100 !default; +$toast-md-button-cancel-text-color: $text-color-step-900 !default; /// @prop - Background color of the cancel toast button on hover $toast-md-button-cancel-background-color-hover: rgba($background-color-rgb, $toast-md-button-opacity-hover) !default; diff --git a/core/src/css/themes/dark.scss b/core/src/css/themes/dark.scss index d5474ee9d6a..983273e3931 100644 --- a/core/src/css/themes/dark.scss +++ b/core/src/css/themes/dark.scss @@ -6,48 +6,56 @@ --ion-color-primary-contrast-rgb: 0, 0, 0; --ion-color-primary-shade: #447ce0; --ion-color-primary-tint: #5f98ff; + --ion-color-secondary: #62bdff; --ion-color-secondary-rgb: 98, 189, 255; --ion-color-secondary-contrast: #000000; --ion-color-secondary-contrast-rgb: 0, 0, 0; --ion-color-secondary-shade: #56a6e0; --ion-color-secondary-tint: #72c4ff; + --ion-color-tertiary: #8482fb; --ion-color-tertiary-rgb: 132, 130, 251; --ion-color-tertiary-contrast: #000000; --ion-color-tertiary-contrast-rgb: 0, 0, 0; --ion-color-tertiary-shade: #7472dd; --ion-color-tertiary-tint: #908ffb; + --ion-color-success: #2dd55b; --ion-color-success-rgb: 45, 213, 91; --ion-color-success-contrast: #000000; --ion-color-success-contrast-rgb: 0, 0, 0; --ion-color-success-shade: #28bb50; --ion-color-success-tint: #42d96b; + --ion-color-warning: #ffce31; --ion-color-warning-rgb: 255, 206, 49; --ion-color-warning-contrast: #000000; --ion-color-warning-contrast-rgb: 0, 0, 0; --ion-color-warning-shade: #e0b52b; --ion-color-warning-tint: #ffd346; + --ion-color-danger: #f56570; --ion-color-danger-rgb: 245, 101, 112; --ion-color-danger-contrast: #000000; --ion-color-danger-contrast-rgb: 0, 0, 0; --ion-color-danger-shade: #d85963; --ion-color-danger-tint: #f6747e; + --ion-color-dark: #f4f5f8; --ion-color-dark-rgb: 244, 245, 248; --ion-color-dark-contrast: #000000; --ion-color-dark-contrast-rgb: 0, 0, 0; --ion-color-dark-shade: #d7d8da; --ion-color-dark-tint: #f5f6f9; + --ion-color-medium: #989aa2; --ion-color-medium-rgb: 152, 154, 162; --ion-color-medium-contrast: #000000; --ion-color-medium-contrast-rgb: 0, 0, 0; --ion-color-medium-shade: #86888f; --ion-color-medium-tint: #a2a4ab; + --ion-color-light: #222428; --ion-color-light-rgb: 34, 36, 40; --ion-color-light-contrast: #ffffff; @@ -61,35 +69,58 @@ & { --ion-background-color: #000000; --ion-background-color-rgb: 0, 0, 0; + --ion-text-color: #ffffff; --ion-text-color-rgb: 255, 255, 255; - --ion-color-step-50: #0d0d0d; - --ion-color-step-100: #1a1a1a; - --ion-color-step-150: #262626; - --ion-color-step-200: #333333; - --ion-color-step-250: #404040; - --ion-color-step-300: #4d4d4d; - --ion-color-step-350: #595959; - --ion-color-step-400: #666666; - --ion-color-step-450: #737373; - --ion-color-step-500: #808080; - --ion-color-step-550: #8c8c8c; - --ion-color-step-600: #999999; - --ion-color-step-650: #a6a6a6; - --ion-color-step-700: #b3b3b3; - --ion-color-step-750: #bfbfbf; - --ion-color-step-800: #cccccc; - --ion-color-step-850: #d9d9d9; - --ion-color-step-900: #e6e6e6; - --ion-color-step-950: #f2f2f2; + + --ion-background-color-step-50: #0d0d0d; + --ion-background-color-step-100: #1a1a1a; + --ion-background-color-step-150: #262626; + --ion-background-color-step-200: #333333; + --ion-background-color-step-250: #404040; + --ion-background-color-step-300: #4d4d4d; + --ion-background-color-step-350: #595959; + --ion-background-color-step-400: #666666; + --ion-background-color-step-450: #737373; + --ion-background-color-step-500: #808080; + --ion-background-color-step-550: #8c8c8c; + --ion-background-color-step-600: #999999; + --ion-background-color-step-650: #a6a6a6; + --ion-background-color-step-700: #b3b3b3; + --ion-background-color-step-750: #bfbfbf; + --ion-background-color-step-800: #cccccc; + --ion-background-color-step-850: #d9d9d9; + --ion-background-color-step-900: #e6e6e6; + --ion-background-color-step-950: #f2f2f2; + + --ion-text-color-step-50: #f2f2f2; + --ion-text-color-step-100: #e6e6e6; + --ion-text-color-step-150: #d9d9d9; + --ion-text-color-step-200: #cccccc; + --ion-text-color-step-250: #bfbfbf; + --ion-text-color-step-300: #b3b3b3; + --ion-text-color-step-350: #a6a6a6; + --ion-text-color-step-400: #999999; + --ion-text-color-step-450: #8c8c8c; + --ion-text-color-step-500: #808080; + --ion-text-color-step-550: #737373; + --ion-text-color-step-600: #666666; + --ion-text-color-step-650: #595959; + --ion-text-color-step-700: #4d4d4d; + --ion-text-color-step-750: #404040; + --ion-text-color-step-800: #333333; + --ion-text-color-step-850: #262626; + --ion-text-color-step-900: #1a1a1a; + --ion-text-color-step-950: #0d0d0d; + --ion-item-background: #000000; --ion-card-background: #1c1c1d; } & ion-modal { - --ion-background-color: var(--ion-color-step-100); - --ion-toolbar-background: var(--ion-color-step-150); - --ion-toolbar-border-color: var(--ion-color-step-250); + --ion-background-color: var(--ion-color-step-100, var(--ion-background-color-step-100)); + --ion-toolbar-background: var(--ion-color-step-150, var(--ion-background-color-step-150)); + --ion-toolbar-border-color: var(--ion-color-step-250, var(--ion-background-color-step-250)); } } @@ -97,28 +128,52 @@ & { --ion-background-color: #121212; --ion-background-color-rgb: 18, 18, 18; + --ion-text-color: #ffffff; --ion-text-color-rgb: 255, 255, 255; + --ion-border-color: #222222; - --ion-color-step-50: #1e1e1e; - --ion-color-step-100: #2a2a2a; - --ion-color-step-150: #363636; - --ion-color-step-200: #414141; - --ion-color-step-250: #4d4d4d; - --ion-color-step-300: #595959; - --ion-color-step-350: #656565; - --ion-color-step-400: #717171; - --ion-color-step-450: #7d7d7d; - --ion-color-step-500: #898989; - --ion-color-step-550: #949494; - --ion-color-step-600: #a0a0a0; - --ion-color-step-650: #acacac; - --ion-color-step-700: #b8b8b8; - --ion-color-step-750: #c4c4c4; - --ion-color-step-800: #d0d0d0; - --ion-color-step-850: #dbdbdb; - --ion-color-step-900: #e7e7e7; - --ion-color-step-950: #f3f3f3; + + --ion-background-color-step-50: #1e1e1e; + --ion-background-color-step-100: #2a2a2a; + --ion-background-color-step-150: #363636; + --ion-background-color-step-200: #414141; + --ion-background-color-step-250: #4d4d4d; + --ion-background-color-step-300: #595959; + --ion-background-color-step-350: #656565; + --ion-background-color-step-400: #717171; + --ion-background-color-step-450: #7d7d7d; + --ion-background-color-step-500: #898989; + --ion-background-color-step-550: #949494; + --ion-background-color-step-600: #a0a0a0; + --ion-background-color-step-650: #acacac; + --ion-background-color-step-700: #b8b8b8; + --ion-background-color-step-750: #c4c4c4; + --ion-background-color-step-800: #d0d0d0; + --ion-background-color-step-850: #dbdbdb; + --ion-background-color-step-900: #e7e7e7; + --ion-background-color-step-950: #f3f3f3; + + --ion-text-color-step-50: #f3f3f3; + --ion-text-color-step-100: #e7e7e7; + --ion-text-color-step-150: #dbdbdb; + --ion-text-color-step-200: #d0d0d0; + --ion-text-color-step-250: #c4c4c4; + --ion-text-color-step-300: #b8b8b8; + --ion-text-color-step-350: #acacac; + --ion-text-color-step-400: #a0a0a0; + --ion-text-color-step-450: #949494; + --ion-text-color-step-500: #898989; + --ion-text-color-step-550: #7d7d7d; + --ion-text-color-step-600: #717171; + --ion-text-color-step-650: #656565; + --ion-text-color-step-700: #595959; + --ion-text-color-step-750: #4d4d4d; + --ion-text-color-step-800: #414141; + --ion-text-color-step-850: #363636; + --ion-text-color-step-900: #2a2a2a; + --ion-text-color-step-950: #1e1e1e; + --ion-item-background: #1e1e1e; --ion-toolbar-background: #1f1f1f; --ion-tab-bar-background: #1f1f1f; diff --git a/core/src/css/themes/high-contrast-dark.always.scss b/core/src/css/themes/high-contrast-dark.always.scss new file mode 100644 index 00000000000..46594686b2a --- /dev/null +++ b/core/src/css/themes/high-contrast-dark.always.scss @@ -0,0 +1,13 @@ +@import "./high-contrast-dark"; + +:root { + @include high-contrast-dark-base-theme(); +} + +:root.ios { + @include high-contrast-dark-ios-theme(); +} + +:root.md { + @include high-contrast-dark-md-theme(); +} diff --git a/core/src/css/themes/high-contrast-dark.class.scss b/core/src/css/themes/high-contrast-dark.class.scss new file mode 100644 index 00000000000..d73e528c190 --- /dev/null +++ b/core/src/css/themes/high-contrast-dark.class.scss @@ -0,0 +1,13 @@ +@import "./high-contrast-dark"; + +.ion-theme-high-contrast.ion-theme-dark { + @include high-contrast-dark-base-theme(); +} + +.ion-theme-high-contrast.ion-theme-dark.ios { + @include high-contrast-dark-ios-theme(); +} + +.ion-theme-high-contrast.ion-theme-dark.md { + @include high-contrast-dark-md-theme(); +} diff --git a/core/src/css/themes/high-contrast-dark.scss b/core/src/css/themes/high-contrast-dark.scss new file mode 100644 index 00000000000..8ebed223855 --- /dev/null +++ b/core/src/css/themes/high-contrast-dark.scss @@ -0,0 +1,263 @@ +@use "sass:map"; +@import "../../themes/ionic.functions.color"; + +$primary: #7cabff !default; +$secondary: #62bdff !default; +$tertiary: #b6b9f9 !default; +$success: #4ada71 !default; +$warning: #ffce31 !default; +$danger: #fc9aa2 !default; +$light: #222428 !default; +$medium: #a8aab3 !default; +$dark: #f4f5f8 !default; + +$colors: ( + primary: ( + base: $primary, + contrast: #000, + shade: get-color-shade($primary), + tint: get-color-tint($primary) + ), + secondary: ( + base: $secondary, + contrast: #000, + shade: get-color-shade($secondary), + tint: get-color-tint($secondary) + ), + tertiary: ( + base: $tertiary, + contrast: #000, + shade: get-color-shade($tertiary), + tint: get-color-tint($tertiary) + ), + success: ( + base: $success, + contrast: #000, + shade: get-color-shade($success), + tint: get-color-tint($success) + ), + warning: ( + base: $warning, + contrast: #000, + shade: get-color-shade($warning), + tint: get-color-tint($warning) + ), + danger: ( + base: $danger, + contrast: #000, + shade: get-color-shade($danger), + tint: get-color-tint($danger) + ), + light: ( + base: $light, + contrast: #fff, + shade: get-color-shade($light), + tint: get-color-tint($light) + ), + medium: ( + base: $medium, + contrast: #000, + shade: get-color-shade($medium), + tint: get-color-tint($medium) + ), + dark: ( + base: $dark, + contrast: #000, + shade: get-color-shade($dark), + tint: get-color-tint($dark) + ) +) !default; + +/// Text step colors are generated based on +/// how dark or light text can be. The darkest +/// text color is usually the default text color (white or black). +/// The darker the $lightest-text-color is, the darker each +/// stepped color is. If you want to increase text contrast, +/// make $lightest-text-color darker. +$text-color: #ffffff; + +$darkest-text-color: #888888; +$lightest-text-color: $text-color; + +/// Loop through each color object above +/// and generate CSS Variables for each +/// color. Do not use the ion-color function +/// here because that will set the variable +/// to evaluate to itself with a fallback. +/// For example: +/// --ion-color-primary: #{ion-color(primary, base)}; +/// Maps to: +/// --ion-color-primary: var(--ion-color-primary, #...); + +/// We apply certain styles to the per-mode classes (ios and md) +/// as opposed to setting everything in :root +// so any high contrast dark styles override the standard +/// contrast dark styles. +@mixin high-contrast-dark-base-theme() { + & { + @each $color-name, $value in $colors { + --ion-color-#{$color-name}: #{map.get($value, base)}; + --ion-color-#{$color-name}-rgb: #{color-to-rgb-list(map.get($value, base))}; + --ion-color-#{$color-name}-contrast: #{map.get($value, contrast)}; + --ion-color-#{$color-name}-contrast-rgb: #{color-to-rgb-list(map.get($value, contrast))}; + --ion-color-#{$color-name}-shade: #{map.get($value, shade)}; + --ion-color-#{$color-name}-tint: #{map.get($value, tint)}; + } + + --ion-placeholder-opacity: 0.8; + } +} + +@mixin high-contrast-dark-ios-theme() { + & { + $background-color: #000000; + + --ion-background-color: #{$background-color}; + --ion-background-color-rgb: #{color-to-rgb-list($background-color)}; + + --ion-text-color: #{$text-color}; + --ion-text-color-rgb: #{color-to-rgb-list($text-color)}; + + --ion-item-background: #000000; + --ion-card-background: #1c1c1d; + + /// Only the item borders should increase in contrast + /// Borders for elements like toolbars should remain the same + --ion-item-border-color: var(--ion-background-color-step-400); + + --ion-text-color-step-50: #{mix($darkest-text-color, $lightest-text-color, 5%)}; + --ion-text-color-step-100: #{mix($darkest-text-color, $lightest-text-color, 10%)}; + --ion-text-color-step-150: #{mix($darkest-text-color, $lightest-text-color, 15%)}; + --ion-text-color-step-200: #{mix($darkest-text-color, $lightest-text-color, 20%)}; + --ion-text-color-step-250: #{mix($darkest-text-color, $lightest-text-color, 25%)}; + --ion-text-color-step-300: #{mix($darkest-text-color, $lightest-text-color, 30%)}; + --ion-text-color-step-350: #{mix($darkest-text-color, $lightest-text-color, 35%)}; + --ion-text-color-step-400: #{mix($darkest-text-color, $lightest-text-color, 40%)}; + --ion-text-color-step-450: #{mix($darkest-text-color, $lightest-text-color, 45%)}; + --ion-text-color-step-500: #{mix($darkest-text-color, $lightest-text-color, 50%)}; + --ion-text-color-step-550: #{mix($darkest-text-color, $lightest-text-color, 55%)}; + --ion-text-color-step-600: #{mix($darkest-text-color, $lightest-text-color, 60%)}; + --ion-text-color-step-650: #{mix($darkest-text-color, $lightest-text-color, 65%)}; + --ion-text-color-step-700: #{mix($darkest-text-color, $lightest-text-color, 70%)}; + --ion-text-color-step-750: #{mix($darkest-text-color, $lightest-text-color, 75%)}; + --ion-text-color-step-800: #{mix($darkest-text-color, $lightest-text-color, 80%)}; + --ion-text-color-step-850: #{mix($darkest-text-color, $lightest-text-color, 85%)}; + --ion-text-color-step-900: #{mix($darkest-text-color, $lightest-text-color, 90%)}; + --ion-text-color-step-950: #{mix($darkest-text-color, $lightest-text-color, 95%)}; + + --ion-background-color-step-50: #{mix($text-color, $background-color, 5%)}; + --ion-background-color-step-100: #{mix($text-color, $background-color, 10%)}; + --ion-background-color-step-150: #{mix($text-color, $background-color, 15%)}; + --ion-background-color-step-200: #{mix($text-color, $background-color, 20%)}; + --ion-background-color-step-250: #{mix($text-color, $background-color, 25%)}; + --ion-background-color-step-300: #{mix($text-color, $background-color, 30%)}; + --ion-background-color-step-350: #{mix($text-color, $background-color, 35%)}; + --ion-background-color-step-400: #{mix($text-color, $background-color, 40%)}; + --ion-background-color-step-450: #{mix($text-color, $background-color, 45%)}; + --ion-background-color-step-500: #{mix($text-color, $background-color, 50%)}; + --ion-background-color-step-550: #{mix($text-color, $background-color, 55%)}; + --ion-background-color-step-600: #{mix($text-color, $background-color, 60%)}; + --ion-background-color-step-650: #{mix($text-color, $background-color, 65%)}; + --ion-background-color-step-700: #{mix($text-color, $background-color, 70%)}; + --ion-background-color-step-750: #{mix($text-color, $background-color, 75%)}; + --ion-background-color-step-800: #{mix($text-color, $background-color, 80%)}; + --ion-background-color-step-850: #{mix($text-color, $background-color, 85%)}; + --ion-background-color-step-900: #{mix($text-color, $background-color, 90%)}; + --ion-background-color-step-950: #{mix($text-color, $background-color, 95%)}; + } + + // Modal + // -------------------------------------------------- + + & ion-modal { + --ion-background-color: var(--ion-background-color-step-100); + --ion-toolbar-background: var(--ion-background-color-step-150); + --ion-toolbar-border-color: var(--ion-background-color-step-250); + } +} + +@mixin high-contrast-dark-md-theme() { + & { + $background-color: #121212; + + --ion-background-color: #{$background-color}; + --ion-background-color-rgb: #{color-to-rgb-list($background-color)}; + + --ion-text-color: #{$text-color}; + --ion-text-color-rgb: #{color-to-rgb-list($text-color)}; + + --ion-border-color: #222222; + + --ion-item-background: #1e1e1e; + --ion-toolbar-background: #1f1f1f; + --ion-tab-bar-background: #1f1f1f; + --ion-card-background: #1e1e1e; + + /// Only the item borders should increase in contrast + /// Borders for elements like toolbars should remain the same + --ion-item-border-color: var(--ion-background-color-step-400); + + --ion-text-color-step-50: #{mix($darkest-text-color, $lightest-text-color, 5%)}; + --ion-text-color-step-100: #{mix($darkest-text-color, $lightest-text-color, 10%)}; + --ion-text-color-step-150: #{mix($darkest-text-color, $lightest-text-color, 15%)}; + --ion-text-color-step-200: #{mix($darkest-text-color, $lightest-text-color, 20%)}; + --ion-text-color-step-250: #{mix($darkest-text-color, $lightest-text-color, 25%)}; + --ion-text-color-step-300: #{mix($darkest-text-color, $lightest-text-color, 30%)}; + --ion-text-color-step-350: #{mix($darkest-text-color, $lightest-text-color, 35%)}; + --ion-text-color-step-400: #{mix($darkest-text-color, $lightest-text-color, 40%)}; + --ion-text-color-step-450: #{mix($darkest-text-color, $lightest-text-color, 45%)}; + --ion-text-color-step-500: #{mix($darkest-text-color, $lightest-text-color, 50%)}; + --ion-text-color-step-550: #{mix($darkest-text-color, $lightest-text-color, 55%)}; + --ion-text-color-step-600: #{mix($darkest-text-color, $lightest-text-color, 60%)}; + --ion-text-color-step-650: #{mix($darkest-text-color, $lightest-text-color, 65%)}; + --ion-text-color-step-700: #{mix($darkest-text-color, $lightest-text-color, 70%)}; + --ion-text-color-step-750: #{mix($darkest-text-color, $lightest-text-color, 75%)}; + --ion-text-color-step-800: #{mix($darkest-text-color, $lightest-text-color, 80%)}; + --ion-text-color-step-850: #{mix($darkest-text-color, $lightest-text-color, 85%)}; + --ion-text-color-step-900: #{mix($darkest-text-color, $lightest-text-color, 90%)}; + --ion-text-color-step-950: #{mix($darkest-text-color, $lightest-text-color, 95%)}; + + --ion-background-color-step-50: #{mix($text-color, $background-color, 5%)}; + --ion-background-color-step-100: #{mix($text-color, $background-color, 10%)}; + --ion-background-color-step-150: #{mix($text-color, $background-color, 15%)}; + --ion-background-color-step-200: #{mix($text-color, $background-color, 20%)}; + --ion-background-color-step-250: #{mix($text-color, $background-color, 25%)}; + --ion-background-color-step-300: #{mix($text-color, $background-color, 30%)}; + --ion-background-color-step-350: #{mix($text-color, $background-color, 35%)}; + --ion-background-color-step-400: #{mix($text-color, $background-color, 40%)}; + --ion-background-color-step-450: #{mix($text-color, $background-color, 45%)}; + --ion-background-color-step-500: #{mix($text-color, $background-color, 50%)}; + --ion-background-color-step-550: #{mix($text-color, $background-color, 55%)}; + --ion-background-color-step-600: #{mix($text-color, $background-color, 60%)}; + --ion-background-color-step-650: #{mix($text-color, $background-color, 65%)}; + --ion-background-color-step-700: #{mix($text-color, $background-color, 70%)}; + --ion-background-color-step-750: #{mix($text-color, $background-color, 75%)}; + --ion-background-color-step-800: #{mix($text-color, $background-color, 80%)}; + --ion-background-color-step-850: #{mix($text-color, $background-color, 85%)}; + --ion-background-color-step-900: #{mix($text-color, $background-color, 90%)}; + --ion-background-color-step-950: #{mix($text-color, $background-color, 95%)}; + } + + // Toast + // -------------------------------------------------- + + & ion-toast { + --color: var(--ion-background-color); + } + + & ion-toast::part(button) { + // Fallback for browsers that don't support color-mix + color: var(--color); + } + + /* stylelint-disable-next-line order/order */ + @supports (color: color-mix(in lch, plum, pink)) { + & ion-toast::part(button) { + color: color-mix(in srgb, var(--color) 73%, var(--button-color)); + } + } + + & ion-toast::part(button cancel) { + color: var(--color); + } +} diff --git a/core/src/css/themes/high-contrast-dark.system.scss b/core/src/css/themes/high-contrast-dark.system.scss new file mode 100644 index 00000000000..f47602edd01 --- /dev/null +++ b/core/src/css/themes/high-contrast-dark.system.scss @@ -0,0 +1,15 @@ +@import "./high-contrast-dark"; + +@media (prefers-contrast: more) and (prefers-color-scheme: dark) { + :root { + @include high-contrast-dark-base-theme(); + } + + :root.ios { + @include high-contrast-dark-ios-theme(); + } + + :root.md { + @include high-contrast-dark-md-theme(); + } +} diff --git a/core/src/css/themes/high-contrast.always.scss b/core/src/css/themes/high-contrast.always.scss new file mode 100644 index 00000000000..70f5498d924 --- /dev/null +++ b/core/src/css/themes/high-contrast.always.scss @@ -0,0 +1,9 @@ +@import "./high-contrast"; + +:root { + @include high-contrast-light-base-theme(); +} + +:root.md { + @include high-contrast-light-md-theme(); +} diff --git a/core/src/css/themes/high-contrast.class.scss b/core/src/css/themes/high-contrast.class.scss new file mode 100644 index 00000000000..46c949d54e9 --- /dev/null +++ b/core/src/css/themes/high-contrast.class.scss @@ -0,0 +1,9 @@ +@import "./high-contrast"; + +.ion-theme-high-contrast { + @include high-contrast-light-base-theme(); +} + +.ion-theme-high-contrast.md { + @include high-contrast-light-md-theme(); +} diff --git a/core/src/css/themes/high-contrast.scss b/core/src/css/themes/high-contrast.scss new file mode 100644 index 00000000000..f67e0d4f46a --- /dev/null +++ b/core/src/css/themes/high-contrast.scss @@ -0,0 +1,160 @@ +@use "sass:map"; +@import "../../themes/ionic.functions.color"; + +$primary: #003fae !default; +$secondary: #01487b !default; +$tertiary: #3400e6 !default; +$success: #004314 !default; +$warning: #5f4100 !default; +$danger: #9c000c !default; +$light: #f4f5f8 !default; +$medium: #444446 !default; +$dark: #222428 !default; + +$colors: ( + primary: ( + base: $primary, + contrast: #fff, + shade: get-color-shade($primary), + tint: get-color-tint($primary) + ), + secondary: ( + base: $secondary, + contrast: #fff, + shade: get-color-shade($secondary), + tint: get-color-tint($secondary) + ), + tertiary: ( + base: $tertiary, + contrast: #fff, + shade: get-color-shade($tertiary), + tint: get-color-tint($tertiary) + ), + success: ( + base: $success, + contrast: #fff, + shade: get-color-shade($success), + tint: get-color-tint($success) + ), + warning: ( + base: $warning, + contrast: #fff, + shade: get-color-shade($warning), + tint: get-color-tint($warning) + ), + danger: ( + base: $danger, + contrast: #fff, + shade: get-color-shade($danger), + tint: get-color-tint($danger) + ), + light: ( + base: $light, + contrast: #000, + shade: get-color-shade($light), + tint: get-color-tint($light) + ), + medium: ( + base: $medium, + contrast: #fff, + shade: get-color-shade($medium), + tint: get-color-tint($medium) + ), + dark: ( + base: $dark, + contrast: #fff, + shade: get-color-shade($dark), + tint: get-color-tint($dark) + ) +) !default; + +/// Text step colors are generated based on +/// how dark or light text can be. The darkest +/// text color is usually the default text color (white or black). +/// The darker the $lightest-text-color is, the darker each +/// stepped color is. If you want to increase text contrast, +/// make $lightest-text-color darker. +$background-color: #ffffff; +$text-color: #000000; + +$darkest-text-color: $text-color; +$lightest-text-color: #888888; + +/// Loop through each color object above +/// and generate CSS Variables for each +/// color. Do not use the ion-color function +/// here because that will set the variable +/// to evaluate to itself with a fallback. +/// For example: +/// --ion-color-primary: #{ion-color(primary, base)}; +/// Maps to: +/// --ion-color-primary: var(--ion-color-primary, #...); +@mixin high-contrast-light-base-theme() { + & { + @each $color-name, $value in $colors { + --ion-color-#{$color-name}: #{map.get($value, base)}; + --ion-color-#{$color-name}-rgb: #{color-to-rgb-list(map.get($value, base))}; + --ion-color-#{$color-name}-contrast: #{map.get($value, contrast)}; + --ion-color-#{$color-name}-contrast-rgb: #{color-to-rgb-list(map.get($value, contrast))}; + --ion-color-#{$color-name}-shade: #{map.get($value, shade)}; + --ion-color-#{$color-name}-tint: #{map.get($value, tint)}; + } + + --ion-background-color: #{$background-color}; + --ion-background-color-rgb: #{color-to-rgb-list($background-color)}; + + --ion-text-color: #{$text-color}; + --ion-text-color-rgb: #{color-to-rgb-list($text-color)}; + + --ion-placeholder-opacity: 0.8; + + /// Only the item borders should increase in contrast + /// Borders for elements like toolbars should remain the same + --ion-item-border-color: #7a7a7a; + + --ion-text-color-step-50: #{mix($lightest-text-color, $darkest-text-color, 5%)}; + --ion-text-color-step-100: #{mix($lightest-text-color, $darkest-text-color, 10%)}; + --ion-text-color-step-150: #{mix($lightest-text-color, $darkest-text-color, 15%)}; + --ion-text-color-step-200: #{mix($lightest-text-color, $darkest-text-color, 20%)}; + --ion-text-color-step-250: #{mix($lightest-text-color, $darkest-text-color, 25%)}; + --ion-text-color-step-300: #{mix($lightest-text-color, $darkest-text-color, 30%)}; + --ion-text-color-step-350: #{mix($lightest-text-color, $darkest-text-color, 35%)}; + --ion-text-color-step-400: #{mix($lightest-text-color, $darkest-text-color, 40%)}; + --ion-text-color-step-450: #{mix($lightest-text-color, $darkest-text-color, 45%)}; + --ion-text-color-step-500: #{mix($lightest-text-color, $darkest-text-color, 50%)}; + --ion-text-color-step-550: #{mix($lightest-text-color, $darkest-text-color, 55%)}; + --ion-text-color-step-600: #{mix($lightest-text-color, $darkest-text-color, 60%)}; + --ion-text-color-step-650: #{mix($lightest-text-color, $darkest-text-color, 65%)}; + --ion-text-color-step-700: #{mix($lightest-text-color, $darkest-text-color, 70%)}; + --ion-text-color-step-750: #{mix($lightest-text-color, $darkest-text-color, 75%)}; + --ion-text-color-step-800: #{mix($lightest-text-color, $darkest-text-color, 80%)}; + --ion-text-color-step-850: #{mix($lightest-text-color, $darkest-text-color, 85%)}; + --ion-text-color-step-900: #{mix($lightest-text-color, $darkest-text-color, 90%)}; + --ion-text-color-step-950: #{mix($lightest-text-color, $darkest-text-color, 95%)}; + } +} + +@mixin high-contrast-light-md-theme() { + // Toast + // -------------------------------------------------- + + & ion-toast { + --color: var(--ion-background-color); + } + + & ion-toast::part(button) { + // Fallback for browsers that don't support color-mix + color: var(--color); + } + + /* stylelint-disable-next-line order/order */ + @supports (color: color-mix(in lch, plum, pink)) { + & ion-toast::part(button) { + color: color-mix(in srgb, var(--color) 70%, var(--button-color)); + } + } + + & ion-toast::part(button cancel) { + color: var(--color); + } +} diff --git a/core/src/css/themes/high-contrast.system.scss b/core/src/css/themes/high-contrast.system.scss new file mode 100644 index 00000000000..7424552ec57 --- /dev/null +++ b/core/src/css/themes/high-contrast.system.scss @@ -0,0 +1,11 @@ +@import "./high-contrast"; + +@media (prefers-contrast: more) { + :root { + @include high-contrast-light-base-theme(); + } + + :root.md { + @include high-contrast-light-md-theme(); + } +} diff --git a/core/src/themes/ionic.globals.scss b/core/src/themes/ionic.globals.scss index d421a43b6ff..f4c52d1472b 100644 --- a/core/src/themes/ionic.globals.scss +++ b/core/src/themes/ionic.globals.scss @@ -35,7 +35,7 @@ $screen-breakpoints: ( // Input placeholder opacity // Ensures that the placeholder has the // correct color contrast against the background. -$placeholder-opacity: 0.6 !default; +$placeholder-opacity: var(--ion-placeholder-opacity, 0.6) !default; $form-control-label-margin: 16px !default; diff --git a/core/src/themes/ionic.theme.default.ios.scss b/core/src/themes/ionic.theme.default.ios.scss index dcf60dba750..c5c5e7045a9 100644 --- a/core/src/themes/ionic.theme.default.ios.scss +++ b/core/src/themes/ionic.theme.default.ios.scss @@ -7,26 +7,26 @@ // iOS General Colors // -------------------------------------------------- $backdrop-ios-color: var(--ion-backdrop-color, #000) !default; -$overlay-ios-background-color: var(--ion-overlay-background-color, var(--ion-color-step-100, #f9f9f9)) !default; +$overlay-ios-background-color: var(--ion-overlay-background-color, var(--ion-color-step-100, var(--ion-background-color-step-100, #f9f9f9))) !default; // iOS Tabs & Tab bar // -------------------------------------------------- -$tabbar-ios-background: var(--ion-tab-bar-background, var(--ion-color-step-50, #f7f7f7)) !default; +$tabbar-ios-background: var(--ion-tab-bar-background, var(--ion-color-step-50, var(--ion-background-color-step-50, #f7f7f7))) !default; $tabbar-ios-background-focused: var(--ion-tab-bar-background-focused, get-color-shade(#fff)) !default; -$tabbar-ios-border-color: var(--ion-tab-bar-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, .2)))) !default; +$tabbar-ios-border-color: var(--ion-tab-bar-border-color, var(--ion-border-color, var(--ion-color-step-150, var(--ion-background-color-step-150, rgba(0, 0, 0, .2)))) )!default; $tabbar-ios-color: var(--ion-tab-bar-color, $text-color-step-400) !default; $tabbar-ios-color-selected: var(--ion-tab-bar-color-selected, ion-color(primary, base)) !default; // iOS Toolbar // -------------------------------------------------- -$toolbar-ios-background: var(--ion-toolbar-background, var(--ion-color-step-50, #f7f7f7)) !default; -$toolbar-ios-border-color: var(--ion-toolbar-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, .2)))) !default; +$toolbar-ios-background: var(--ion-toolbar-background, var(--ion-color-step-50, var(--ion-background-color-step-50, #f7f7f7))) !default; +$toolbar-ios-border-color: var(--ion-toolbar-border-color, var(--ion-border-color, var(--ion-color-step-150, var(--ion-background-color-step-150, rgba(0, 0, 0, .2))))) !default; $toolbar-ios-color: var(--ion-toolbar-color, $text-color) !default; // iOS List & List Items // -------------------------------------------------- $item-ios-background: var(--ion-item-background, $background-color) !default; -$item-ios-border-color: var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-250, #c8c7cc))) !default; +$item-ios-border-color: var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-250, var(--ion-background-color-step-250, #c8c7cc)))) !default; $item-ios-color: var(--ion-item-color, $text-color) !default; // iOS Card diff --git a/core/src/themes/ionic.theme.default.md.scss b/core/src/themes/ionic.theme.default.md.scss index 1a1abb48b5b..14f1f34c7ad 100644 --- a/core/src/themes/ionic.theme.default.md.scss +++ b/core/src/themes/ionic.theme.default.md.scss @@ -8,14 +8,14 @@ // Material Design General Colors // -------------------------------------------------- $backdrop-md-color: var(--ion-backdrop-color, #000) !default; -$border-md-color: var(--ion-border-color, var(--ion-color-step-150, #c1c4cd)) !default; +$border-md-color: var(--ion-border-color, var(--ion-color-step-150, var(--ion-background-color-step-150, #c1c4cd))) !default; $overlay-md-background-color: var(--ion-overlay-background-color, var(--ion-background-color, #fff)) !default; // Material Design Tabs & Tab bar // -------------------------------------------------- $tabbar-md-background: var(--ion-tab-bar-background, $background-color) !default; $tabbar-md-background-focused: var(--ion-tab-bar-background-focused, get-color-shade(#fff)) !default; -$tabbar-md-border-color: var(--ion-tab-bar-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, .07)))) !default; +$tabbar-md-border-color: var(--ion-tab-bar-border-color, var(--ion-border-color, var(--ion-color-step-150, var(--ion-background-color-step-150, rgba(0, 0, 0, .07))))) !default; $tabbar-md-color: var(--ion-tab-bar-color, $text-color-step-350) !default; $tabbar-md-color-selected: var(--ion-tab-bar-color-selected, ion-color(primary, base)) !default; @@ -28,7 +28,7 @@ $toolbar-md-color: var(--ion-toolbar-color, var(--ion-t // Material Design List & List Items // -------------------------------------------------- $item-md-background: var(--ion-item-background, $background-color) !default; -$item-md-border-color: var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, .13)))) !default; +$item-md-border-color: var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-150, var(--ion-background-color-step-150, rgba(0, 0, 0, .13))))) !default; $item-md-color: var(--ion-item-color, $text-color) !default; // Material Design Card diff --git a/core/src/themes/ionic.theme.default.scss b/core/src/themes/ionic.theme.default.scss index 57b04cbbcdf..8a7c4c6013c 100644 --- a/core/src/themes/ionic.theme.default.scss +++ b/core/src/themes/ionic.theme.default.scss @@ -101,44 +101,44 @@ $text-color-rgb: var(--ion-text-color-rgb, $text-color-rgb-va // For example, $text-color-step-XXX will be $text-color stepping towards $background-color, // but a $background-color-step-XXX will be $background-color stepping towards $text-color. -$background-color-step-50: var(--ion-color-step-50, mix($text-color-value, $background-color-value, 5%)) !default; -$background-color-step-100: var(--ion-color-step-100, mix($text-color-value, $background-color-value, 10%)) !default; -$background-color-step-150: var(--ion-color-step-150, mix($text-color-value, $background-color-value, 15%)) !default; -$background-color-step-200: var(--ion-color-step-200, mix($text-color-value, $background-color-value, 20%)) !default; -$background-color-step-250: var(--ion-color-step-250, mix($text-color-value, $background-color-value, 25%)) !default; -$background-color-step-300: var(--ion-color-step-300, mix($text-color-value, $background-color-value, 30%)) !default; -$background-color-step-350: var(--ion-color-step-350, mix($text-color-value, $background-color-value, 35%)) !default; -$background-color-step-400: var(--ion-color-step-400, mix($text-color-value, $background-color-value, 40%)) !default; -$background-color-step-450: var(--ion-color-step-450, mix($text-color-value, $background-color-value, 45%)) !default; -$background-color-step-500: var(--ion-color-step-500, mix($text-color-value, $background-color-value, 50%)) !default; -$background-color-step-550: var(--ion-color-step-550, mix($text-color-value, $background-color-value, 55%)) !default; -$background-color-step-600: var(--ion-color-step-600, mix($text-color-value, $background-color-value, 60%)) !default; -$background-color-step-650: var(--ion-color-step-650, mix($text-color-value, $background-color-value, 65%)) !default; -$background-color-step-700: var(--ion-color-step-700, mix($text-color-value, $background-color-value, 70%)) !default; -$background-color-step-750: var(--ion-color-step-750, mix($text-color-value, $background-color-value, 75%)) !default; -$background-color-step-800: var(--ion-color-step-800, mix($text-color-value, $background-color-value, 80%)) !default; -$background-color-step-850: var(--ion-color-step-850, mix($text-color-value, $background-color-value, 85%)) !default; -$background-color-step-900: var(--ion-color-step-900, mix($text-color-value, $background-color-value, 90%)) !default; -$background-color-step-950: var(--ion-color-step-950, mix($text-color-value, $background-color-value, 95%)) !default; -$text-color-step-50: var(--ion-color-step-950, mix($background-color-value, $text-color-value, 5%)) !default; -$text-color-step-100: var(--ion-color-step-900, mix($background-color-value, $text-color-value, 10%)) !default; -$text-color-step-150: var(--ion-color-step-850, mix($background-color-value, $text-color-value, 15%)) !default; -$text-color-step-200: var(--ion-color-step-800, mix($background-color-value, $text-color-value, 20%)) !default; -$text-color-step-250: var(--ion-color-step-750, mix($background-color-value, $text-color-value, 25%)) !default; -$text-color-step-300: var(--ion-color-step-700, mix($background-color-value, $text-color-value, 30%)) !default; -$text-color-step-350: var(--ion-color-step-650, mix($background-color-value, $text-color-value, 35%)) !default; -$text-color-step-400: var(--ion-color-step-600, mix($background-color-value, $text-color-value, 40%)) !default; -$text-color-step-450: var(--ion-color-step-550, mix($background-color-value, $text-color-value, 45%)) !default; -$text-color-step-500: var(--ion-color-step-500, mix($background-color-value, $text-color-value, 50%)) !default; -$text-color-step-550: var(--ion-color-step-450, mix($background-color-value, $text-color-value, 55%)) !default; -$text-color-step-600: var(--ion-color-step-400, mix($background-color-value, $text-color-value, 60%)) !default; -$text-color-step-650: var(--ion-color-step-350, mix($background-color-value, $text-color-value, 65%)) !default; -$text-color-step-700: var(--ion-color-step-300, mix($background-color-value, $text-color-value, 70%)) !default; -$text-color-step-750: var(--ion-color-step-250, mix($background-color-value, $text-color-value, 75%)) !default; -$text-color-step-800: var(--ion-color-step-200, mix($background-color-value, $text-color-value, 80%)) !default; -$text-color-step-850: var(--ion-color-step-150, mix($background-color-value, $text-color-value, 85%)) !default; -$text-color-step-900: var(--ion-color-step-100, mix($background-color-value, $text-color-value, 90%)) !default; -$text-color-step-950: var(--ion-color-step-50, mix($background-color-value, $text-color-value, 95%)) !default; +$background-color-step-50: var(--ion-color-step-50, var(--ion-background-color-step-50, mix($text-color-value, $background-color-value, 5%))) !default; +$background-color-step-100: var(--ion-color-step-100, var(--ion-background-color-step-100, mix($text-color-value, $background-color-value, 10%))) !default; +$background-color-step-150: var(--ion-color-step-150, var(--ion-background-color-step-150, mix($text-color-value, $background-color-value, 15%))) !default; +$background-color-step-200: var(--ion-color-step-200, var(--ion-background-color-step-200, mix($text-color-value, $background-color-value, 20%))) !default; +$background-color-step-250: var(--ion-color-step-250, var(--ion-background-color-step-250, mix($text-color-value, $background-color-value, 25%))) !default; +$background-color-step-300: var(--ion-color-step-300, var(--ion-background-color-step-300, mix($text-color-value, $background-color-value, 30%))) !default; +$background-color-step-350: var(--ion-color-step-350, var(--ion-background-color-step-350, mix($text-color-value, $background-color-value, 35%))) !default; +$background-color-step-400: var(--ion-color-step-400, var(--ion-background-color-step-400, mix($text-color-value, $background-color-value, 40%))) !default; +$background-color-step-450: var(--ion-color-step-450, var(--ion-background-color-step-450, mix($text-color-value, $background-color-value, 45%))) !default; +$background-color-step-500: var(--ion-color-step-500, var(--ion-background-color-step-500, mix($text-color-value, $background-color-value, 50%))) !default; +$background-color-step-550: var(--ion-color-step-550, var(--ion-background-color-step-550, mix($text-color-value, $background-color-value, 55%))) !default; +$background-color-step-600: var(--ion-color-step-600, var(--ion-background-color-step-600, mix($text-color-value, $background-color-value, 60%))) !default; +$background-color-step-650: var(--ion-color-step-650, var(--ion-background-color-step-650, mix($text-color-value, $background-color-value, 65%))) !default; +$background-color-step-700: var(--ion-color-step-700, var(--ion-background-color-step-700, mix($text-color-value, $background-color-value, 70%))) !default; +$background-color-step-750: var(--ion-color-step-750, var(--ion-background-color-step-750, mix($text-color-value, $background-color-value, 75%))) !default; +$background-color-step-800: var(--ion-color-step-800, var(--ion-background-color-step-800, mix($text-color-value, $background-color-value, 80%))) !default; +$background-color-step-850: var(--ion-color-step-850, var(--ion-background-color-step-850, mix($text-color-value, $background-color-value, 85%))) !default; +$background-color-step-900: var(--ion-color-step-900, var(--ion-background-color-step-900, mix($text-color-value, $background-color-value, 90%))) !default; +$background-color-step-950: var(--ion-color-step-950, var(--ion-background-color-step-950, mix($text-color-value, $background-color-value, 95%))) !default; +$text-color-step-50: var(--ion-color-step-950, var(--ion-text-color-step-50, mix($background-color-value, $text-color-value, 5%))) !default; +$text-color-step-100: var(--ion-color-step-900, var(--ion-text-color-step-100, mix($background-color-value, $text-color-value, 10%))) !default; +$text-color-step-150: var(--ion-color-step-850, var(--ion-text-color-step-150, mix($background-color-value, $text-color-value, 15%))) !default; +$text-color-step-200: var(--ion-color-step-800, var(--ion-text-color-step-200, mix($background-color-value, $text-color-value, 20%))) !default; +$text-color-step-250: var(--ion-color-step-750, var(--ion-text-color-step-250, mix($background-color-value, $text-color-value, 25%))) !default; +$text-color-step-300: var(--ion-color-step-700, var(--ion-text-color-step-300, mix($background-color-value, $text-color-value, 30%))) !default; +$text-color-step-350: var(--ion-color-step-650, var(--ion-text-color-step-350, mix($background-color-value, $text-color-value, 35%))) !default; +$text-color-step-400: var(--ion-color-step-600, var(--ion-text-color-step-400, mix($background-color-value, $text-color-value, 40%))) !default; +$text-color-step-450: var(--ion-color-step-550, var(--ion-text-color-step-450, mix($background-color-value, $text-color-value, 45%))) !default; +$text-color-step-500: var(--ion-color-step-500, var(--ion-text-color-step-500, mix($background-color-value, $text-color-value, 50%))) !default; +$text-color-step-550: var(--ion-color-step-450, var(--ion-text-color-step-550, mix($background-color-value, $text-color-value, 55%))) !default; +$text-color-step-600: var(--ion-color-step-400, var(--ion-text-color-step-600, mix($background-color-value, $text-color-value, 60%))) !default; +$text-color-step-650: var(--ion-color-step-350, var(--ion-text-color-step-650, mix($background-color-value, $text-color-value, 65%))) !default; +$text-color-step-700: var(--ion-color-step-300, var(--ion-text-color-step-700, mix($background-color-value, $text-color-value, 70%))) !default; +$text-color-step-750: var(--ion-color-step-250, var(--ion-text-color-step-750, mix($background-color-value, $text-color-value, 75%))) !default; +$text-color-step-800: var(--ion-color-step-200, var(--ion-text-color-step-800, mix($background-color-value, $text-color-value, 80%))) !default; +$text-color-step-850: var(--ion-color-step-150, var(--ion-text-color-step-850, mix($background-color-value, $text-color-value, 85%))) !default; +$text-color-step-900: var(--ion-color-step-100, var(--ion-text-color-step-900, mix($background-color-value, $text-color-value, 90%))) !default; +$text-color-step-950: var(--ion-color-step-50, var(--ion-text-color-step-950, mix($background-color-value, $text-color-value, 95%))) !default; // Default General Colors // -------------------------------------------------- diff --git a/core/src/themes/test/colors/theme.e2e.ts b/core/src/themes/test/colors/theme.e2e.ts index 92256500f89..c10356faf7c 100644 --- a/core/src/themes/test/colors/theme.e2e.ts +++ b/core/src/themes/test/colors/theme.e2e.ts @@ -2,10 +2,15 @@ import AxeBuilder from '@axe-core/playwright'; import { expect } from '@playwright/test'; import { configs, test } from '@utils/test/playwright'; +/** + * Small text is defined as 14pt (~18.5px) + * when computing color contrast: https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html + */ const styleTestHelpers = `