Skip to content

fix(a11y): restore icon visibility in Windows High Contrast Mode#10846

Open
bmatar wants to merge 2 commits intoswagger-api:masterfrom
bmatar:bug/7350-a11y-hcm-icons
Open

fix(a11y): restore icon visibility in Windows High Contrast Mode#10846
bmatar wants to merge 2 commits intoswagger-api:masterfrom
bmatar:bug/7350-a11y-hcm-icons

Conversation

@bmatar
Copy link
Copy Markdown

@bmatar bmatar commented Apr 19, 2026

Description

In Windows High Contrast Mode (Edge / Chromium with forced-colors: active), several icon SVGs disappear because their paths use hardcoded fills (or no fill, defaulting to black). Chromium's spec-correct default forced-color-adjust: preserve-parent-color for SVG preserves these author colors against the HCM-substituted Canvas, so a black-fill icon on HCM-dark Canvas sits at 1.00:1 contrast.

Affected controls: operation expand chevrons, lock indicators (auth state), the modal close icon, the Authorize-button lock, JSON-schema accordion chevrons, the model expand toggle, copy-to-clipboard buttons, the dark-mode form checkmark.

In the released swagger-ui (no dark-mode plugin), these icons render as black-on-black in HCM dark — completely invisible. With the dark-mode plugin active, the plugin's CSS coincidentally sets non-default colors on the SVG element, so they render at low contrast (gray-on-black) rather than full disappearance. The fix addresses both.

Two layers: (1) inline icon SVG paths use fill="currentColor" so the inherited (HCM-substituted) color cascades to the path, with a @media (forced-colors: active) block in _buttons.scss pinning the paths to ButtonText for theme-independent contrast and resetting the unlocked-icon opacity dimming that would otherwise composite the system color back to ~40% alpha. (2) Data-URL background-image chevrons get @media blocks that re-render the same SVG shape as a mask-image filled by background-color: ButtonText/CanvasText.

Motivation and Context

Refs #7350 (the existing accessibility issue lists "Expand collapsed chevrons and Unlock controls present in the End Points tab section is not visible highcontrast black mode" as a HCM finding).

Detected by Tactual. WCAG 1.4.11 Non-text Contrast.

Screenshots

All captured under forced-colors: active. Built from the same swagger-ui commit on both ports, only this patch differing.

Operations list — page-light + HCM dark (no dark-mode plugin, what petstore.swagger.io ships):

BEFORE AFTER
ops-no-darkmode_hcm-dark-before ops-no-darkmode_hcm-dark-after

Operations list — page-dark + HCM dark (with dark-mode plugin):

BEFORE AFTER
ops-darkmode_hcm-dark-before ops-darkmode_hcm-dark-after

Models section — page-light + HCM dark:

BEFORE AFTER
models-no-darkmode_hcm-dark-before models-no-darkmode_hcm-dark-after

Operations list — page-light + HCM light:

BEFORE AFTER
ops-no-darkmode_hcm-light-before ops-no-darkmode_hcm-light-after

Out of scope

  • <select> chevron in HCM (the <select> background-image chevron, both released _topbar.scss HTTPS dropdown variant and the dark-mode rule at _dark-mode.scss:156): <select> is a replaced element with no usable pseudo-element, so the mask-image approach used here can't apply without wrapping the <select> in a markup change. Followup PR.
  • Native control color-scheme in dark mode: PR fix(dark-mode): indicate dark color-scheme for native browser controls #10844 adds color-scheme: dark to html.dark-mode for native scrollbars/inputs. Independent and non-conflicting; HCM substitutes system colors regardless of color-scheme.

Checklist

My PR contains...

  • No code changes (src/ is unmodified: changes to documentation, CI, metadata, etc.)
  • Dependency changes (any modification to dependencies in package.json)
  • Bug fixes (non-breaking change which fixes an issue)
  • Improvements (misc. changes to existing features)
  • Features (non-breaking change which adds functionality)

My changes...

  • are breaking changes to a public API (config options, System API, major UI change, etc).
  • are breaking changes to a private API (Redux, component props, utility functions, etc.).
  • are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
  • are not breaking changes.

Documentation

  • My changes do not require a change to the project documentation.
  • My changes require a change to the project documentation.
  • If yes to above: I have updated the documentation accordingly.

Automated tests

  • My changes can not or do not need to be tested.
  • My changes can and should be tested by unit and/or integration tests.
  • If yes to above: I have added tests to cover my changes.
  • If yes to above: I have taken care to cover edge cases in my tests.
  • All new and existing tests passed.

bmatar added 2 commits April 19, 2026 15:15
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 swagger-api#7350
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 swagger-api#10699
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant