Skip to content

Conversation

julien-deramond
Copy link
Member

@julien-deramond julien-deramond commented Jul 2, 2025

Closes #41543
Supersedes #41551

Description

The color-contrast function in scss/_functions.scss currently uses a strict greater than comparison (>) when checking if a contrast ratio meets the minimum requirement:

@if $contrast-ratio > $min-contrast-ratio {

This excludes colors that produce exactly the minimum contrast ratio (4.5:1), which violates the WCAG 2.1 Success Criterion 1.4.3 Contrast (Minimum). The standard requires "at least 4.5:1" (>= 4.5), not "strictly greater than 4.5:1" (> 4.5).

To comply with the standard, the comparison operator should be changed from > to >=:

@if $contrast-ratio >= $min-contrast-ratio {

This would ensure that colors producing exactly a 4.5:1 contrast ratio are accepted, correctly implementing the WCAG requirement.

Investigation results

After extensive testing, it appears that no specific colors in Bootstrap's current implementation produce exactly a 4.5:1 contrast ratio. This is due to Bootstrap’s use of a precomputed luminance list ($_luminance-list) that maps integer RGB values (0–255) to discrete luminance values.

I tested various colors near the 4.5:1 threshold against #fff and found:

  • #777777 (rgb(119, 119, 119)) → 4.4776:1
  • #767676 (rgb(118, 118, 118)) → 4.5415:1

So, there's no integer RGB color that yields exactly 4.5:1 in Bootstrap's current luminance calculation.

Testing colors like #777777, #767676, #787878, #757575 gives the same results with or without the fix, matching results from tooks like WebAIM: Contrast Checker.

Testing approach

I created a Sass test suite (scss/tests/mixins/_color-contrast.test.scss) that tests the boundary condition using a custom minimum contrast ratio that matches actual color contrast values. This clearly demonstrates the behavioral difference between > and >= and validates edge cases around the 4.5:1 threshold.

The test suite documents specific contrast ratios for real-world color examples and ensures regression prevention for future updates. I included several test cases from my own manuel tests covering various scenarios including colors below, at, and above the 4.5:1 threshold, edge cases with very light and very dark backgrounds, custom minimum contrast ratios, RGBA color handling, and luminance and contrast ratio calculation accuracy.

Reflection on practical impact

It's worth reflecting on whether this change is actually useful given the investigation results. The reality is that no real-world colors in Bootstrap's current implementation are affected by this fix - the luminance calculation uses discrete integer RGB values, so no color produces exactly 4.5:1 contrast ratio.

I don't have a strong opinion regarding the merge of this PR.

I'd say this change could still be reasonable to respect the standards compliance (the "at least 4.5:1") and for future-proofing if Bootstrap ever changes its luminance calculation, or if PRs with new Sass implementations such as https://github.com/twbs/bootstrap/pull/41512/files#diff-de6111a37eb67d027961043ce738e80a3ac4ad61a6e7704d35cb3168369cba01 produce slightly different colors.

It remains a kind of theoretical fix, more of a preventive fix that aligns Bootstrap's implementation with accessibility standards and provides peace of mind that the code correctly handles edge cases, even if they don't currently exist in practice.

Warning

While I'm not an accessibility expert, I've done my best to approach this carefully and welcome feedback from others more experienced in this area. Pinging @patrickhlauke, @ffoodd, @mdo for double-check, and most of all, opinions :)

@patrickhlauke
Copy link
Member

such a long description/investigation/etc... i was ready to approve this after the first sentence ;)

@julien-deramond
Copy link
Member Author

julien-deramond commented Jul 2, 2025

such a long description/investigation/etc... i was ready to approve this after the first sentence ;)

My bad 😬, I was detailing everything along the way mostly to help myself understand the topic better.
Thanks for the quick review :)

Copy link
Member

@ffoodd ffoodd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed 💜 Thanks for the tests, that's really handful !

@julien-deramond julien-deramond merged commit b02d5ed into main Jul 2, 2025
14 checks passed
@julien-deramond julien-deramond deleted the main-jd-fix-color-contrast-function branch July 2, 2025 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Incorrect comparison in color-contrast function (should be >= instead of >)
3 participants