Skip to content

Commit 89ad7e2

Browse files
WilcoFierskasperisagerShadowBBjeeyyyDagfinnRomen
authored
New rule: Text nodes have minimal contrast (#833)
* new rule: Text nodes have minimal contrast * Update highest-possible-contrast.md * Update large-scale-text.md * Update background-colors-of-text.md * Add Kasper to package.json * Update background-colors-of-text.md * Update text-node-contrast-afw4f7.md * Use advance measures for character width / height * Use advance height in large scale text * First stab at definition of "disabled" * Clarify exception of form-associated custom elements * Update disabled.md * Update disabled.md * Update text-node-contrast-afw4f7.md * More definitions * Add example.png * Fix a heading * Sort references * Resolve some comments * Reworded expectation note and example descriptions Made the expectation note speak the truth (White text on a black and white image doesn't always pass the test). Rewrote examples descriptions to turn them into actual full sentences. * Removed Kasper from this PR (he is now added to a different one) * Split definition to clarify Clarified by giving the bounding box around text a separate definition. * Split definition to clarify * aliased had to be "anti-aliased" * Added explanation about anti-aliasing. * Added example in another human language than English Added a wise phrase about monkeys * Made the list better readable instead of 2 items it now has 6 * typo fix * fixed more spelling errors * chore: Resolve comments * simplify bounding boxes for text * Apply suggestions from code review Co-Authored-By: DagfinnRomen <dro@difi.no> Co-Authored-By: Jean-Yves Moyen <jym@siteimprove.com> * update based on feedback * updated based on feedback * Update text-node-contrast-afw4f7.md * Update text-node-contrast-afw4f7.md * Apply suggestions from code review Co-Authored-By: DagfinnRomen <dro@difi.no> Co-Authored-By: carlosapaduarte <carlosapaduarte@gmail.com> * update based on feedback * Apply suggestions from code review Co-Authored-By: Jean-Yves Moyen <jym@siteimprove.com> Co-Authored-By: carlosapaduarte <carlosapaduarte@gmail.com> * update based on feedback * resolved comments * Apply suggestions from code review Co-Authored-By: Jean-Yves Moyen <jym@siteimprove.com> * Update text-contrast-afw4f7.md * Update _rules/text-contrast-afw4f7.md Co-Authored-By: Jean-Yves Moyen <jym@siteimprove.com> * new assumption about 1:1 contrast text * resolve feedback on 1:1 contrast text * Update _rules/text-contrast-afw4f7.md Co-Authored-By: EmmaJP <emma.richens@bbc.co.uk> * Update _rules/text-contrast-afw4f7.md Co-Authored-By: EmmaJP <emma.richens@bbc.co.uk> * Update text-contrast-afw4f7.md Co-authored-by: Kasper Isager <kasperisager@gmail.com> Co-authored-by: Brian Bors <b.bors@accessibility.nl> Co-authored-by: Jey Nandakumar <jey.nandakumar@gmail.com> Co-authored-by: DagfinnRomen <dagfinn.romen@digdir.no> Co-authored-by: Jean-Yves Moyen <jym@siteimprove.com> Co-authored-by: carlosapaduarte <carlosapaduarte@gmail.com> Co-authored-by: EmmaJP <emma.richens@bbc.co.uk>
1 parent 34f3b5e commit 89ad7e2

10 files changed

+468
-0
lines changed

__tests__/spelling-ignore.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@
6060
- EARL10-Schema
6161
- labelable
6262
- merchantability
63+
- 14pt
64+
- 18pt
65+
- Helvetica
6366

6467
# Notes and acronyms
6568
- TODO

_rules/text-contrast-afw4f7.md

Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
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

Comments
 (0)