I typed out the sunset gradient in the landing page hero from the comp, and both ends looked good. Then feedback came back that the subtitle looked fuzzy where the white text sat on top. I blamed font size and weight first, and only later realized the real issue: the line runs all the way from #ff512f to #dd2476, and whoever signed off only looked at the pretty end. Treated as two separate flat colors, white text measures 3.25:1 at the 0% stop and 4.58:1 at the 100% stop; the missing half-step is all at the start.
Only two thresholds, and the worst stop decides
The wording follows the page itself: body text must reach at least 4.5:1, large text (24px and up, or 18.66px bold) at least 3:1, and the worst stop is judged against both levels at once. It is a threshold, not a guarantee that everyone can read the text once it passes. Baselines: #000000 on #ffffff is the theoretical ceiling at 21.00:1; #767676 on white is 4.54:1 and #777777 on white is 4.48:1, so one shade of gray flips the verdict, which is why I keep two decimal places. The sunset gradient with black text gives 6.47:1 and 4.59:1, passing both levels.
Six output strings for the same gradient, and where they differ
Changing only the format, leaving the stops and the angle untouched, I switched through each one in the CSS Gradient Generator:
| Output format | Resulting string |
|---|---|
| background-image single line | background-image: linear-gradient(135deg, #ff512f 0%, #dd2476 100%); |
| background shorthand | background: linear-gradient(135deg, #ff512f 0%, #dd2476 100%); |
| Multi-line indented | |
| Inline style | <div style="background: linear-gradient(135deg, #ff512f 0%, #dd2476 100%)"> |
| Tailwind arbitrary value | class="bg-[linear-gradient(135deg,#ff512f_0%,#dd2476_100%)]" |
| CSS variable | |
Only the Tailwind line changes characters: no space after commas, and separating spaces become underscores.
Stop by stop, light palettes almost always need black text
| Palette | White at 0% | White at 100% | Worst white | Worst black |
|---|---|---|---|---|
| Sunset 135deg | 3.25:1 | 4.58:1 | 3.25:1 | 4.59:1 |
| Ocean 120deg | 12.55:1 | 1.91:1 | 1.91:1 | Not measured |
| Mint 90deg | 1.59:1 | 1.34:1 | 1.34:1 | 13.18:1 |
| Neon 45deg | 6.28:1 | 3.72:1 | 3.72:1 | 3.35:1 |
- Ocean with white text has plenty of room at 12.55:1 at the 0% stop, but only 1.91:1 at 100%. Adding a midpoint gives 12.55 / 10.92 / 1.91, and the worst is still 1.91.
- Mint has a worst white of 1.34:1 and a worst black of 13.18:1, so a light background has to switch to black text.
- Neon has a worst white of 3.72:1 and a worst black of 3.35:1, and both directions stop at the large-text-only level.
Transparent stops must be blended with an assumed backdrop first
The fading green is #39ff14 going from 1 to 0, with three notations and three different strings: HEX writes linear-gradient(135deg, #39ff14 0%, #39ff1400 100%), rgb writes rgba(57,255,20,0) and hsl writes hsla(111,100%,54%,0). In an 8-digit HEX the last two digits are alpha: 0.6 maps to 99 (0.6 x 255 = 153), 0.5 maps to 80 and 0.99 maps to fc. Conversely #aabbcc80 parses as 0.5019607843137255, not a clean 0.5, and transparent is alpha 0 of #000000. When the notation is unclear I check it in the Color Format Converter.
A stop with alpha below 1 is blended with the assumed backdrop (white by default) before measuring, and changing the backdrop changes the result: on white it is 1.36:1 at the 0% stop and 1.00:1 at 100%; with a #0a0a0a backdrop the 100% stop rises to 19.80:1 while 0% stays at 1.36:1, so the worst is still 1.36. A dark backdrop rescues only the far end, not the whole line. At that point the page also warns that 8-digit HEX needs Safari 9.1+; for older environments, switch to rgb().
The report has no middle section, and quantization is another trap
The cost of measuring stop by stop is that a midpoint which is not itself a stop never appears in the report. Averaging the two sunset colors per sRGB channel gives a midpoint of #ee3b53, where white text is 3.90:1 and black text 5.39:1, better than 3.25 at the 0% stop but still stuck at the large-text-only level. The muddy gray follows the same arithmetic: the channel average of the ocean ends #1a2980 and #26d0ce is #207da7, a gray-blue that comes from averaging deep blue with bright cyan. To get it reported, write the middle color directly at 50%, the way the three-stop purple preset does.
Quantization is the other trap: each channel has 256 levels from 0 to 255, and 256 cubed is 16,777,216 colors. When two colors fill a 1000px width, each level takes 3.9063px, about one step every 4px, and at 45deg the gradient line is stretched by about 1.4142 times. The channel difference between the two sunset colors is 34 levels in R, 45 in G and 71 in B.
A few questions that tripped me up at the time
Q: Does 0deg point up or right?
Up. CSS 0deg points straight up, and the number grows clockwise: 90deg is right, 180deg is down. In math polar coordinates 90deg points up, so do not mix the two. The angle field accepts -360 to 720, but the output is normalized: -45 becomes 315, 405 becomes 45, and 360 and 720 both become 0.
Q: Is a conic percentage a distance? When should the fallback color be checked?
It is an angle: 100% equals 360deg, and the rainbow ring runs a full circle from 0% to 100%. The spec defines repeating-conic-gradient but no browser implements it, so the page does not offer it. The fallback color is not required by the gradient itself; checking it adds just one line, background-color: #ff512f;, taken from the first stop after sorting. I only check it when using conic, which needs Safari 12.1+; older browsers drop the entire declaration. With linear or radial I leave it off, since it cannot change the contrast of the stops themselves.
Q: What happens if stop positions are entered out of order?
Output is re-sorted from smallest to largest: entering 100, 0, 50 yields linear-gradient(90deg, #8a2387 0%, #e94057 50%, #f27121 100%). Two stops at the same 12% leave no interpolation zone and produce a hard edge; the diagonal stripe preset draws stripes from the four positions 0%, 12%, 12% and 24%, so one period is only 24% and the remaining 76% repeats 4.1667 times by tiling.
Back to that line about white text being hard to read. My method has three steps: measure every stop as a flat color and see whether the worst value falls below 4.5 or below 3; check whether the report includes a midpoint and add one by hand when the gradient crosses hues, because the middle is where it usually fails; then check the type size, since only 24px and up, or 18.66px bold, is allowed to stop at 3. For the sunset set I ended up switching body text to black (worst 4.59:1) and keeping white only for large headings. With neon, white at 3.72 and black at 3.35 both fall short for body text, so I no longer put body copy on it. The question is not whether a gradient can be used, but that nobody had measured the worst stop.