From 1ec99ca1ba973136892fce3f3e25aace4e44bc52 Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 18 Apr 2026 16:57:14 -0400 Subject: [PATCH 1/2] fix(a11y): icon SVGs use currentColor for HCM visibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Icon SVGs (chevrons, lock indicators, expand/collapse arrows, modal close, authorize button) disappear in Windows High Contrast Mode. Their paths have no fill attribute — SVG default is black — or an explicit author color, which Chromium's default forced-color-adjust: preserve-parent-color preserves against the HCM-substituted Canvas. In HCM dark, black-on-black sits at 1.00:1. Path fills now use fill="currentColor" so the inherited color (which IS HCM-substituted) cascades to the path. _buttons.scss sets color: $black on the icon-button parents so currentColor resolves to the prior effective black in normal light mode. _dark-mode.scss swaps fill: $color to color: $color on the same SVG selectors so the cascade carries in dark mode too. An @media (forced-colors: active) block in _buttons.scss pins the icon paths to ButtonText with forced-color-adjust: none — ButtonText is the system color paired with ButtonFace and guarantees contrast in any user HCM theme, independent of the cascade. The same block resets opacity on the .unlocked auth icon, since opacity composites multiplicatively with fill and would otherwise dim the system-color contrast guarantee. Refs #7350 --- .../plugins/icons/components/arrow-down.jsx | 5 +++- .../plugins/icons/components/arrow-up.jsx | 5 +++- src/core/plugins/icons/components/arrow.jsx | 5 +++- src/core/plugins/icons/components/close.jsx | 5 +++- src/core/plugins/icons/components/lock.jsx | 5 +++- src/core/plugins/icons/components/unlock.jsx | 5 +++- .../components/icons/ChevronRight.jsx | 5 +++- src/style/_buttons.scss | 28 +++++++++++++++++-- src/style/_dark-mode.scss | 10 +++---- 9 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/core/plugins/icons/components/arrow-down.jsx b/src/core/plugins/icons/components/arrow-down.jsx index 46011d2bfd7..71f85c5462a 100644 --- a/src/core/plugins/icons/components/arrow-down.jsx +++ b/src/core/plugins/icons/components/arrow-down.jsx @@ -15,7 +15,10 @@ const ArrowDown = ({ className = null, width = 20, height = 20, ...rest }) => ( focusable="false" {...rest} > - + ) diff --git a/src/core/plugins/icons/components/arrow-up.jsx b/src/core/plugins/icons/components/arrow-up.jsx index 410064acd50..40a15da16f3 100644 --- a/src/core/plugins/icons/components/arrow-up.jsx +++ b/src/core/plugins/icons/components/arrow-up.jsx @@ -15,7 +15,10 @@ const ArrowUp = ({ className = null, width = 20, height = 20, ...rest }) => ( focusable="false" {...rest} > - + ) diff --git a/src/core/plugins/icons/components/arrow.jsx b/src/core/plugins/icons/components/arrow.jsx index 07a6b7525fd..f0f5034c9f5 100644 --- a/src/core/plugins/icons/components/arrow.jsx +++ b/src/core/plugins/icons/components/arrow.jsx @@ -15,7 +15,10 @@ const Arrow = ({ className = null, width = 20, height = 20, ...rest }) => ( focusable="false" {...rest} > - + ) diff --git a/src/core/plugins/icons/components/close.jsx b/src/core/plugins/icons/components/close.jsx index 682d9f0133b..1e94d76accc 100644 --- a/src/core/plugins/icons/components/close.jsx +++ b/src/core/plugins/icons/components/close.jsx @@ -15,7 +15,10 @@ const Close = ({ className = null, width = 20, height = 20, ...rest }) => ( focusable="false" {...rest} > - + ) diff --git a/src/core/plugins/icons/components/lock.jsx b/src/core/plugins/icons/components/lock.jsx index e15b648410d..7a7c7568019 100644 --- a/src/core/plugins/icons/components/lock.jsx +++ b/src/core/plugins/icons/components/lock.jsx @@ -15,7 +15,10 @@ const Lock = ({ className = null, width = 20, height = 20, ...rest }) => ( focusable="false" {...rest} > - + ) diff --git a/src/core/plugins/icons/components/unlock.jsx b/src/core/plugins/icons/components/unlock.jsx index e320c86af8d..1dc60da9c13 100644 --- a/src/core/plugins/icons/components/unlock.jsx +++ b/src/core/plugins/icons/components/unlock.jsx @@ -15,7 +15,10 @@ const Unlock = ({ className = null, width = 20, height = 20, ...rest }) => ( focusable="false" {...rest} > - + ) diff --git a/src/core/plugins/json-schema-2020-12/components/icons/ChevronRight.jsx b/src/core/plugins/json-schema-2020-12/components/icons/ChevronRight.jsx index e91c90faab9..8906ac9a1a6 100644 --- a/src/core/plugins/json-schema-2020-12/components/icons/ChevronRight.jsx +++ b/src/core/plugins/json-schema-2020-12/components/icons/ChevronRight.jsx @@ -10,7 +10,10 @@ const ChevronRight = () => ( height="24" viewBox="0 0 24 24" > - + ) diff --git a/src/style/_buttons.scss b/src/style/_buttons.scss index 7806d17ee1d..c53163ab098 100644 --- a/src/style/_buttons.scss +++ b/src/style/_buttons.scss @@ -54,7 +54,7 @@ } svg { - fill: $btn-authorize-svg-fill-color; + color: $btn-authorize-svg-fill-color; } } @@ -88,6 +88,7 @@ border: none; background: none; + color: $black; .locked { opacity: 1; @@ -116,6 +117,7 @@ .expand-operation { border: none; background: none; + color: $black; svg { width: 20px; @@ -128,14 +130,14 @@ &:hover { svg { - fill: $expand-methods-svg-fill-color-hover; + color: $expand-methods-svg-fill-color-hover; } } svg { transition: all 0.3s; - fill: $expand-methods-svg-fill-color; + color: $expand-methods-svg-fill-color; } } @@ -178,6 +180,7 @@ button { border: none; text-align: center; background: none; + color: $black; } // overrides for smaller copy button for curl command @@ -197,3 +200,22 @@ button { height: 26px; position: unset; } + +// Render icon buttons in HCM system colors regardless of author fill cascade. +@media (forced-colors: active) { + .authorization__btn svg path, + .expand-operation svg path, + .opblock-control-arrow svg path, + .models-control svg path, + .close-modal svg path, + .btn.authorize svg path, + .json-schema-2020-12-accordion svg path { + forced-color-adjust: none; + fill: ButtonText; + } + + // Reset opacity dimming so HCM system-color contrast isn't composited away. + .authorization__btn .unlocked { + opacity: 1; + } +} diff --git a/src/style/_dark-mode.scss b/src/style/_dark-mode.scss index 17dd7000974..0c1ebe6e925 100644 --- a/src/style/_dark-mode.scss +++ b/src/style/_dark-mode.scss @@ -94,7 +94,7 @@ html.dark-mode { .authorization__btn svg, .expand-operation svg, .opblock-control-arrow svg { - fill: $neutral-40; + color: $neutral-40; opacity: 1; } @@ -217,7 +217,7 @@ html.dark-mode { border-color: $neutral-80; .close-modal svg { - fill: $neutral-20; + color: $neutral-20; } } @@ -294,7 +294,7 @@ html.dark-mode { color: $authorize-button; svg { - fill: $authorize-button; + color: $authorize-button; } } } @@ -618,7 +618,7 @@ html.dark-mode { background: $neutral-95; svg { - fill: $neutral-20; + color: $neutral-20; } } @@ -735,7 +735,7 @@ html.dark-mode { } svg { - fill: $neutral-40; + color: $neutral-40; } } } From ac4f3eca8dbd45a46aa38dda95f924d4219aac0f Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 18 Apr 2026 16:59:04 -0400 Subject: [PATCH 2/2] fix(a11y): data-URL icon chevrons render in HCM via mask-image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The model expand chevron, copy-to-clipboard button, and form checkmark all render as data-URL background-image SVGs with hardcoded fill colors. In Windows High Contrast Mode the system strips background-image, so these icons become invisible — the underlying button/checkbox still functions but loses its visual indicator. Added @media (forced-colors: active) blocks that re-render the same SVG shape as a mask-image filled by background-color: ButtonText (or CanvasText for the model toggle on a div pseudo-element). With forced-color-adjust: none the system color is honored as authored, so the icon shape stays visible against any HCM theme background. Light-mode and dark-mode variants of each rule both updated. Refs #10699 --- src/style/_buttons.scss | 9 +++++++++ src/style/_dark-mode.scss | 28 ++++++++++++++++++++++++++++ src/style/_form.scss | 9 +++++++++ src/style/_models.scss | 11 +++++++++++ 4 files changed, 57 insertions(+) diff --git a/src/style/_buttons.scss b/src/style/_buttons.scss index c53163ab098..23f35eddbc3 100644 --- a/src/style/_buttons.scss +++ b/src/style/_buttons.scss @@ -169,6 +169,15 @@ button { height: 25px; background: url("data:image/svg+xml, ") center center no-repeat; + + @media (forced-colors: active) { + forced-color-adjust: none; + background-color: ButtonText; + background-image: none; + mask-image: url("data:image/svg+xml, "); + mask-position: center center; + mask-repeat: no-repeat; + } } } diff --git a/src/style/_dark-mode.scss b/src/style/_dark-mode.scss index 0c1ebe6e925..ea79fbc61a2 100644 --- a/src/style/_dark-mode.scss +++ b/src/style/_dark-mode.scss @@ -260,6 +260,15 @@ html.dark-mode { background: $neutral-80 url('data:image/svg+xml, ') center center no-repeat; + + @media (forced-colors: active) { + forced-color-adjust: none; + background-color: ButtonText; + background-image: none; + mask-image: url('data:image/svg+xml, '); + mask-position: center center; + mask-repeat: no-repeat; + } } } } @@ -356,6 +365,15 @@ html.dark-mode { button { background: url("data:image/svg+xml, ") center center no-repeat; + + @media (forced-colors: active) { + forced-color-adjust: none; + background-color: ButtonText; + background-image: none; + mask-image: url("data:image/svg+xml, "); + mask-position: center center; + mask-repeat: no-repeat; + } } } @@ -569,6 +587,16 @@ html.dark-mode { background: url('data:image/svg+xml, ') center no-repeat; background-size: 100%; + + @media (forced-colors: active) { + forced-color-adjust: none; + background-color: CanvasText; + background-image: none; + mask-image: url('data:image/svg+xml, '); + mask-position: center; + mask-repeat: no-repeat; + mask-size: 100%; + } } } diff --git a/src/style/_form.scss b/src/style/_form.scss index 047ac5e83e8..db03832e981 100644 --- a/src/style/_form.scss +++ b/src/style/_form.scss @@ -209,6 +209,15 @@ textarea { background: $form-checkbox-background-color url('data:image/svg+xml, ') center center no-repeat; + + @media (forced-colors: active) { + forced-color-adjust: none; + background-color: ButtonText; + background-image: none; + mask-image: url('data:image/svg+xml, '); + mask-position: center center; + mask-repeat: no-repeat; + } } } } diff --git a/src/style/_models.scss b/src/style/_models.scss index 7a3b3a12324..a1547164a24 100644 --- a/src/style/_models.scss +++ b/src/style/_models.scss @@ -47,6 +47,17 @@ background: url('data:image/svg+xml, ') center no-repeat; background-size: 100%; + + // HCM: substitute data-URL chevron with a mask + system color. + @media (forced-colors: active) { + forced-color-adjust: none; + background-color: CanvasText; + background-image: none; + mask-image: url('data:image/svg+xml, '); + mask-position: center; + mask-repeat: no-repeat; + mask-size: 100%; + } } }