[HN Gopher] How to have the browser pick a contrasting color in CSS
___________________________________________________________________
How to have the browser pick a contrasting color in CSS
Author : Kerrick
Score : 120 points
Date : 2025-05-17 16:26 UTC (6 hours ago)
(HTM) web link (webkit.org)
(TXT) w3m dump (webkit.org)
| crtasm wrote:
| >This browser does not support contrast-color(). Try this demo in
| a browser that does, like Safari Technology Preview
| judah wrote:
| And I don't yet see an entry for this on caniuse.com. I'm
| guessing this is super new.
| miiiiiike wrote:
| Very new. I think Safari is the only one that ships it and
| even then it's still in preview
| homebrewer wrote:
| It works in "Gnome Web", which is mostly a wrapper around
| WebKit.
|
| > Support for this feature first shipped in March 2021, in
| Safari Technology Preview 122.
|
| https://webkit.org/blog/11577/release-notes-for-safari-
| techn...
|
| > Added experimental support for CSS Color 5 color-
| contrast()
|
| https://trac.webkit.org/changeset/273683/webkit/
| jeroenhd wrote:
| MDN doesn't even have it yet. Looks like it's WebKit
| exclusive for a while.
|
| The [draft for addition to the CSS
| spec](https://drafts.csswg.org/css-color-5/#resolving-
| contrast) just got added. Kind of wonder why Apple includes
| this in Safari as color-contrast rather than -webkit-color-
| contrast until other browsers have at least indicated a
| position on this draft. All I can find is decisions to defer
| the specifications (going back to as far as 2020).
| ctippett wrote:
| You don't need the Technology Preview, it's available as a
| WebKit Feature Flag under the advanced settings of normal
| Safari. I just enabled it on my phone and was able to view the
| demos.
| mgkimsal wrote:
| not available on desktop safari (version 18.2
| (20620.1.16.11.8)) under feature flags.
| ctippett wrote:
| I'm on version 18.5 (20621.2.5.11.8) and it's there.
| JimDabell wrote:
| It's available in desktop Safari here, I'm using version
| 18.5 (20621.2.5.11.8).
|
| Safari has had several security fixes since then, so you
| should update:
|
| 18.3: https://support.apple.com/en-us/122074
|
| 18.3.1: https://support.apple.com/en-us/122285
|
| 18.4: https://support.apple.com/en-us/122379
|
| 18.5: https://support.apple.com/en-us/122719
|
| Also, 18.4 was a pretty big update for standards support
| and other features:
|
| https://webkit.org/blog/16574/webkit-features-in-
| safari-18-4...
| mediumsmart wrote:
| >But, on a large project, with a large team, carefully managing
| such details can become a really hard task to get right. Suddenly
| a dark button has unreadable black text, and users can't figure
| out what to do.
|
| _Cant someone take a look at the buttons before the large
| project ships? Alternatively make it mandatory to never have
| black text on a dark button and tell every team member including
| the large ones._
|
| Interesting to read about the perceptual contrast vs mathematical
| - I did not know that. Going to integrate that into my workflow.
| johnisgood wrote:
| You may want to read about APCA, as you can have perceptual
| contrast calculations using the APCA algorithm.
| refulgentis wrote:
| You can have them with WCAG2, the stock APCA example hides
| the ball significantly and leads to a lot of incorrect
| conclusions in the article (tl;dr: black has _more_ contrast
| by either measure, its just that APCA says you don 't need
| _as much_ contrast, so you can use white and have
| _sufficient_ contrast)
| johnisgood wrote:
| I know about WCAG, too. You can also just implement a
| function that detects whether or not a color is dark or
| not. It is a general purpose function, e.g. my "isDark"
| function is: "func() < 0.5" (func() is omitted, but it is
| an algorithm). You can have "isLight", too, by doing ">
| 0.5". There are many ways to do this. You can just simply
| convert a hex color to RGB, then compute the luminance of
| the color, and then compare the luminance to a threshold
| (e.g. 0.5) to classify it as dark or light. The luminance
| function (WCAG luminance formula) converts RGB values to
| the range 0-1, applies gamma correction, and calculates
| luminance using the weighted sum of the gamma-corrected RGB
| values.
|
| > APCA says you don't need as much contrast
|
| You can always specify the threshold if you want, e.g.
| "apcaContrast(color)) >= $targetContrast" after adjusting,
| depending on what you want to do.
|
| It really is easy, just make sure you have enough color
| space.
| refulgentis wrote:
| The WCAG luminance formula (relative luminance in color
| science terms) has perceptual mid gray at 0.18, not 0.5.
|
| re: just change APCA contrast target, that's separate
| from the Not Even Wrong stuff in the article. I didn't
| mean to imply APCA is wrong to say you need less
| contrast, but rather, that the article is wrong to
| conclude white has more contrast.
| johnisgood wrote:
| Well, I used 0.5 as a convenient and intuitive midpoint
| of the 0-1 luminance range, but this of course is a
| simplification and doesn't align with human perception
| (edit: it is aligned), it was more of an example if
| anything.
|
| You are right, 0.18 is indeed perceptually closer to
| "middle gray" because the eye responds more sensitively
| to darker tones, so yeah, using a threshold closer to
| 0.18 makes more sense if we want to identify whether a
| color "feels" light or dark.
|
| That said, 0.5 is a mathematical midpoint, but as I said,
| not aligned with how humans perceive brightness (edit: it
| is aligned).
|
| Ultimately one could use 0.18-0.3 as threshold.
| anoncareer0212 wrote:
| > midpoint of the 0-1 luminance range
|
| There are two physical quantities for luminance,
| relative, and perceptual, so that passed along a nugget
| for those not as wise as you who might not know that :)
| As you know and have mentioned, using 0.5 with the
| luminance calculation you mentioned, for relative
| luminance, would be in error (I hate being pedantic, but
| it's important for some parties, a11y is a de facto legal
| requirement for a lot of work, and 0.5 would be spot on
| for ensuring WCAG 2 text contrast as long as used with
| perceptual luminance, L*)
|
| > doesn't align with human perception
|
| It is 100% aligned with how humans perceive brightness,
| in fact, it's a stable work product dating back to the
| early 1900s.
|
| > Ultimately one could use 0.18-0.3 as threshold
|
| Perceptual luminance and relative luminance have precise
| mathematical definitions, one can be calculated in terms
| of the other.
|
| If you need to hit contrast K with background color C,
| you won't be able to treat this as variable. What you
| pass along about it being variable is valuable, of
| course, in that, given K and C, output has a _range_ ,
| i.e. if contrast algo says you need +40 L* for your text
| to hit APCA/WCAG whatever, and your C has 50 L*, your
| palette is everything from 90 L* to 100 L* and 0 L* to 10
| L*.
| johnisgood wrote:
| So 0.5 is correct after all?! I thought I was completely
| off with 0.5 and I thought it does not align with human
| perception because I thought I was wrong. Ouch. In my
| defense, it has been a while. :D
|
| BTW, would this relatively simple way to determine if the
| color is dark work? $luminance = 0.299 *
| $r + 0.587 * $g + 0.114 * $b; return $luminance <
| $threshold;
|
| Where $threshold is 128, I think? IIRC 128 is a common
| threshold from what I remember, in this case.
| mediumsmart wrote:
| I thought the white looks sharper but is not really. I
| would darken the blue a bit to be happy about it.
| refulgentis wrote:
| The article is wrong:
|
| - Their work _does_ ensure contrast.
|
| - The white on blue clearly has _less_ contrast, not _more_.
| (squinting is a cheap way to test, or, walking backwards from
| your monitor)
|
| With APCA, backgrounds around L* 60 tend to still allow white
| foregrounds, which is _aesthetically_ closer to what the eye
| wants.
|
| A black foreground would have more contrast regardless, even by
| APCA.
|
| To be fair, this is how APCA is almost always demonstrated as a
| win over the long-running standard, so people run with the
| premise that the demo image of APCA is _more contrast_ , rather
| than "ours say you'll have _enough_ contrast to be accessible
| with a white foreground, even if it also says the contrast would
| be higher with a black foreground ".
|
| (source: in 2020 built color system around the same science,
| enabling latest iterations of Material theming)
| refulgentis wrote:
| Voters, I'd be very happy for feedback, I'm quite surprised it
| is -3.
|
| EDIT:
|
| I get it, it is easily read as "the entire article is wrong"
| instead of "the article is wrong on these points"
|
| You're free to elaborate on your concerns. We could raise this
| to a conversation, I think that'll feel better for both of us
| than me taking that remark about me personally.
|
| For example, I agree that the primary container color shouldn't
| have been L* 90 _and_ used for buttons, and they shouldnt have
| severely limited chroma. In fact, I left over it and the
| dysfunction between VPs wondering why we didn 't have it day 1,
| approving fixes repeatedly, and Android dysfunction that kept
| the conversation at "What? Didn't hear nothing from nobody in
| engineering! Anyways, lock screen clocks!"
| troupo wrote:
| I didn't vote, but "your article is wrong" take ignores
| literally the entire article, and the rather detailed
| explanation on why "bigger contrast by pure numbers is more
| contrast" does not work.
|
| > in 2020 built color system around the same science,
| enabling latest iterations of Material theming
|
| No wonder everything Google builds, including Material,
| always has issues with contrast.
| qfr wrote:
| there is a way to do something close to this using lch:
| --text: lch(from var(--bg) calc((49.44 - l) * infinity) 0 0);
|
| source: https://til.jakelazaroff.com/css/swap-between-black-and-
| whit...
| natemwilson wrote:
| I've never seen any CSS function that has this call back style
| where you get parameters that you can modify. So interesting!
| Are there any other examples of this or is this unique to lch?
| fireflies_ wrote:
| This is "relative color" syntax, it works with a range of
| color spaces/color functions. The key is the "from" at the
| front. Here's the MDN documentation:
| https://developer.mozilla.org/en-
| US/docs/Web/CSS/CSS_colors/...
| halflife wrote:
| It may be confusing, but everything here is static param. The
| --- prefix is css variables, where inside a css declaration
| block you write: --bg: blue
| LorenzA wrote:
| there is a good article from lea verou
| https://lea.verou.me/blog/2024/contrast-color/ on a workaround
| like this
| politelemon wrote:
| I'm still not convinced that the contrasting colour should be the
| browser vendor's decision, it won't always be right or
| predictable. Will this be a definitive deterministic standard
| across all browsers? Instead this function feels like a tool to
| help UX teams during design phase.
| refulgentis wrote:
| c.f. https://news.ycombinator.com/item?id=44015980, when you
| cut out the incorrect stuff due to confusion re: APCA's button
| example, it's a bit clearer that it's 100% right.
|
| Consistent, it is not. Ex. we can imagine a background at L* 50
| that is ~equally served with a white or black foreground - in
| that case, the aesthetic principles come into play.
|
| To also disambiguate that, and get to 100% reliable, if both a
| darker and lighter color are available given contrast K and
| background color C, look at C, if it's L* is >= 60, choose
| lighter.
|
| Then, it is 100% correct _and_ consistent.
| MBCook wrote:
| > Will this be a definitive deterministic standard across all
| browsers?
|
| The article says the standard specifies the calculation to use.
| andix wrote:
| I'm already feeling some issues with HDR displays, embedded
| devices, and other special cases. The standard Safari on
| macOS/iOS and chrome on Windows/Linux/Android are probably
| going to handle it correctly. But I'm very happy if proven
| wrong :)
| mcfedr wrote:
| Choose is a strange word here. There is an algorithm that
| calculates the color.
| jbritton wrote:
| At a minimum it would be nice to know good colors for the pseudo
| classes active, focus, hover, link, visited and their various
| combinations for a light and dark theme. Additionally material UI
| adds disabled, before, after.
| dp-hackernews wrote:
| Surely the relative colour theory colour wheel is the answer to
| this problem.
|
| "Color Wheel: The Basic Color Theory for Artists and Designers"
| https://dessign.net/color-wheel-theory/
| atum47 wrote:
| I made a video tutorial about a similar thing long time ago -
| choosing black or white for text color given a color background.
| My solution was very simplistic. I just transformed the color to
| gray scale and compared it between black and white. It was a fun
| project. I'm not good making videos though.
|
| https://youtu.be/tUJvE4xfTgo?si=vFlegFA_7lzijfSR (warning: video
| is in Portuguese)
| coolcase wrote:
| Funny a sister comment gave a color space formula to do just
| that
|
| https://news.ycombinator.com/item?id=44015990
|
| Video seems fine. I don't speak Portuguese though so can't
| judge what you said but code looks good!
| andix wrote:
| Is there a good alternative for this that is done at build time?
| Something that works on top of SASS, Tailwind, etc?
|
| It will take some time until this feature is broadly available,
| and I'm having some doubt that it will be implemented in the same
| (or correct) way on all platforms.
| jjcm wrote:
| This is a great overview of the pros/cons of this. For those
| creating just a simple site, this is a solid easy way to have
| proper contrast.
|
| For those making anything at a production scale where you need
| wcag compliance however, I'd avoid this and leverage a proper
| semantic token layer. Semantic tokens will help both accelerate
| your dev cycle, and they'll help guarantee proper contrast ratios
| in a way that looks visually better than just switching your
| foreground layer to black or white. The great thing about a
| semantic token layer is they're extremely easy to theme, which
| means you get light/dark theming for very little additional cost.
| You can also create separate WCAG2 / APCA accessible themes,
| should your brand color be one of the ones that WCAG2 has issues
| with - will get you compliance while still providing a better
| visual contrast option.
|
| This is kind of my niche domain specialty - I run the
| variables/tokens stream at Figma, and I've worked on the dark
| mode implentation for both Figma and Atlassian. Happy to answer
| any questions about tokens/themes/accessible color.
| charrondev wrote:
| What do you mean by semantic tokens?
|
| This exact type of functionality has caused a major project a
| work on to use CSS in JS (for relative colors and contrast
| colors.
|
| I'm glad to see this type of thing coming around the corner and
| look forward to it being widely available in a couple years.
| jjcm wrote:
| With regards to color on the web, semantic tokens refer to
| css variables that are named in a way that describes their
| use, ie:
|
| * bg-brand (this would be used whenever you need your brand
| color as a background)
|
| * text-danger (likely a red text color)
|
| * icon-warning-hover (likely a dark yellow-orange that's
| slightly different from icon-warning)
|
| Generally speaking, there are three "levels" of tokens:
| primitive, semantic, and component. Primitive tokens describe
| the value. In the case of color, this might be a color ramp.
| IE red/100, red/200, red/300. Semantic tokens reference
| primitive tokens. IE bg-brand might have its value set to
| blue/300. This layer is sometimes called a "reference" layer
| because of this, but I'm not a fan of that nomenclature since
| the component layer also references the semantic layer. The
| component layer is one that describes where in a component
| the token should be used, ie button-bg or button-text. I
| highly, HIGHLY recommend against using a component layer
| though in all but the most extreme multi-brand situation. If
| you aren't unilever, you should never use component tokens.
___________________________________________________________________
(page generated 2025-05-17 23:00 UTC)