jjcm 3 hours ago

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 2 hours ago

    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 2 hours ago

      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.

      • ZYbCRq22HbJ2y7 an hour ago

        Aren't there many, many schemes for naming tokens in design systems? Aren't you being a bit forward in presenting this as a general practice?

        https://medium.com/eightshapes-llc/naming-tokens-in-design-s...

        • ryanwhitney an hour ago

          Not parent, but the generalization is true. There’s usually a base layer (red/300, etc) and a more semantic layer (.text-danger).

          As your link covers, there’s then a million different ways to implement/extend that based on whatever theming and systems you’re implementing on top.

      • recroad an hour ago

        This only works if you don’t let users theme your site. If you do, then OPs approach works better.

econ 35 minutes ago

Back when systeem colors were actually cool I made some system color styles. It looked really nice but you don't know how they contrast. That one is called [say] buttonFace and another buttonText turned out to to be meaningless. Someone wrote some js for me that took getComputedStyle and calculated the contrast. If it was unacceptable it either took a second candidate color or failed back on text-shadow to darken or lighten an aura around the text sufficiently.

https://i.sstatic.net/18bQt.png

I forget the calculation but thinking about it you can probably just take the average of the 3 rgb values and compare them(?) It would produce a low value for blue and give preference to white text.

qfr 6 hours ago

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 4 hours ago

    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?

    • KTibow 43 minutes ago

      Some newer ones like calc-size are also like this.

    • halflife 4 hours ago

      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

mediumsmart 7 hours ago

>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 6 hours ago

    You may want to read about APCA, as you can have perceptual contrast calculations using the APCA algorithm.

    • refulgentis 6 hours ago

      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 6 hours ago

        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 5 hours ago

          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 5 hours ago

            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 4 hours ago

              > 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 3 hours ago

                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 4 hours ago

        I thought the white looks sharper but is not really. I would darken the blue a bit to be happy about it.

politelemon 6 hours ago

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.

  • MBCook 5 hours ago

    > Will this be a definitive deterministic standard across all browsers?

    The article says the standard specifies the calculation to use.

    • andix 3 hours ago

      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 4 hours ago

    Choose is a strange word here. There is an algorithm that calculates the color.

  • refulgentis 6 hours ago

    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.

jbritton 6 hours ago

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.

atum47 5 hours ago

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)

andix 3 hours ago

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.

flysand7 an hour ago

Sadly, doesn't work on firefox yet :(

crtasm 7 hours ago

>This browser does not support contrast-color(). Try this demo in a browser that does, like Safari Technology Preview

refulgentis 6 hours ago

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 5 hours ago

    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 5 hours ago

      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.