Files
cremote/docs/contrast_check_fix_container_elements.md
Josh at WLTechBlog 6b26c13add bump
2025-12-09 13:48:27 -07:00

3.1 KiB

Contrast Check Fix: Container Element False Positives

Issue Description

The automated contrast checker was incorrectly flagging parent container elements (DIVs) instead of the actual text elements (H2, P, etc.) when checking color contrast.

Example from the Field

On https://visionleadership.org/gala-sponsorship/:

Element 18 (H2 heading):

  • Tag: H2
  • Text: "Gala Presenting Sponsor"
  • Color: rgb(255, 255, 255) WHITE
  • Background: rgba(0, 0, 0, 0) - transparent
  • Parent background: rgb(12, 113, 195) - BLUE
  • Actual contrast ratio: 5.04:1 PASSES AA

Element 17 (parent DIV):

  • Tag: DIV
  • Color: rgb(61, 61, 61) - DARK GRAY
  • Background: rgb(12, 113, 195) - BLUE
  • Computed contrast ratio: 2.15:1 FALSE POSITIVE

Root Cause

The contrast checker was using the selector:

"p, h1, h2, h3, h4, h5, h6, a, button, span, div, li, td, th, label, input, textarea"

When a DIV container had text content (from child elements like H2), it was being checked using the DIV's computed text color (dark gray) instead of the actual H2's color (white).

Solution

Added logic to skip container elements (DIV, SPAN) that don't have direct text nodes (text that is a direct child, not in descendant elements).

Implementation

// Skip container elements that don't have direct text nodes
const hasDirectTextContent = Array.from(element.childNodes).some(node => 
    node.nodeType === Node.TEXT_NODE && node.textContent.trim().length > 0
);

// For container elements (div, span), only check if they have direct text
// For semantic text elements (h1-h6, p, a, button, etc.), always check
const isSemanticTextElement = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'A', 'BUTTON', 'LABEL', 'LI', 'TD', 'TH'].includes(element.tagName);
const isContainerElement = ['DIV', 'SPAN'].includes(element.tagName);

if (isContainerElement && !hasDirectTextContent) {
    // Skip this container - its children will be checked separately
    return;
}

Behavior After Fix

  1. Semantic text elements (H1-H6, P, A, BUTTON, LABEL, LI, TD, TH) are always checked, regardless of whether they have direct text content
  2. Container elements (DIV, SPAN) are only checked if they have direct text nodes
  3. Container elements without direct text are skipped - their child elements will be checked separately

Test Results

After the fix, the contrast check correctly identifies:

  • H2 "Gala Presenting Sponsor": White text on blue background = 5.04:1 PASSES AA
  • H2 "Gala Bar Sponsor": White text on blue background = 5.04:1 PASSES AA
  • H2 "Table Sponsor": White text on blue background = 5.04:1 PASSES AA
  • H2 "RSVP Gala Ticket": White text on blue background = 5.04:1 PASSES AA
  • Parent DIVs are not checked (correctly skipped)

Files Modified

  • daemon/daemon.go - Lines 9221-9241: Added logic to skip container elements without direct text content

Impact

  • Eliminates false positives from parent containers
  • Improves accuracy of contrast checking
  • Reduces noise in accessibility reports
  • No impact on legitimate contrast violations