|
| 1 | +--- |
| 2 | +id: afw4f7 |
| 3 | +name: Text has minimum contrast |
| 4 | +rule_type: atomic |
| 5 | + |
| 6 | +description: | |
| 7 | + This rule checks that the highest possible contrast of every text character with its background meets the minimal contrast requirement |
| 8 | +
|
| 9 | +accessibility_requirements: |
| 10 | + wcag20:1.4.3: # Contrast (Minimum) |
| 11 | + forConformance: true |
| 12 | + failed: not satisfied |
| 13 | + passed: further testing needed |
| 14 | + inapplicable: further testing needed |
| 15 | + wcag20:1.4.6: # Contrast (Enhanced) |
| 16 | + forConformance: true |
| 17 | + failed: not satisfied |
| 18 | + passed: further testing needed |
| 19 | + inapplicable: further testing needed |
| 20 | + |
| 21 | +input_aspects: |
| 22 | + - DOM Tree |
| 23 | + - CSS Styling |
| 24 | + - Language |
| 25 | + |
| 26 | +acknowledgements: |
| 27 | + authors: |
| 28 | + - Brian Bors |
| 29 | + - Kasper Isager |
| 30 | + - Wilco Fiers |
| 31 | +--- |
| 32 | + |
| 33 | +## Applicability |
| 34 | + |
| 35 | +Any [visible][] character in a [text node][] that is a [child](https://dom.spec.whatwg.org/#concept-tree-child) (in the [flat tree](https://drafts.csswg.org/css-scoping/#flat-tree)) of an HTML element, except if the [text node][] is a [descendant](https://dom.spec.whatwg.org/#concept-shadow-including-descendant) of an element that: |
| 36 | + |
| 37 | +- has a [semantic role](#semantic-role) that inherits from [widget](https://www.w3.org/TR/wai-aria-1.1/#widget); or |
| 38 | +- is used in the [accessible name](#accessible-name) of a [widget](https://www.w3.org/TR/wai-aria-1.1/#widget) that is [disabled][]; or |
| 39 | +- has a [semantic role](#semantic-role) of [group](https://www.w3.org/TR/wai-aria-1.1/#group) and is [disabled][]. |
| 40 | + |
| 41 | +**Note**: When the [foreground color](#foreground-colors-of-text) is the same as the [background color](#background-colors-of-text), this rule deems the character not [visible][], so it does not need to be tested for contrast. |
| 42 | + |
| 43 | +## Expectation |
| 44 | + |
| 45 | +For each test target, the [highest possible contrast](#highest-possible-contrast) between the [foreground colors](#foreground-colors-of-text) and [background colors](#background-colors-of-text) is at least 4.5:1 or 3.0:1 for [larger scale text](#large-scale-text), except if the test target is part of a [text node][] that is [decorative](#decorative), or does not express anything in [human language](https://www.w3.org/TR/WCAG21/#dfn-human-language-s). |
| 46 | + |
| 47 | +**Note**: Passing this rule does not mean that the text has sufficient color contrast. If all background pixels have a low contrast with all foreground pixels, the success criterion is guaranteed to not be satisfied. When some pixels have sufficient contrast, and others do not, legibility should be considered. There is no clear method for determining legibility, which is why this is out of scope for this rule. |
| 48 | + |
| 49 | +**Note**: When the text color or background color is not specified in the web page, colors from other [origins](https://www.w3.org/TR/css3-cascade/#cascading-origins) will be used. Testers must ensure colors are not effected by styles from a [user origin](https://www.w3.org/TR/css3-cascade/#cascade-origin-user). Contrast issues cause by specifying the text color but not the background or vise versa, must be tested separately from this rule. |
| 50 | + |
| 51 | +## Assumptions |
| 52 | + |
| 53 | +- [Success criterion 1.4.3: Contrast (Minimum)](https://www.w3.org/TR/WCAG21/#contrast-minimum) has exceptions for "incidental" text, which includes inactive user interface components and decorative texts. The rule assumes that [text nodes](https://dom.spec.whatwg.org/#text) that should be ignored are [disabled]() or hidden from assistive technologies. If this isn't the case, the rule may produce incorrect results. |
| 54 | + |
| 55 | +- [Success criterion 1.4.3: Contrast (Minimum)](https://www.w3.org/TR/WCAG21/#contrast-minimum) also has an exception for logos and brand names. Since logos and brand names are usually displayed through images to ensure correct rendering, this rule does not take logos or brand names into consideration. If a logo or brand name is included using [text nodes](https://dom.spec.whatwg.org/#text), this rule may produce incorrect results. |
| 56 | + |
| 57 | +- Text that has the same foreground and background color (a contrast ratio of 1:1) is not considered to be "visual presentation of text", making it inapplicable to the success criterion. Text hidden in this way can still cause accessibility issues under other success criteria, depending on the content. |
| 58 | + |
| 59 | +## Accessibility Support |
| 60 | + |
| 61 | +Different browsers have different levels of support for CSS. This can cause contrast issues in one browser that do not appear in another. Because of that, this rule can produce different results depending on the browser that is used. For example, a text that is positioned using CSS transform may be on a different background in a browser that does not support CSS transform. |
| 62 | + |
| 63 | +## Background |
| 64 | + |
| 65 | +- [Understanding Success Criterion 1.4.3: Contrast (Minimum)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html) |
| 66 | +- [Understanding Success Criterion 1.4.6: Contrast (Enhanced)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced.html) |
| 67 | +- [G18: Ensuring that a contrast ratio of at least 4.5:1 exists between text (and images of text) and background behind the text](https://www.w3.org/WAI/WCAG21/Techniques/general/G18) |
| 68 | +- [G145: Ensuring that a contrast ratio of at least 3:1 exists between text (and images of text) and background behind the text](https://www.w3.org/WAI/WCAG21/Techniques/general/G145) |
| 69 | +- [F83: Failure of Success Criterion 1.4.3 and 1.4.6 due to using background images that do not provide sufficient contrast with foreground text (or images of text)](https://www.w3.org/WAI/WCAG21/Techniques/failures/F83) |
| 70 | +- [CSS Scoping Module Level 1 (Editor's Draft)](https://drafts.csswg.org/css-scoping/) |
| 71 | + |
| 72 | +## Test Cases |
| 73 | + |
| 74 | +### Passed |
| 75 | + |
| 76 | +#### Passed Example 1 |
| 77 | + |
| 78 | +This dark gray text has a contrast ratio of 12.6:1 on the white background. |
| 79 | + |
| 80 | +```html |
| 81 | +<p style="color: #333; background: #FFF;"> |
| 82 | + Some text in a human language |
| 83 | +</p> |
| 84 | +``` |
| 85 | + |
| 86 | +#### Passed Example 2 |
| 87 | + |
| 88 | +This dark gray text has a contrast ratio between 12.6:1 and 5:1 on the white to blue gradient background. |
| 89 | + |
| 90 | +```html |
| 91 | +<p style="color: #333; background: linear-gradient(to right, #FFF, #00F); width: 500px;"> |
| 92 | + Some text in a human language |
| 93 | +</p> |
| 94 | +``` |
| 95 | + |
| 96 | +#### Passed Example 3 |
| 97 | + |
| 98 | +This light gray text has a contrast ratio between 13:1 and 5:1 on the background image. |
| 99 | + |
| 100 | +```html |
| 101 | +<p |
| 102 | + style="color: #CCC; height:50px; padding-top:15px; background: #000 no-repeat -20px -20px url('../test-assets/contrast/black-hole.jpeg');" |
| 103 | +> |
| 104 | + Black hole sun |
| 105 | +</p> |
| 106 | +``` |
| 107 | + |
| 108 | +#### Passed Example 4 |
| 109 | + |
| 110 | +This black text has a contrast ratio between 6.1:1 and 9:1 on gray background with white text shadow on it. |
| 111 | + |
| 112 | +```html |
| 113 | +<p style="color: #000; background: #737373; text-shadow: white 0 0 3px"> |
| 114 | + Some text in a human language |
| 115 | +</p> |
| 116 | +``` |
| 117 | + |
| 118 | +#### Passed Example 5 |
| 119 | + |
| 120 | +This 18pt large black text has a contrast ratio of 3.6:1 on the gray background. |
| 121 | + |
| 122 | +```html |
| 123 | +<p style="color: #000; font-size:18pt; background: #666;"> |
| 124 | + Some text in a human language |
| 125 | +</p> |
| 126 | +``` |
| 127 | + |
| 128 | +#### Passed Example 6 |
| 129 | + |
| 130 | +This 14pt bold black text has a contrast ratio of 3.6:1 on the gray background. |
| 131 | + |
| 132 | +```html |
| 133 | +<p style="color: #000; font-size:14pt; font-weight:700; background: #666;"> |
| 134 | + Some text in English |
| 135 | +</p> |
| 136 | +``` |
| 137 | + |
| 138 | +#### Passed Example 7 |
| 139 | + |
| 140 | +The first `p` element is has a contrast ratio of 21:1 (default black on white). The second `p` element contains Helvetica text which is decorative, because it does not convey information or provides functionality; its purpose is to show the aesthetic of the Helvetica font. |
| 141 | + |
| 142 | +**Note**: Because this is non-text content, [success criterion 1.4.11 Non-text Contrast](https://www.w3.org/TR/WCAG21/#non-text-contrast) requires font example to have a color contrast of 3:1. |
| 143 | + |
| 144 | +```html |
| 145 | +<p style="color: #333; background: #FFF;"> |
| 146 | + Helvetica is a widely used sans-serif typeface developed in 1957 by Max Miedinger and Eduard Hoffmann. |
| 147 | +</p> |
| 148 | +<p style="font-family: helvetica; background: #EEE; color: #777;" aria-hidden="true"> |
| 149 | + The quick brown fox jumps over the lazy dog. |
| 150 | +</p> |
| 151 | +``` |
| 152 | + |
| 153 | +#### Passed Example 8 |
| 154 | + |
| 155 | +This text does not convey anything in human language. |
| 156 | + |
| 157 | +```html |
| 158 | +<p style="color: #000; background: #666;"> |
| 159 | + ----=====++++++++___________***********%%%%%%%%%%%±±±±@@@@@@@@ |
| 160 | +</p> |
| 161 | +``` |
| 162 | + |
| 163 | +#### Passed Example 9 |
| 164 | + |
| 165 | +This text has the default browser text color on the default browser background color. By default this is black text on a white background, which has a contrast ratio of 21:1. |
| 166 | + |
| 167 | +```html |
| 168 | +<p>Some text in a human language</p> |
| 169 | +``` |
| 170 | + |
| 171 | +#### Passed Example 10 |
| 172 | + |
| 173 | +This dark gray text has a contrast ratio of 12.6:1 on the white background in a shadow DOM tree. |
| 174 | + |
| 175 | +```html |
| 176 | +<p style="color: #CCC; background: #fff;" id="p"></p> |
| 177 | +<script> |
| 178 | + const shadowRoot = document.getElementById('p').attachShadow({ mode: 'open' }) |
| 179 | + shadowRoot.textContent = '<span style="color: #333;">Some text in English</span>' |
| 180 | +</script> |
| 181 | +``` |
| 182 | + |
| 183 | +### Failed |
| 184 | + |
| 185 | +#### Failed Example 1 |
| 186 | + |
| 187 | +This light gray text has a contrast ratio of 2.3:1 on the white background. |
| 188 | + |
| 189 | +```html |
| 190 | +<p style="color: #AAA; background: white;"> |
| 191 | + Some text in English |
| 192 | +</p> |
| 193 | +``` |
| 194 | + |
| 195 | +#### Failed Example 2 |
| 196 | + |
| 197 | +This light gray text has a contrast ratio between 1.2:1 and 2.3:1 on the white to blue gradient background. |
| 198 | + |
| 199 | +```html |
| 200 | +<p style="color: #AAA; background: linear-gradient(to right, #FFF, #00F); width: 300px"> |
| 201 | + Some text in English |
| 202 | +</p> |
| 203 | +``` |
| 204 | + |
| 205 | +#### Failed Example 3 |
| 206 | + |
| 207 | +This light gray text has a contrast ratio between 2.7:1 and 3:1 on the background image. |
| 208 | + |
| 209 | +```html |
| 210 | +<p |
| 211 | + style="color: #555; height:50px; padding-top:20px; background: black no-repeat -20px -20px url('../test-assets/contrast/black-hole.jpeg');" |
| 212 | +> |
| 213 | + Black hole sun |
| 214 | +</p> |
| 215 | +``` |
| 216 | + |
| 217 | +#### Failed Example 4 |
| 218 | + |
| 219 | +This black text with 30% alpha channel has a contrast ratio of 2.1:1 on the white background. |
| 220 | + |
| 221 | +```html |
| 222 | +<p style="color: rgba(0,0,0,.3); background: #FFF"> |
| 223 | + Some text in English |
| 224 | +</p> |
| 225 | +``` |
| 226 | + |
| 227 | +#### Failed Example 5 |
| 228 | + |
| 229 | +This black text with 30% opacity has a contrast ratio of 2.1:1 on the white background. |
| 230 | + |
| 231 | +```html |
| 232 | +<div style="background: #FFF"> |
| 233 | + <p style="color: #000; opacity: .3"> |
| 234 | + Some text in English |
| 235 | + </p> |
| 236 | +</div> |
| 237 | +``` |
| 238 | + |
| 239 | +#### Failed Example 6 |
| 240 | + |
| 241 | +This light gray text has a contrast ratio of 2.3:1 on the white background in a shadow DOM tree. |
| 242 | + |
| 243 | +```html |
| 244 | +<p style="color: #aaa; background: #fff;" id="p"></p> |
| 245 | +<script> |
| 246 | + const shadowRoot = document.getElementById('p').attachShadow({ mode: 'open' }) |
| 247 | + shadowRoot.textContent = 'Some text in English' |
| 248 | +</script> |
| 249 | +``` |
| 250 | + |
| 251 | +#### Failed Example 7 |
| 252 | + |
| 253 | +This semi-transparent gray text has a contrast ratio between 2.3:1 and 4.2:1 on the black and white background. The light gray text is compared to the white section of the background and the dark gray text is compared to the black section of the background. |
| 254 | + |
| 255 | +```html |
| 256 | +<style> |
| 257 | + #backgroundSplit { |
| 258 | + color: rgba(90, 90, 90, 0.8); |
| 259 | + background-position: top 0 left 0; |
| 260 | + background-image: linear-gradient(90deg, transparent, transparent 3.3em, black 3.3em, black 6em); |
| 261 | + padding: 0 1em; |
| 262 | + } |
| 263 | +</style> |
| 264 | +<span id="backgroundSplit"> |
| 265 | + Hello world |
| 266 | +</span> |
| 267 | +``` |
| 268 | + |
| 269 | +### Inapplicable |
| 270 | + |
| 271 | +#### Inapplicable Example 1 |
| 272 | + |
| 273 | +This text is not [visible][] because of `display: none`. |
| 274 | + |
| 275 | +```html |
| 276 | +<p style="display: none">Some invisible text in English</p> |
| 277 | +``` |
| 278 | + |
| 279 | +#### Inapplicable Example 2 |
| 280 | + |
| 281 | +This text is not [visible][] because it is positioned off screen. |
| 282 | + |
| 283 | +```html |
| 284 | +<p style="position:absolute; top: -999em">Some invisible text in English</p> |
| 285 | +``` |
| 286 | + |
| 287 | +#### Inapplicable Example 3 |
| 288 | + |
| 289 | +This text is not [visible][] because the foreground color is the same as the background color. |
| 290 | + |
| 291 | +```html |
| 292 | +<p style="color: white; background: white;" aria-hidden="true">Hidden text - U U D D L R L R B A S</p> |
| 293 | +``` |
| 294 | + |
| 295 | +#### Inapplicable Example 4 |
| 296 | + |
| 297 | +This text is not the child of an HTML element. |
| 298 | + |
| 299 | +```html |
| 300 | +<svg> |
| 301 | + <text x="0" y="15">I love SVG!</text> |
| 302 | +</svg> |
| 303 | +``` |
| 304 | + |
| 305 | +#### Inapplicable Example 5 |
| 306 | + |
| 307 | +This text not part of a [text node][]. |
| 308 | + |
| 309 | +```html |
| 310 | +<p> |
| 311 | + <img scr="../test-assets/contrast/example.png" alt="example" /> |
| 312 | +</p> |
| 313 | +``` |
| 314 | + |
| 315 | +#### Inapplicable Example 6 |
| 316 | + |
| 317 | +This text is part of a widget because it is a child of a `button` element. |
| 318 | + |
| 319 | +```html |
| 320 | +<button>My button!</button> |
| 321 | +``` |
| 322 | + |
| 323 | +#### Inapplicable Example 7 |
| 324 | + |
| 325 | +This text is part of a widget because it is a child of an element with the `role` attribute set to `button`. |
| 326 | + |
| 327 | +```html |
| 328 | +<div role="button">My button!</div> |
| 329 | +``` |
| 330 | + |
| 331 | +#### Inapplicable Example 8 |
| 332 | + |
| 333 | +This text is part of a label of a [disabled][] widget, because it is in a `label` element that is the label for an `input` element with `type="text"`. |
| 334 | + |
| 335 | +```html |
| 336 | +<label style="color:#888; background: white;"> |
| 337 | + My name |
| 338 | + <input type="text" disabled /> |
| 339 | +</label> |
| 340 | +``` |
| 341 | + |
| 342 | +#### Inapplicable Example 9 |
| 343 | + |
| 344 | +This text is part of a label of a [disabled][] widget, because it is in an element that is referenced by `aria-labelledby` from an element with `role="textbox"`. |
| 345 | + |
| 346 | +```html |
| 347 | +<label id="my_pets_name" style="color:#888; background: white;"> |
| 348 | + My pet's name |
| 349 | +</label> |
| 350 | +<div |
| 351 | + role="textbox" |
| 352 | + aria-labelledby="my_pets_name" |
| 353 | + aria-disabled="true" |
| 354 | + style="height:20px; width:100px; border:1px solid black;" |
| 355 | +> |
| 356 | + test |
| 357 | +</div> |
| 358 | +``` |
| 359 | + |
| 360 | +#### Inapplicable Example 10 |
| 361 | + |
| 362 | +This text is part of a label of a [disabled][] widget, because it is in a `label` element that is the label for an `input` element in a `fieldset` element with the `disabled` attribute. |
| 363 | + |
| 364 | +```html |
| 365 | +<fieldset disabled style="color:#888; background: white;"> |
| 366 | + <label> |
| 367 | + My name |
| 368 | + <input /> |
| 369 | + </label> |
| 370 | +</fieldset> |
| 371 | +``` |
| 372 | + |
| 373 | +#### Inapplicable Example 11 |
| 374 | + |
| 375 | +This text is part of a label of a [disabled][] widget, because it is in a `label` element that is the label for an `input` element in an element with `role="group"` with the `aria-disabled="true"` attribute. |
| 376 | + |
| 377 | +```html |
| 378 | +<div role="group" aria-disabled="true" style="color:#888; background: white;"> |
| 379 | + <label> |
| 380 | + My name |
| 381 | + <input /> |
| 382 | + </label> |
| 383 | +</div> |
| 384 | +``` |
| 385 | + |
| 386 | +[disabled]: #disabled-element 'Definition of disabled' |
| 387 | +[visible]: #visible 'Definition of visible' |
| 388 | +[text node]: https://dom.spec.whatwg.org/#text 'Definition of text node' |
0 commit comments