Skip to content

Instantly share code, notes, and snippets.

@una una/color-functions.md
Last active Jul 2, 2019

Embed
What would you like to do?
CSS Color Functions

CSS Color Functions

Web developers and design systems developers often use functions to design components. With the increasing usage of design systems that support multiple platforms, and increased capability of Dark Mode in UI, this becomes even more useful to not need to manually set color, and to instead have a single source from which layouts are calculated. Currently Sass, calc() on HSL values, or PostCSS is used to do this.

Examples:

  • Components with color variations based on calculations from a parent. (i.e. Button with outline that uses the primary button color to adjust the size)
  • Theming - Palletes based on a color or set of colors for themes. Especially when a single base system is used with multiple themes
  • Uniformity among transformations between components with different primary colors

Proposal

  • 3 functions: adjust-color, mix-color, and contrast-color
  • LCH is the default color space. You can specify transformations in another color space (ie. HSL lightness instead of the default LCH lightness - lightness(red, lch)). If the argument is non-compliant with the color space (i.e. lightness(red, cmyk)), the transformation will be clipped to CMYK after an LCH transformation.
  • In this example, like filter, > 100% or 1 = positive change (i.e. lighter), and < 100% or 1 = negative (i.e. makes it darker) and/or we can do negative and positive values based on 0 (- 30% || + 30%)

adjust-color

  • Adjusts color via:
    • lightness
    • chroma
    • hue
  • adjust-color(<color>, <colorFunctions(<amount>,<optional color space>)>)
  • ex. adjust-color(red, lightness(130%) chroma(0.4));
  • ex. adjust-color(red, lightness(130%, hsl) chroma(0.4, lch)); ← with color space argument

mix-color

  • Mix two colors by an specified ammount of the first value. See examples:
  • mix-color(<color1>, <color2>, <colorFunctions(<amount>,<optional color space>)>)
  • ex. mix-color(red, yellow, lightness(30%) chroma(0.8)); ← 30% of red lightness and 70% of yellow lightness, 80% of red alpha and 20% of yellow alpha
  • ex. mix-color(red, yellow, lightness(30%, rgb) chroma(0.8, lch)); ← specifying color spaces

contrast-color

  • Determine which of the list of colors (second input, space-separated list of values) has the highest contrast with the background (first input)
  • contrast-color(<bgcolor || currentBackground>, <color>{2,}) ← which of these (2+) colors is the most contrasted
@svgeesus

This comment has been minimized.

Copy link

commented Jun 5, 2019

Interesting proposal. A few initial comments:

I assume the lightness, chroma and hue are those from CIE LCH (probably a good idea, and if so, the proposal should say so).

That implies the initial value of the color space argument is lch.

It is less clear what happens with non-default values. For example

adjust-color(red, lightness(130%, hsl) chroma(0.4, a98rgb))

where the a98rgb color space is compatible with Adobe RGB.

The contrast-color seems fairly clear (if contrast is defined as luminance contrast, same as in WCAG).

Clamping should also be defined, both in terms of the displayed value and also the serialization. For example the results from

adjust-color(lch(80, 30, 180), lightness(200%))
adjust-color(lch(80, 30, 270), hue(200%))
adjust-color(lch(80, 120, 180), chroma(500%))
@LeaVerou

This comment has been minimized.

Copy link

commented Jun 5, 2019

Initial comments:

  • I like the simplicity, just three functions that seem to cover a large percentage of use cases. Not a huge fan of the nested parentheses, I'd prefer a comma-based syntax with no parens.
  • I like quietly using LCH as the default color space. It makes me wonder, do we even need adjustment in other color spaces? Are there use cases for adjusting e.g. RGB red or CMYK cyan? Are there use cases for HSL-based adjustment?
  • Mixing colors with mixing and matching channels from each color is a very interesting, novel idea. Not sure what the optional color space argument does, what does a chroma adjustment do in sRGB?
  • I like the ability to provide colors for the contrast function. They should probably be optional and default to black and white. Some people also want to provide the contrast ratio instead, I wonder if we should support that. Not sure how we'd handle semi-transparent colors, since the contrast in that case is a range, so how do we order overlapping ranges? I don't think defaulting the color to currentBackground is implementable easily enough to be worth it, but that's very minor.
@una

This comment has been minimized.

Copy link
Owner Author

commented Jun 5, 2019

@LeaVerou Agree with all of the above ^

  • The reasoning for a color space option (to adjust in other color spaces) is to help people transition their existing systems for accurate results. Currently, many are using either Sass/PostCSS functions (which are RGB or HSL based) or manual HSL calculations and matching their current color algorithms would make adoption easier and more accurate. An author may want to use CMYK if they are applying design for print.
  • If a color space does not match a color space (i.e. chroma transform for RGB), we could fallback to lch as it would be without that argument present
  • I like the black/white default options for contrast-color() for transparent colors, this is already an existing a11y testing issue. I'm not aware of any trackers that handle it well
@argyleink

This comment has been minimized.

Copy link

commented Jun 5, 2019

love this, well written gist btw 😍

So exciting that we wont need this shenanigan to get the contrast task done (doesnt work in safari atm) https://css-tricks.com/css-variables-calc-rgb-enforcing-high-contrast-colors/


Comments and feedback:

  • Chris's suggestion of clamping is a great addition
  • I'd love to see what Lea is envisioning with no parens and a comma based syntax, can we see an example?
  • I'm kinda into Lea's suggestion also of not even giving the option to adjust color in other spaces. Who is out there relying on the muddy or improperly lightened color adjustments found in other color spaces than lch? Also makes me curious of the LOE in allowing passing a color space, so we can weight the value vs the effort. Maybe specifying a color space comes in a 2nd iteration of the spec? lch is such a healthy denominator.
  • Another great Lea suggestion to have the contrast function default to black and white

In context
I took a stab at trying on some of the proposed syntax, cuz it's fun! how'd i do?

/* darken link on hover */

a {
  --primary: hsl(330 100% 70%);

  color: var(--primary);

  &:hover {
    color: adjust-color(var(--primary), lightness(-10%));
  }
}
/* dark mode */

:root {
  --brand-background: white;
  --brand-text-color: hsl(0 0% 10%);
}

body {
  background-color: var(--brand-background);
  color: var(--brand-text-color);
}

@media (prefers-color-scheme: dark) {
  --brand-background: adjust-color(var(--background-color), lightness(-90%));
  --brand-text-color: adjust-color(var(--brand-text-color), lightness(+90%));
}
@LeaVerou

This comment has been minimized.

Copy link

commented Jun 5, 2019

@argyleink By no parens and commas I meant instead of adjust-color(red, lightness(130%) chroma(0.4)) it would be adjust-color(red, lightness 130%, chroma 0.4).

What worries me a bit about this proposal is that even though the simplicity is appealing, it may come back to bite us if we keep finding new adjustments we need to support. E.g. I already realized we can't do alpha adjustments. What else is missing?

@una

This comment has been minimized.

Copy link
Owner Author

commented Jun 5, 2019

Notes from CSSWG Meeting:

  • @AmeliaBR specifying suggested blend modes for mix
  • Naming: color-mod(), color-mix(), color-contrast()
  • Include optional argument values for color space and for clipped output color(?)
  • Default for color-mix() to evenly interpolate between colors (i.e. color-mix(red, yellow), color-mix(red, yellow, rgb))
@mirisuzanne

This comment has been minimized.

Copy link

commented Jun 6, 2019

I like aspects of both proposals for color-adjustments, but replying here to specifically comment on mix and contrast.

mix:
I’m glad to see the default even interpolation added in that final comment, but curious if the shortcut can be extended to any single weighting value in addition to the default 50%. eg color-mix(red, yellow, 70%) (defaulting to lch)

contrast:
I like the basic thrust, but working on design systems, I often need more control than max-contrast from a list. I’d like to see these uses considered as well:

  • min contrast to meet a particular ratio (from list)
  • adjust given color to meet a particular ratio

I imagine the latter may open up too much complexity - but I’d consider the former a primary use-case.

I also like the way font-stacks and nested variable fallbacks allow you to weight your preferences - that would be another way to put the author in control of how colors are selected from the list based on ratio: make the list order meaningful.

@smfr

This comment has been minimized.

Copy link

commented Jun 6, 2019

Not a big fan of color-mod() (sounds like modulus). Maybe color-adjust()?
I think (with all the dark mode stuff happening) you need a way to be able to invert the lightness of a color, which doesn't seem possible with this proposal.

@AmeliaBR

This comment has been minimized.

Copy link

commented Jun 6, 2019

@smfr, color-adjust was mentioned and excluded because of confusing with the color-adjust and forced-color-adjust properties, which are completely unrelated (they're about disabling user agent overrides of colors for printing or accessibility reasons).

But, other names are definitely open for discussion. color-mod also breaks the usual CSS practice of not using partial words or shorthands.

@AmeliaBR

This comment has been minimized.

Copy link

commented Jun 6, 2019

@mirisuzanne re contrast functions

min contrast to meet a particular ratio (from list)

This seems like a good extension of @una's proposal. So an author could include pure black and white in their palette, without having them automatically win out over the brand color palette.

And I agree that having an order to the list of colors makes sense: pick the first color in this list that meets this contrast ratio number (where that number is calculated by the WCAG formula). Or if the author hasn't specified a number, pick the max contrast possible but pick the first in the list if there is a tie.

adjust given color to meet a particular ratio

The previous attempt at defining color modification functions tried to do this, but it turned into quite the can of worms & was one of the reasons that set of proposals was torn out. It doesn't mean it will never happen in the future, but I definitely want to at least handle the pick-from-a-palette use case first.

@alice

This comment has been minimized.

Copy link

commented Jul 2, 2019

adjust given color to meet a particular ratio

I actually wrote code to do this a few years ago (for the Accessibility Developer Tools extension, RIP) and I was pretty happy with the results. It is definitely a somewhat complex algorithm, though.

If at some hypothetical future time you did want to re-attempt this, I'd be happy to explain the algorithm and the reasoning behind it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.