Skip to content

Commit e59859d

Browse files
LeaVeroufantasai
andcommitted
[css-color-6] contrast-color() algorithm syntax changes
- If color candidates are provided, algorithm is mandatory, otherwise a UA dependent algorithm is used (part of the resolution in #7361 ) - Reorganization of prose and examples - Add keyword to specify algorithm without target contrast Co-Authored-By: fantasai <725717+fantasai@users.noreply.github.com>
1 parent 9065544 commit e59859d

File tree

1 file changed

+127
-108
lines changed

1 file changed

+127
-108
lines changed

css-color-6/Overview.bs

Lines changed: 127 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Introduction {#intro}
3434

3535
<em>This section is not normative.</em>
3636

37-
This module adds two one function:
37+
This module adds a new <<color>> function:
3838
''contrast-color()''.
3939

4040

@@ -52,88 +52,44 @@ Introduction {#intro}
5252
https://caniuse.com/mdn-css_types_color_color-contrast
5353
-->
5454

55-
Computing a sufficiently contrasting color: the ''contrast-color()'' function {#colorcontrast}
55+
Computing a Contrasting Color: the ''contrast-color()'' function {#colorcontrast}
5656
========================================================================================
5757

58-
The purpose of this function is to generate sufficiently contrasting colors from other colors,
59-
without authors having to compute them manually.
58+
The <dfn>contrast-color()</dfn> [=functional notation=]
59+
identifies a sufficiently contrasting color
60+
against a specified background or foreground color
61+
without requiring manual computation.
6062

6163
Its syntax is:
6264

6365
<pre class='prod'>
64-
<dfn>contrast-color()</dfn> = contrast-color( <<color>> [ vs <<color>>#{2,} ]? [ to <<target-contrast>>]? )
65-
<dfn><<target-contrast>></dfn> = wcag2([<<number>> | [ aa | aaa ] && large? ])
66+
<<contrast-color()>> = contrast-color( <<color>> [[ vs <<color>># ]? to <<target-contrast>>]? )
67+
<<target-contrast>> = <<wcag2>>
6668
</pre>
6769

68-
The only mandatory argument is the first color, which is the color to be contrasted,
69-
typically (but not necessarily) a background color.
70+
The only mandatory argument is the <dfn lt="contrast base color" local-lt="base color">base color</dfn>
71+
against which the contrast is computed.
72+
This is typically (but not necessarily) a background color.
7073

71-
An optional list of color candidates is provided after a ''vs'' keyword.
74+
An optional list of <dfn local-lt="color candidates">contrast color candidates</dfn> may be provided after a ''vs'' keyword.
7275
If no candidates are provided, the default candidates are used: ''white, black''.
7376

74-
An optional target <a href="https://www.w3.org/TR/WCAG21/#contrast-minimum">luminance contrast</a> [[!WCAG21]]
75-
is provided after a ''contrast-color()/to'' keyword.
77+
The <dfn><<target-contrast>></dfn> argument specifies the contrast algorithm to use.
78+
If no [=color candidates=] have been provided,
79+
<<target-contrast>> may be omitted,
80+
in which case a UA-chosen algorithm is used.
81+
Arguments to a <<target-contrast>> [=functional notation=]
82+
indicate the <dfn>target contrast level</dfn>.
7683

77-
If a target contrast is specified,
78-
the <a href="#winner">returned color</a> is the first color candidate that meets or exceeds the target contrast.
79-
If no target is given,
84+
If the [=target contrast level=] is omitted,
8085
the color candidate with the greatest contrast is returned.
81-
If multiple candidates have the same contrast,
82-
the one that comes earliest in the list is returned.
86+
Otherwise,
87+
the returned color is the first color candidate that meets or exceeds that level,
88+
defaulting to ''white'' or ''black'' if none qualify.
8389

84-
The single color is separated from the list
85-
with the keyword <css>vs</css>
86-
and the target contrast, if present, is separated from the list
87-
with the keyword <css>to</css>.
8890

89-
The target contrast is specified by providing a function for the algorithm used
90-
(currently only [WCAG 2.1](#luminance-contrast) is supported )
91-
with the contrast ratio as the argument.
92-
93-
For ''wcag2()'', ''aa'' is equivalent to ''4.5'',
94-
''aa large'' (or ''large aa'') is equivalent to ''3'',
95-
''aaa'' is equivalent to ''7'',
96-
and ''aaa large'' (or ''large aaa'') is equivalent to ''4.5''.
97-
98-
<h3 id="luminance-contrast">
99-
Calculating luminance and WCAG 2.1 contrast
100-
</h3>
101-
102-
For each color in the list,
103-
the CIE Luminance (Y) is calculated,
104-
relative to a [=D65 whitepoint=].
105-
106-
For each pair of colors,
107-
the WCAG 2.1 contrast is calculated:
108-
contrast = (Y<sub>l</sub> + 0.05) / (Y<sub>d</sub> + 0.05)
109-
where Y<sub>d</sub> is the luminance of the darker color in the pair
110-
and Y<sub>l</sub> is the luminance of the lighter color.
111-
The factor 0.05 represents the luminance contribution of the viewing flare.
112-
113-
<div class="example">
114-
Suppose that the single color was
115-
116-
<pre class="lang-css">color(display-p3 0.38 0.11 0.05)</pre>
117-
118-
while the first color in the list was
119-
120-
<pre class="lang-css">yellow</pre>
121-
122-
The calculation is as follows:
123-
* <span class="swatch" style="--color: rgb(41.482% 7.941% 1.375%)"></span> color(display-p3 0.38 0.11 0.05) is <span class="swatch" style="--color: rgb(41.482% 7.941% 1.375%)"></span> color(xyz 0.06191 0.03568 0.00463) so the relative luminance is <b>0.03568</b>
124-
* <span class="swatch" style="--color: yellow"></span> yellow is <span class="swatch" style="--color: yellow"></span> rgb(100% 100% 0%) which is <span class="swatch" style="--color: yellow"></span> color(xyz 0.76998 0.92781 0.13853) so the relative luminance is <b>0.92781</b>
125-
* the contrast is (0.92781 + 0.05) / (0.03568 + 0.05) = <b>11.4123</b>
126-
</div>
127-
128-
<h3 id="winner">
129-
Finding the winning color
130-
</h3>
131-
132-
It then selects from that list
133-
the first color to meet or exceed the target contrast.
134-
If no target is specified,
135-
it selects the first color with the highest contrast
136-
to the single color.
91+
<h3 id="contrast-color-winner">
92+
Finding the Winning Color</h3>
13793

13894
<!--
13995
<wpt>
@@ -144,20 +100,14 @@ Computing a sufficiently contrasting color: the ''contrast-color()'' function {#
144100
</wpt>
145101
-->
146102

147-
<div class="example">
148-
<pre class="lang-css">contrast-color(wheat vs tan, sienna, var(--myAccent), #d2691e)</pre>
149-
150-
The calculation is as follows:
151-
* <span class="swatch" style="--color: wheat"></span> wheat (#f5deb3), the background, has relative luminance 0.749
152-
* <span class="swatch" style="--color: tan"></span> tan (#d2b48c) has relative luminance 0.482 and contrast ratio <strong>1.501</strong>
153-
* <span class="swatch" style="--color: sienna"></span> sienna (#a0522d) has relative luminance 0.137 and contrast ratio <strong>4.273</strong>
154-
155-
Suppose myAccent has the value <span class="swatch" style="--color: #b22222"></span> #b22222:
156-
* #b22222 has relative luminance 0.107 and contrast ratio <strong>5.081</strong>
157-
* <span class="swatch" style="--color: #d2691e"></span> #d2691e has relative luminance 0.305 and contrast ratio <strong>2.249</strong>
158-
The highest contrast ratio is <strong>5.081</strong> so var(--myAccent) wins
103+
<h4 id="contrast-color-target-winner">
104+
If there is a target contrast</h4>
159105

160-
</div>
106+
Candidate colors are tested sequentially,
107+
starting with the first color in the list,
108+
and ending with an automatically appended ''white, black''.
109+
The first color to pass the specified level of contrast
110+
against the [=base color=] wins.
161111

162112
<!-- live example
163113
https://colorjs.io/notebook/?storage=https%3A%2F%2Fgist.github.com%2Fsvgeesus%2Fec249f376fcecbaa8794f75dbfc1dacf
@@ -169,14 +119,12 @@ Computing a sufficiently contrasting color: the ''contrast-color()'' function {#
169119
* <span class="swatch" style="--color: wheat"></span> wheat (#f5deb3), the background, has relative luminance 0.749
170120
* <span class="swatch" style="--color: bisque"></span> bisque (#ffe4c4) has relative luminance 0.807 and contrast ratio <strong>1.073</strong>
171121
* <span class="swatch" style="--color: darkgoldenrod"></span> darkgoldenrod (#b8860b) has relative luminance 0.273 and contrast ratio <strong>2.477</strong>
172-
* <span class="swatch" style="--color: olive"></span> olive (#808000 ) has relative luminance 0.200 and contrast ratio <strong>3.193</strong>
122+
* <span class="swatch" style="--color: olive"></span> olive (#808000) has relative luminance 0.200 and contrast ratio <strong>3.193</strong>
173123
* <span class="swatch" style="--color: sienna"></span> sienna (#a0522d) has relative luminance 0.137 and contrast ratio <strong>4.274</strong>
174124
* <span class="swatch" style="--color: darkgreen"></span> darkgreen (#006400 ) has relative luminance 0.091 and contrast ratio <strong>5.662</strong>
175-
* <span class="swatch" style="--color: maroon"></span> maroon (#800000 ) has relative luminance 0.046 and contrast ratio <strong>8.333</strong>
176-
125+
* <span class="swatch" style="--color: maroon"></span> maroon (#800000) has relative luminance 0.046 and contrast ratio <strong>8.333</strong>
177126

178127
The first color in the list which meets the desired contrast ratio of 4.5 is <span class="swatch" style="--color: darkgreen"></span> darkgreen.
179-
180128
</div>
181129

182130
<div class="example">
@@ -186,47 +134,56 @@ Computing a sufficiently contrasting color: the ''contrast-color()'' function {#
186134
* the relative luminances and contrast ratios are the same as the previous example.
187135

188136
The first color in the list which meets the desired contrast ratio of 5.8 is <span class="swatch" style="--color: maroon"></span> maroon.
137+
</div>
138+
139+
<div class="example">
140+
<pre class="lang-css">contrast-color(wheat vs bisque, darkgoldenrod, olive to wcag2(AA))</pre>
189141

142+
The calculation is as follows:
143+
* the relative luminances and contrast ratios are the same as the previous example.
144+
145+
No color in the list meets the desired contrast ratio of 4.5.
146+
The contrast of <span class="swatch" style="--color: white"></span> ''white'' against the
147+
[=base color=] of <span class="swatch" style="--color: wheat"></span> ''wheat'' is 1.314, which is smaller than 4.5 (AA),
148+
and thus does not pass the target contrast.
149+
Therefore, <span class="swatch" style="--color: black"></span> ''black'' is returned, which has a contrast of 15.982 > 4.5.
190150
</div>
191151

192-
The candidate colors are tested sequentially,
193-
from left to right;
194-
a color is the temporary winner
195-
if it has the highest contrast of all those tested so far.
152+
<h4 id="contrast-color-max-winner">
153+
If no target contrast is specified</h4>
196154

197-
List traversal is terminated once the target contrast has been met or exceeded.
155+
Candidate colors are tested sequentially,
156+
starting with the first color in the list.
157+
A color is the <var>temporary winner</var>
158+
if it has the highest contrast against the [=base color=] of all those tested so far.
198159

199-
Once the end of the list is reached, if there is no target contrast,
200-
the current temporary winner is the overall winner.
160+
Once the end of the list is reached, the current <var>temporary winner</var> is the overall winner.
201161
Thus, if two colors in the list happen to have the same contrast,
202-
the earlier in the list wins
203-
because the later one has the same contrast, not higher.
204-
205-
If there is a target contrast,
206-
and the end of the list is reached without meeting that target,
207-
either ''white'' or ''black'' is returned,
208-
whichever has the higher contrast.
162+
the earlier one wins.
209163

210164
<div class="example">
211-
<pre class="lang-css">contrast-color(wheat vs bisque, darkgoldenrod, olive to wcag2(AA))</pre>
165+
<pre class="lang-css">
166+
--myAccent: #b22222;
167+
color: contrast-color(wheat vs tan, sienna, var(--myAccent), #d2691e to wcag2)
168+
</pre>
212169

213170
The calculation is as follows:
214-
* the relative luminances and contrast ratios are the same as the previous example.
215-
216-
No color in the list meets the desired contrast ratio of 4.5,
217-
so <span class="swatch" style="--color: black"></span> black
218-
is returned as the contrast (15.982)
219-
is higher than that of white (1.314).
171+
* <span class="swatch" style="--color: wheat"></span> wheat (#f5deb3), the background, has relative luminance 0.749
172+
* <span class="swatch" style="--color: tan"></span> tan (#d2b48c) has relative luminance 0.482 and contrast ratio <strong>1.501</strong>
173+
* <span class="swatch" style="--color: sienna"></span> sienna (#a0522d) has relative luminance 0.137 and contrast ratio <strong>4.273</strong>
174+
* <span class="swatch" style="--color: #b22222"></span> --myAccent (#b22222) has relative luminance 0.107 and contrast ratio <strong>5.081</strong>
175+
* <span class="swatch" style="--color: #d2691e"></span> #d2691e has relative luminance 0.305 and contrast ratio <strong>2.249</strong>
220176

177+
The highest contrast ratio is <strong>5.081</strong> so ''var(--myAccent)'' wins
221178
</div>
222179

223180
<div class="example">
224181
<pre class="lang-css">
225182
foo {
226183
--bg: hsl(200 50% 80%);
227184
--purple-in-hsl: hsl(300 100% 25%);
228-
color: contrast-color(var(--bg) vs hsl(200 83% 23%), purple, var(--purple-in-hsl));
229-
}
185+
color: contrast-color(var(--bg) vs hsl(200 83% 23%), purple, var(--purple-in-hsl) to wcag2);
186+
}
230187
</pre>
231188

232189
The calculation is as follows:
@@ -241,6 +198,68 @@ Computing a sufficiently contrasting color: the ''contrast-color()'' function {#
241198
<!-- great example to use in WPT -->
242199
</div>
243200

201+
<h3 id="contrast-algorithms">
202+
Contrast algorithms
203+
</h3>
204+
205+
ISSUE: Currently only [[#luminance-contrast|WCAG 2.1]] is supported,
206+
however this algorithm is known to have problems,
207+
particularly on dark backgrounds.
208+
Future revisions of this module will likely introduce additional contrast algorithms.
209+
210+
<h4 id="luminance-contrast">
211+
WCAG 2.1: the ''wcag2'' keyword and ''wcag2()'' function
212+
</h4>
213+
214+
The <dfn for="contrast-color()" value>wcag2</dfn> keyword and <dfn for="contrast-color()">wcag2()</dfn> [=functional notations=]
215+
indicate use of the [[!WCAG21]] <a href="https://www.w3.org/TR/WCAG21/#contrast-minimum">luminance contrast</a> algorithm.
216+
Their syntax is:
217+
218+
<pre class="prod">
219+
<dfn><<wcag2>></dfn> = wcag | wcag2([<<number>> | [ aa | aaa ] && large? ])
220+
</pre>
221+
222+
Its [=target contrast level=] keywords map as follows:
223+
* ''aa'' computes to ''4.5''
224+
* ''aa large'' (or ''large aa'') computes to ''3''
225+
* ''aaa'' computes to ''7''
226+
* ''aaa large'' (or ''large aaa'') computes to ''4.5''
227+
228+
To find the contrast of a pair of colors,
229+
first their CIE Luminance (Y) is calculated relative to a [=D65 whitepoint=].
230+
Then the WCAG 2.1 contrast is calculated:
231+
contrast = (Y<sub>l</sub> + 0.05) / (Y<sub>d</sub> + 0.05)
232+
where Y<sub>d</sub> is the luminance of the darker color in the pair
233+
and Y<sub>l</sub> is the luminance of the lighter color.
234+
The factor 0.05 represents the luminance contribution of the viewing flare.
235+
236+
<div class="example">
237+
Suppose that the [=base color=] were
238+
239+
<pre class="lang-css">color(display-p3 0.38 0.11 0.05)</pre>
240+
241+
while the first [=candidate color=] in the list were
242+
243+
<pre class="lang-css">yellow</pre>
244+
245+
The calculation is as follows:
246+
* <span class="swatch" style="--color: rgb(41.482% 7.941% 1.375%)"></span> color(display-p3 0.38 0.11 0.05) is <span class="swatch" style="--color: rgb(41.482% 7.941% 1.375%)"></span> color(xyz 0.06191 0.03568 0.00463) so the relative luminance is <b>0.03568</b>
247+
* <span class="swatch" style="--color: yellow"></span> yellow is <span class="swatch" style="--color: yellow"></span> rgb(100% 100% 0%) which is <span class="swatch" style="--color: yellow"></span> color(xyz 0.76998 0.92781 0.13853) so the relative luminance is <b>0.92781</b>
248+
* the contrast is (0.92781 + 0.05) / (0.03568 + 0.05) = <b>11.4123</b>
249+
</div>
250+
251+
252+
253+
254+
255+
256+
257+
258+
259+
260+
261+
262+
244263

245264
<!--
246265
████████ ████████ ██████ ███████ ██ ██ ██ ████████

0 commit comments

Comments
 (0)