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