Skip to content

New rule: Text nodes have minimal contrast #833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 58 commits into from
Jan 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
7f2571d
new rule: Text nodes have minimal contrast
WilcoFiers Aug 23, 2019
c44f9a7
Update highest-possible-contrast.md
WilcoFiers Aug 23, 2019
2ef5fb9
Update large-scale-text.md
WilcoFiers Aug 23, 2019
a575e29
Update background-colors-of-text.md
WilcoFiers Aug 23, 2019
9aedf12
Add Kasper to package.json
WilcoFiers Aug 23, 2019
c5e79d2
Update background-colors-of-text.md
WilcoFiers Aug 26, 2019
5ef8422
Update text-node-contrast-afw4f7.md
WilcoFiers Aug 26, 2019
28740ca
Use advance measures for character width / height
WilcoFiers Aug 26, 2019
8e05cfe
Use advance height in large scale text
WilcoFiers Aug 26, 2019
83161d2
First stab at definition of "disabled"
kasperisager Aug 27, 2019
4877a49
Clarify exception of form-associated custom elements
kasperisager Aug 29, 2019
174c64e
Update disabled.md
kasperisager Sep 5, 2019
28ff7c0
Update disabled.md
kasperisager Sep 5, 2019
ec8738c
Update text-node-contrast-afw4f7.md
WilcoFiers Sep 9, 2019
a774d59
More definitions
WilcoFiers Sep 9, 2019
5fded85
Add example.png
WilcoFiers Sep 9, 2019
e093e30
Merge branch 'develop' into color-contrast-text-nodes
WilcoFiers Sep 11, 2019
4bae23b
Fix a heading
WilcoFiers Sep 11, 2019
69c25bd
Sort references
kasperisager Sep 11, 2019
67166e2
Resolve some comments
WilcoFiers Sep 24, 2019
c7ce2db
Reworded expectation note and example descriptions
ShadowBB Sep 24, 2019
842ee3a
Removed Kasper from this PR (he is now added to a different one)
ShadowBB Sep 24, 2019
1e2d780
Split definition to clarify
ShadowBB Sep 24, 2019
b5fd606
Split definition to clarify
ShadowBB Sep 24, 2019
148f535
aliased had to be "anti-aliased"
ShadowBB Sep 24, 2019
626b2e7
Added explanation about anti-aliasing.
ShadowBB Sep 24, 2019
7e7f242
Added example in another human language than English
ShadowBB Sep 24, 2019
61173bd
Made the list better readable instead of 2 items it now has 6
ShadowBB Sep 24, 2019
6e7dd05
Merge branch 'develop' into color-contrast-text-nodes
WilcoFiers Sep 25, 2019
b8f7651
typo fix
WilcoFiers Sep 25, 2019
3ea9cd8
fixed more spelling errors
WilcoFiers Sep 25, 2019
dca1663
Merge branch 'develop' into color-contrast-text-nodes
WilcoFiers Sep 25, 2019
c2228d1
chore: Resolve comments
WilcoFiers Oct 4, 2019
8b4c6c4
Merge branch 'develop' into color-contrast-text-nodes
jeeyyy Oct 16, 2019
621e77d
Merge branch 'develop' into color-contrast-text-nodes
jeeyyy Oct 16, 2019
3b3279c
simplify bounding boxes for text
WilcoFiers Nov 11, 2019
6a8e0cc
Apply suggestions from code review
WilcoFiers Nov 15, 2019
8834e9f
update based on feedback
WilcoFiers Nov 15, 2019
1de24e0
updated based on feedback
WilcoFiers Nov 19, 2019
a6fd54b
Merge branch 'develop' into color-contrast-text-nodes
Jym77 Nov 26, 2019
952b11d
Merge branch 'develop' into color-contrast-text-nodes
WilcoFiers Nov 27, 2019
00cea78
Update text-node-contrast-afw4f7.md
WilcoFiers Nov 27, 2019
98d663c
Update text-node-contrast-afw4f7.md
WilcoFiers Nov 27, 2019
4aebbfa
Apply suggestions from code review
WilcoFiers Dec 9, 2019
b9d354a
update based on feedback
WilcoFiers Dec 9, 2019
805875e
Apply suggestions from code review
WilcoFiers Dec 13, 2019
43de19e
update based on feedback
WilcoFiers Dec 13, 2019
d993248
Merge branch 'color-contrast-text-nodes' of github.com:act-rules/act-…
WilcoFiers Dec 13, 2019
8b639d7
resolved comments
WilcoFiers Dec 13, 2019
243deec
Apply suggestions from code review
WilcoFiers Dec 17, 2019
b688d96
Update text-contrast-afw4f7.md
WilcoFiers Dec 17, 2019
4893c3c
Update _rules/text-contrast-afw4f7.md
WilcoFiers Jan 9, 2020
2bc7f23
new assumption about 1:1 contrast text
WilcoFiers Jan 9, 2020
e6fc156
Merge branch 'develop' into color-contrast-text-nodes
WilcoFiers Jan 9, 2020
b6b70b0
resolve feedback on 1:1 contrast text
WilcoFiers Jan 10, 2020
14b9ecb
Update _rules/text-contrast-afw4f7.md
WilcoFiers Jan 14, 2020
3bcd16a
Update _rules/text-contrast-afw4f7.md
WilcoFiers Jan 14, 2020
6ba646d
Update text-contrast-afw4f7.md
WilcoFiers Jan 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions __tests__/spelling-ignore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
- EARL10-Schema
- labelable
- merchantability
- 14pt
- 18pt
- Helvetica

# Notes and acronyms
- TODO
Expand Down
388 changes: 388 additions & 0 deletions _rules/text-contrast-afw4f7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,388 @@
---
id: afw4f7
name: Text has minimum contrast
rule_type: atomic

description: |
This rule checks that the highest possible contrast of every text character with its background meets the minimal contrast requirement

accessibility_requirements:
wcag20:1.4.3: # Contrast (Minimum)
forConformance: true
failed: not satisfied
passed: further testing needed
inapplicable: further testing needed
wcag20:1.4.6: # Contrast (Enhanced)
forConformance: true
failed: not satisfied
passed: further testing needed
inapplicable: further testing needed

input_aspects:
- DOM Tree
- CSS Styling
- Language

acknowledgements:
authors:
- Brian Bors
- Kasper Isager
- Wilco Fiers
---

## Applicability

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:

- has a [semantic role](#semantic-role) that inherits from [widget](https://www.w3.org/TR/wai-aria-1.1/#widget); or
- 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
- has a [semantic role](#semantic-role) of [group](https://www.w3.org/TR/wai-aria-1.1/#group) and is [disabled][].

**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.

## Expectation

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).

**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.

**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.

## Assumptions

- [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.

- [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.

- 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.

## Accessibility Support

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.

## Background

- [Understanding Success Criterion 1.4.3: Contrast (Minimum)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html)
- [Understanding Success Criterion 1.4.6: Contrast (Enhanced)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced.html)
- [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)
- [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)
- [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)
- [CSS Scoping Module Level 1 (Editor's Draft)](https://drafts.csswg.org/css-scoping/)

## Test Cases

### Passed

#### Passed Example 1

This dark gray text has a contrast ratio of 12.6:1 on the white background.

```html
<p style="color: #333; background: #FFF;">
Some text in a human language
</p>
```

#### Passed Example 2

This dark gray text has a contrast ratio between 12.6:1 and 5:1 on the white to blue gradient background.

```html
<p style="color: #333; background: linear-gradient(to right, #FFF, #00F); width: 500px;">
Some text in a human language
</p>
```

#### Passed Example 3

This light gray text has a contrast ratio between 13:1 and 5:1 on the background image.

```html
<p
style="color: #CCC; height:50px; padding-top:15px; background: #000 no-repeat -20px -20px url('../test-assets/contrast/black-hole.jpeg');"
>
Black hole sun
</p>
```

#### Passed Example 4

This black text has a contrast ratio between 6.1:1 and 9:1 on gray background with white text shadow on it.

```html
<p style="color: #000; background: #737373; text-shadow: white 0 0 3px">
Some text in a human language
</p>
```

#### Passed Example 5

This 18pt large black text has a contrast ratio of 3.6:1 on the gray background.

```html
<p style="color: #000; font-size:18pt; background: #666;">
Some text in a human language
</p>
```

#### Passed Example 6

This 14pt bold black text has a contrast ratio of 3.6:1 on the gray background.

```html
<p style="color: #000; font-size:14pt; font-weight:700; background: #666;">
Some text in English
</p>
```

#### Passed Example 7

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.

**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.

```html
<p style="color: #333; background: #FFF;">
Helvetica is a widely used sans-serif typeface developed in 1957 by Max Miedinger and Eduard Hoffmann.
</p>
<p style="font-family: helvetica; background: #EEE; color: #777;" aria-hidden="true">
The quick brown fox jumps over the lazy dog.
</p>
```

#### Passed Example 8

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is Passed Example 8 intended to be an example of something non-meaningful or decorative? If so, should it not be an Inapplicable Example?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, this is an exception mentioned in the expectation. This is because "decorative" is subjective, and the ACT rules format requires the applicability to be objective.

Copy link
Collaborator

@EmmaJP EmmaJP Jan 14, 2020

Choose a reason for hiding this comment

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

So passed example 7 is including text that can be considered decorative (visual only), and passed example 8 is including text that is not conveying meaning.

And in this instance the exceptions are passing examples rather than inapplicable examples because ... ? The only reason I can determine is because the exception is in the expectation rather than the applicability. Is this consistent across rules? Should it be added to the rule guidance docs?

Cause in my understanding, a rule is inapplicable to something that is an exception.

This text does not convey anything in human language.

```html
<p style="color: #000; background: #666;">
----=====++++++++___________***********%%%%%%%%%%%±±±±@@@@@@@@
</p>
```

#### Passed Example 9

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.

```html
<p>Some text in a human language</p>
```

#### Passed Example 10

This dark gray text has a contrast ratio of 12.6:1 on the white background in a shadow DOM tree.

```html
<p style="color: #CCC; background: #fff;" id="p"></p>
<script>
const shadowRoot = document.getElementById('p').attachShadow({ mode: 'open' })
shadowRoot.textContent = '<span style="color: #333;">Some text in English</span>'
</script>
```

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looking at the Expectation, there seems to be missing a Passed Example with decorative text.

Copy link
Member Author

Choose a reason for hiding this comment

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

Passed example 7 has decorative text. We'll update the example description to make this clearer.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I disagree.

I think that the text in Passed Example 7 (the font example) does not meet the WCAG definition of "pure decoration" that is the exception to the SC:

serving only an aesthetic purpose, providing no information, and having no functionality

The text in that example has the functionality of showing the font. This interpretation (it is not pure decoration) is seconded by the note on that example saying that it needs a 3:1 contrast to meet 1.4.11. I agree that it qualifies as a "Graphical Object" for 1.4.11:

Graphical Objects: Parts of graphics required to understand the content, except when a particular presentation of graphics is essential to the information being conveyed.
(even if it's "text graphics").

In any case, if the text in that example is "pure decoration", the note and the reference to 1.4.11 need to go away. If it is not "pure decoration" (which I think), then a "pure decoration" example should be added.

Copy link
Member Author

Choose a reason for hiding this comment

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

"functionality" as it is used in WCAG refers to things users can do on the page. Adding an item to a shopping cart is functionality. See the definition of functionality. The font shape isn't "information" either, otherwise 1.3.1 would apply, information conveyed through presentation.

Additionally, if this wasn't "purely decorative", it wouldn't be exempt under the 1.4.3 "incidental" bullet at all. Here's what 1.4.3's understanding doc says about it:

For example, if random words are used to create a background and the words could be rearranged or substituted without changing meaning, then it would be decorative and would not need to meet this criterion.

This is precisely the same here. You can change the words without changing the meaning. That makes it decorative.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree that the text is decorative.
I do not understand why a decorative text is relevant for 1.4.11 (as pointed out by the note in Passed Example 7).
(it is not a UI component, and it is not a "Graphical Object" either; so 1.4.11 does not apply)

This is not important enough to block that PR…

Copy link
Collaborator

Choose a reason for hiding this comment

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

Passed Example 7 is a little confusing. The first paragraph passes because it is using browser default styles. If the second paragraph is decorative, then it is an example of inapplicable.

Not sure how to split it as an example though. Perhaps a different example of each is needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll put a foreground and background color on passed example 7 to clear this up, and add a separate example for browser defaults. That's a good point.

### Failed

#### Failed Example 1

This light gray text has a contrast ratio of 2.3:1 on the white background.

```html
<p style="color: #AAA; background: white;">
Some text in English
</p>
```

#### Failed Example 2

This light gray text has a contrast ratio between 1.2:1 and 2.3:1 on the white to blue gradient background.

```html
<p style="color: #AAA; background: linear-gradient(to right, #FFF, #00F); width: 300px">
Some text in English
</p>
```

#### Failed Example 3

This light gray text has a contrast ratio between 2.7:1 and 3:1 on the background image.

```html
<p
style="color: #555; height:50px; padding-top:20px; background: black no-repeat -20px -20px url('../test-assets/contrast/black-hole.jpeg');"
>
Black hole sun
</p>
```

#### Failed Example 4

This black text with 30% alpha channel has a contrast ratio of 2.1:1 on the white background.

```html
<p style="color: rgba(0,0,0,.3); background: #FFF">
Some text in English
</p>
```

#### Failed Example 5

This black text with 30% opacity has a contrast ratio of 2.1:1 on the white background.

```html
<div style="background: #FFF">
<p style="color: #000; opacity: .3">
Some text in English
</p>
</div>
```

#### Failed Example 6

This light gray text has a contrast ratio of 2.3:1 on the white background in a shadow DOM tree.

```html
<p style="color: #aaa; background: #fff;" id="p"></p>
<script>
const shadowRoot = document.getElementById('p').attachShadow({ mode: 'open' })
shadowRoot.textContent = 'Some text in English'
</script>
```

#### Failed Example 7

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.

```html
<style>
#backgroundSplit {
color: rgba(90, 90, 90, 0.8);
background-position: top 0 left 0;
background-image: linear-gradient(90deg, transparent, transparent 3.3em, black 3.3em, black 6em);
padding: 0 1em;
}
</style>
<span id="backgroundSplit">
Hello world
</span>
```

### Inapplicable

#### Inapplicable Example 1

This text is not [visible][] because of `display: none`.

```html
<p style="display: none">Some invisible text in English</p>
```

#### Inapplicable Example 2

This text is not [visible][] because it is positioned off screen.

```html
<p style="position:absolute; top: -999em">Some invisible text in English</p>
```

#### Inapplicable Example 3

This text is not [visible][] because the foreground color is the same as the background color.

```html
<p style="color: white; background: white;" aria-hidden="true">Hidden text - U U D D L R L R B A S</p>
```

#### Inapplicable Example 4

This text is not the child of an HTML element.

```html
<svg>
<text x="0" y="15">I love SVG!</text>
</svg>
```

#### Inapplicable Example 5

This text not part of a [text node][].

```html
<p>
<img scr="../test-assets/contrast/example.png" alt="example" />
</p>
```

#### Inapplicable Example 6

This text is part of a widget because it is a child of a `button` element.

```html
<button>My button!</button>
```

#### Inapplicable Example 7

This text is part of a widget because it is a child of an element with the `role` attribute set to `button`.

```html
<div role="button">My button!</div>
```

#### Inapplicable Example 8

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"`.

```html
<label style="color:#888; background: white;">
My name
<input type="text" disabled />
</label>
```

#### Inapplicable Example 9

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"`.

```html
<label id="my_pets_name" style="color:#888; background: white;">
My pet's name
</label>
<div
role="textbox"
aria-labelledby="my_pets_name"
aria-disabled="true"
style="height:20px; width:100px; border:1px solid black;"
>
test
</div>
```

#### Inapplicable Example 10

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.

```html
<fieldset disabled style="color:#888; background: white;">
<label>
My name
<input />
</label>
</fieldset>
```

#### Inapplicable Example 11

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.

```html
<div role="group" aria-disabled="true" style="color:#888; background: white;">
<label>
My name
<input />
</label>
</div>
```

[disabled]: #disabled-element 'Definition of disabled'
[visible]: #visible 'Definition of visible'
[text node]: https://dom.spec.whatwg.org/#text 'Definition of text node'
Loading