Skip to content

CheckHoverContrast Test Step Documentation ​

Overview ​

The CheckHoverContrast test step verifies that interactive elements comply with WCAG 2.1 SC 1.4.11 (Non-text Contrast) requirements for hover state changes. Specifically, it checks that when a color change is the only visual cue indicating the hover state, the contrast ratio between the default and hover states is at least 3:1.


Purpose ​

Interactive controls often change appearance on hover to indicate interactivity. When this change relies solely on color (without other visual cues like cursor changes, shadows, or borders), users with low vision may not perceive the state change. This test ensures sufficient contrast between states for accessibility.


WCAG Reference ​

  • Success Criterion: 1.4.11 Non-text Contrast (Level AA)
  • Requirement: Visual information required to identify UI components and states must have a contrast ratio of at least 3:1 against adjacent colors
  • Applies to: Hover state changes where color is the only visual indicator

References ​


How It Works ​

1. Element Selection ​

The test targets elements from visually_graphic_elements (icons, buttons with graphics) since these are most likely to have color-only hover states without cursor changes.

2. Pre-Check Filters (Skip Conditions) ​

Before testing, elements are skipped if they:

  • Are not visible
  • Are disabled (disabled attribute or aria-disabled="true")

3. Visual Cue Detection ​

The test only applies when color change is the sole visual indicator. Elements are skipped if they have other visual cues:

Visual CueDetection Method
Interactive cursorpointer, hand, grab, etc. in default or hover state
Cursor changeAny cursor change to a meaningful value
Shadow appearingbox-shadow or text-shadow added on hover
Border changesBorder width, style, or visibility changes
Transform changestransform property changes (scale, rotate, etc.)
Size changesElement dimensions change by >2px
SVG transformsTransform on child SVG elements
Outline changesOutline width or style changes
Text decorationUnderline/strikethrough changes
Opacity changesOpacity difference >0.1
Browser defaultsDefault browser hover styling (e.g., link underlines)

4. Color Extraction Priority ​

For graphic elements (icons), colors are extracted in this priority order:

  1. SVG child colors - fill and stroke from <svg>, <path>, <circle>, etc.
  2. Graphic properties - fill, stroke, color on the element itself
  3. Container properties - background-color, border colors

This ensures we measure the control's color change, not the background.

5. Screenshot-Based Comparison ​

The test uses a robust screenshot comparison approach:

  1. Scroll element into view
  2. Capture default state screenshot
  3. Hover the element (wait 300ms for styles to apply)
  4. Capture hover state screenshot
  5. Compare screenshots to detect visual change
  6. Calculate contrast ratio using image analysis with CSS color hints

6. Contrast Calculation ​

The contrast ratio is calculated between:

  • Default state color - the element's color before hover
  • Hover state color - the element's color after hover

The comparison uses get_hover_contrast_ratio() which:

  • Analyzes pixel differences between screenshots
  • Uses CSS color hints when available
  • Falls back to dominant color analysis when hints are unavailable

Report Format ​

When a violation is found:

FieldValue
Title"Insufficient contrast against the control and background"
DescriptionThe <color name> (#hex color) of "<control name>" control on hover fails color contrast against the <color name> background (#hex color) with a ratio of <contrast ratio>.
Failed WCAG CP1.4.11

Configuration ​

python
CheckHoverContrast(
    debug=False,           # Enable debug logging
    target_key="visually_graphic_elements"  # Key in context.data for elements to check
)

Constants ​

  • WCAG_NON_TEXT_CONTRAST_THRESHOLD: 3.0 (minimum required contrast ratio)
  • INTERACTIVE_CURSOR_VALUES: Set of cursor values that indicate visual cues (pointer, hand, grab, etc.)

Limitations ​

1. Background Images ​

  • Issue: If an element uses a background-image instead of background-color, CSS-based color extraction cannot capture the color.
  • Mitigation: The screenshot-based comparison still works by analyzing rendered pixels, but accuracy may be reduced.
  • Edge case: If hover changes the background image itself (not just overlays a color), contrast calculation may not be accurate.

2. Complex Gradients ​

  • Issue: Elements with gradient backgrounds (linear-gradient, radial-gradient) don't have a single color value.
  • Mitigation: Screenshot analysis attempts to find dominant colors, but results may vary.

3. Pseudo-elements ​

  • Issue: Hover effects applied via ::before or ::after pseudo-elements may not be captured by get_all_styles().
  • Mitigation: Screenshot comparison catches visual changes, but CSS color hints may be missing.

4. Animations/Transitions ​

  • Issue: Elements with CSS transitions may not have fully transitioned when the hover screenshot is taken.
  • Mitigation: 300ms delay after hover, but very slow transitions may not complete.

5. JavaScript-Driven Hover Effects ​

  • Issue: Hover effects triggered by JavaScript mouseenter events may have timing issues.
  • Mitigation: The 300ms delay helps, but complex JS interactions may not be fully captured.

6. Nested Interactive Elements ​

  • Issue: Nested buttons/links may have hover effects that interfere with each other.
  • Current behavior: Each element is tested individually.

7. Dynamic Content ​

  • Issue: Elements that load content dynamically on hover (tooltips, dropdowns) may affect screenshots.
  • Mitigation: These typically have other visual cues and would be skipped.

8. SVG Sprite Icons ​

  • Issue: Icons using SVG sprites (<use xlink:href>) may have colors defined in the sprite, not on the element.
  • Mitigation: _get_svg_child_colors() attempts to extract colors from child elements.

9. Icon Fonts ​

  • Issue: Icon fonts (FontAwesome, etc.) use the color property, which is checked, but may be inherited from parent elements.
  • Mitigation: CSS color property is included in graphic color properties.

10. High-Contrast Mode ​

  • Issue: Users with Windows High Contrast Mode may see different colors than what's tested.
  • Current behavior: Test runs with standard browser rendering.

Out of Scope ​

The following are explicitly not tested by this step:

  • Disabled components - Skipped automatically
  • Elements with other visual cues - Cursor changes, shadows, borders, etc.
  • Text contrast - Covered by WCAG 1.4.3, not 1.4.11
  • Focus state contrast - Handled by CheckFocusIndicatorContrast
  • Active/visited states - Only hover state is tested
  • Browser default hover colors - Skipped as browser responsibility

Dependencies ​

Internal Modules ​

  • BaseTestStep, TestStepResult - Base test infrastructure
  • Element - Page element wrapper with hover/screenshot capabilities
  • BaseLogger - Logging infrastructure

Utility Functions ​

  • src.utils.color - Color parsing and conversion utilities
  • src.vision.comparison - Image comparison and contrast calculation
    • compare_byte_images() - Detect visual differences
    • get_hover_contrast_ratio() - Calculate contrast between states

Example Usage ​

python
from src.a11y_test_definitions.test_step.visually_disabled.CheckHoverContrast import CheckHoverContrast

# Create test step
hover_contrast_check = CheckHoverContrast(debug=True)

# Run as part of a test case
result = hover_contrast_check.run(context, logger)

Debug Mode ​

When debug=True, the test outputs detailed step-by-step information:

[CheckHoverContrast]     Step 1: Checking visibility...
[CheckHoverContrast]     Step 2: Checking disabled state...
[CheckHoverContrast]     Step 3: Getting default cursor/styles...
[CheckHoverContrast]     -> Default cursor: auto
[CheckHoverContrast]     -> Default SVG colors: {'fill': 'rgb(102, 102, 102)', 'stroke': ''}
[CheckHoverContrast]     Step 4: Scrolling into view and taking default screenshot...
[CheckHoverContrast]     Step 5: Hovering element...
[CheckHoverContrast]     Step 6: Getting hover cursor...
[CheckHoverContrast]     -> Hover cursor: auto
[CheckHoverContrast]     Step 7: Getting hover styles...
[CheckHoverContrast]     -> Hover SVG colors: {'fill': 'rgb(0, 102, 204)', 'stroke': ''}
[CheckHoverContrast]     Step 10: Comparing screenshots...
[CheckHoverContrast]     Step 11: Extracting color hints...
[CheckHoverContrast]     -> CSS colors: default=(102, 102, 102), hover=(0, 102, 204)
[CheckHoverContrast]     Step 12: Calculating contrast ratio...
[CheckHoverContrast]     -> Contrast: 2.45:1 (fg=(0, 102, 204), bg=(102, 102, 102))

Version History ​

VersionChanges
1.0Initial implementation with CSS-based color extraction
1.1Added SVG child color extraction for icon support
1.2Added screenshot-based comparison for robustness
1.3Added comprehensive visual cue detection (cursor, shadow, transform, etc.)