Skip to content

contrast-color() MVP should support explicit light/dark colors rather than unspecified "very light/dark colors" #11534

@LeaVerou

Description

@LeaVerou

In #9166 we resolved to add contrast-color(<color>) that would generate either "a very light" or "a very dark" color, depending on what contrasts better with its argument, and contrast-color(<color> max) which would produce white and black.

However, in my experience working with designers, this "very light or very dark color" stuff would not fly. Like, at all. white is often acceptable as the text color for a darker background, but for lighter backgrounds, black (or an unspecified "very dark color") usually less so. Usually designers want a specific dark token for the light background case, not just an unspecified "very dark color".

Additionally, if you look at the color tokens specified by designers, it is actually very, very hard to generate a good dark color if all you know is the lightest tint. E.g. take a look at this Tailwind blue scale from https://palettes.colorjs.io/palettes/tailwind/ :

Image

If all you're given is blue 50, how can you calculate anything that even moderately resembles blue 950?? It's not just lightness. Note how the chroma is much higher, note how the hue shifts a little. This is true for most of them, this not a carefully picked example.

Yes, this means that authors could specify colors that do not have sufficient contrast. But with the current design, they still can: either by not using contrast-color() at all because it doesn't serve their needs, or by transforming the result with RCS in convoluted ways. I think "garbage in, garbage out" is okay. If people don't think so, perhaps we could introduce a safeguard and allow (with a MAY) the function to still return white/black if the colors passed have particularly egregious contrast (what that is could be UA-defined).

Syntax

Since at this point we're sufficiently early in the game that we can change contrast-color() in backwards incompatible ways, I would propose this redesign:

  • Drop max, make white and black the base case
  • Support additional arguments for a dark and light foreground color. These could be either ordered (with dark one first, since there are way more cases that you need an explicit dark color, than an explicit light color) or, ideally, the UA would figure out which one is meant for the light case and which one is meant for the dark case.

Basically, the grammar would become:

contrast-color() = contrast-color( <color>#{1, 3} )

Or, alternatively, if we want a more expressive version that allows us to extend more easily, it could be something like this:

contrast-color() = contrast-color( [<color> or <color>]? on <color>)

This would allow us to down the line support specifying the foreground color and determining the background color, though I'm not sure how a version that defaults to white but allows you to specify the dark color could look like (the grammar above allows you to specify both or none).

Perhaps something like this combines the best of both worlds:

contrast-color() = contrast-color( on <color>, <color>#{0, 2})

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Thursday afternoon

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions