A Guide To Modern CSS Colors With RGB, HSL, HWL, LAB and LCH

There’s more to color on the web than meets the eye, and it’s about to get a lot more interesting! Today, we’ll take a look at the best ways to use colors in a design system, and what we can expect from our colors in the not-too-distant future.

Well-Known Color Values

There are many different ways to define colors in CSS. CSS named colors are one of the simplest ways to color an element:

.my-element {
  background-color: red;
}

These are very limited, and rarely fit the designs we are building! We could also use color hex (hexadecimal) values. This code gives our element a red background color:

.my-element {
  background-color: #ff0000;
}

Unless you’re a color expert, hex values are very difficult to read. It’s unlikely you would be able to guess the color of an element by reading the hex value. When building a website we might be given a hex color value by a designer, but if they asked us to make it, say 20% darker, we would have a hard time doing that by adjusting the hex value, without a visual guide or color picker.

RGB

RGB (red, green, blue) notation is an alternative way of writing colors, giving us access to the same range of colors as hex values, in a much more readable form. We have an rgb() function in CSS for this. Colors on the web are additive, meaning the higher the proportion of red, green and blue, the lighter the resulting color will be. If we only use the red channel, the result is red:

.my-element {
  background-color: rgb(255, 0, 0);
}

Setting the red, green and blue channels to the highest value will result in white:

.my-element {
  background-color: rgb(255, 255, 255);
}

We can also add an alpha channel (for transparency), by using the rgba() function:

.my-element {
  background-color: rgba(255, 0, 0, 0.5); // transparency of 50%
}

.my-element {
  background-color: rgba(255, 0, 0, 1); // fully opaque
}

rgb() and rgba() allow us to “mix” colors in our code to some extent, but the results can be somewhat unpredictable.

HSL

More recently, we have been able to use HSL (hue, saturation, lightness) values, with the hsl() and hsla() color functions. As a developer, these are far more intuitive when it comes to adjusting color values. For example, we can get darker and lighter variants of the same color by adjusting the lightness parameter:

.my-element {
  background-color: hsl(0deg, 100%, 20%); // dark red
}

.my-element {
  background-color: hsl(0deg, 100%, 50%); // medium red
}

.my-element {
  background-color: hsl(0deg, 100%, 80%); // light red
}

The hue parameter represents the position on a color wheel, and can be any value between 0 and 360deg. The function also accepts turn units (e.g. 0.5turn), and unitless values.

The following are all valid:

.my-element {
  background-color: hsl(180deg, 50%, 50%);
}

.my-element {
  background-color: hsl(0.5turn, 50%, 50%);
}

.my-element {
  background-color: hsl(180, 50%, 50%);
}

Tip: Holding down SHIFT and clicking the color swatch in the inspector in Chrome and Firefox dev tools will toggle the color value between hex, RGB and HSL!

hsl() and hsla() lend themselves well to manipulation with custom properties, as we’ll see shortly.

currentColor

The currentColor keyword is worth a mention as another way of setting a color on an element that’s been around for a while. It effectively allows us to use the current text color of an element as a variable. It’s pretty limited when compared with custom properties, but it’s often used for setting the fill color of SVG icons, to ensure they match the text color of their parent. Read about it here.

Modern Color Syntax

The CSS Color Module Level 4 provides us with a more convenient syntax for our color functions, which is widely supported in browsers. We no longer need the values to be comma-separated, and the rgb() and hsl() functions can take an optional alpha parameter, separated with a forward slash:

.my-element {
  /* optional alpha value gives us 50% opacity */
  background-color: hsl(0 100% 50% / 0.5);
}

.my-element {
  /* With no alpha value the background is fully opaque*/
  background-color: hsl(0 100% 50%);
}
New CSS Color Functions

HWB

HWB stands for hue, whiteness and blackness. Like HSL, the hue can be anywhere within a range of 0 to 360. The other two arguments control how much white or black is mixed into that hue, up to 100% (which would result in a totally white or totally black color). If equal amounts of white and black are mixed in, the color becomes increasingly gray. We can think of this as being similar to mixing paint. It could be especially useful for creating monochrome color palettes

Try it out with this demo (works in Safari only):

Why do we need LAB and LCH when we have HSL? One reason is that using LAB or LCH, gives us access to a much larger range of colors. LCH and LAB are designed to give us access to the entire spectrum of human vision. Furthermore, HSL and RGB have a few shortcomings: they are not perceptually uniform and, in HSL, increasing or decreasing the lightness has quite a different effect depending on the hue.

In this demo, we can see a stark contrast between LCH and HSL by hitting the grayscale toggle. For the HSL hue and saturation strips, there are clear differences in the perceptual lightness of each square, even though the “lightness” component of the HSL function is the same! Meanwhile, the chroma and hue strips on the LCH side have an almost-uniform perceptual lightness.

We can also see a big difference when using LCH color for gradients. Both these gradients start and end with the same color (with LCH values converted to the HSL equivalents using this converter). But the LCH gradient goes through vibrant shades of blue and purple in the middle, whereas the HSL gradient looks muddier and washed-out by comparison.

LAB and LCH, while perhaps being syntactically a little less intuitive, behave in a way that makes more sense to the human eye. In her article, LCH color in CSS: what, why, and how?, Lea Verou explains in detail the advantages of LCH color. She also built this LCH color picker.

As with other color functions, hwb(), lab() and lch() can also take an optional alpha parameter.

.my-element {
  background-color: lch(80% 240 50 / 0.5); // Resulting color has 50% opacity
}
Browser Support And Color Spaces

hwb(), lab() and lch() are currently only supported in Safari. It’s possible to start using them straight away by providing a fallback for non-supporting browsers. Browsers that don’t support the color function will simple ignore the second rule:

.my-element {
  background-color: lch(55% 102 360);

  /* LCH color converted to RGB using Lea Verou’s tool: https://css.land/lch/ */
  background-color: rgb(98.38% 0% 53.33%);
}

If other styles depend on newer color functions being supported, we could use a feature query:

.my-element {
  display: none;
}

/* Only display this element if the browser supports lch() */
@supports (background-color: lch(55% 102 360)) {
  .my-element {
    display: block;
    background-color: lch(55% 102 360);
  }
}

It’s worth noting, as Lea explains in her article, that although modern screens are capable of displaying colors beyond RGB, most browsers currently only support colors within the sRGB color space. In the LAB color demo you might notice that moving the sliders beyond a certain point doesn’t actually affect the color, even in Safari where lab() and lch() are supported. Using values outside of the sRGB range will only have an effect when hardware and browsers advance sufficiently.

Safari now supports the color() function, which enables us to display colors in the P3 space, but these are limited to RGB colors for now, and don’t yet give us all the advantages of LAB and LCH.

.my-element {
  background: rgb(98.38% 0% 53.33%); // bright pink
  background: color(display-p3 0.947 0 0.5295); // equivalent in P3 color space
}

Recommended Reading: Wide Gamut Color in CSS with Display-P3” by Nikita Vasilyev

Accessibility

Once they are widely supported, perhaps LAB and LCH can help us choose more accessible color combinations. Foreground text should have the same contrast ratio with background colors with different hue or chroma values, as long as their lightness value remains the same. That’s certainly not the case at the moment with HSL colors.

Color Management

A wider range of color functions means we have more options when it comes to managing colors in our application. Often we require several variants of a given color in our design system, ranging from dark to light.

Custom Properties

CSS custom properties allow us to store values for reuse in our stylesheets. As they allow partial property values, they can be especially useful for managing and manipulating color values. HSL lends itself particularly well to custom properties, due to its intuitiveness. In the previous demo, I’m using them to adjust the hue for each segment of the color strip by calculating a --hue value based on the element’s index (defined in another custom property).

li {
  --hue: calc(var(--i) * (360 / 10));
  background: hsl(var(--hue, 0) 50% 45%);
}

We can also do things like calculate complementary colors (colors from opposite sides of the color wheel). Plenty has been written about this, so I won’t cover old ground here, but if you’re curious then Sara Soueidan’s article on color management with HSL is a great place to start.

Migrating From Hex/RGB To HSL

RGB colors might serve your needs up to a point, but if you need the flexibility to be able to derive new shades from your base color palette then you might be better off switching to HSL (or LCH, once supported). I would recommend embracing custom properties for this.

Note: There are plenty of online resources for converting hex or RGB values to HSL (here’s one example).

Perhaps you have colors stored as Sass variables:

$primary: rgb(141 66 245);

When converting to HSL, we can assign custom properties for the hue, saturation and lightness values. This makes it easy to create darker or lighter, more or less saturated variants of the original color.

:root {
  --h: 265;
  --s: 70%;
  --l: 50%;

  --primary: hsl(var(--h) var(--s) var(--l));
  --primaryDark: hsl(var(--h) var(--s) 35%);
  --primaryLight: hsl(var(--h) var(--s) 75%);
}

HSL can be incredibly useful for creating color schemes, as detailed in the article Building a Color Scheme by Adam Argyle. In the article he creates light, dark and dim color schemes, using a brand color as a base. I like this approach because it allows for some fine-grained control over the color variant (for example, decreasing the saturation for colors in the “dark” scheme), but still retains the big advantage of custom properties: updating the brand color in just one place will be carried through to all the color schemes, so it could potentially save us a lot of work in the future.

Sass Color Functions

When it comes to mixing and adjusting colors, Sass has provided color functions to enable us to do just this for many years. We can saturate or desaturate, lighten or darken, even mix two colors together. These work great in some cases, but they have some limitations: firstly, we can only use them at compile-time, not for manipulating colors live in the browser. Secondly, they are limited to RGB and HSL, so they suffer from the same issues of perceptual uniformity, as we can see in this demo, where a color is increasingly desaturated yet appears increasingly lighter when converted to grayscale.

To ensure that the lightness remains uniform, we could use custom properties with LCH in a similar way to HSL above.

li {
  --hue: calc(var(--i) * (360 / 10));
  background: lch(50% 45 var(--hue, 0));
}
Color Mixing And Manipulation

Color Mixing

One thing CSS doesn’t yet allow us to do is mix colors in the browser. That’s all about to change: the Level 5 CSS Color Specification (working draft) contains proposals for color mixing functions that sound rather promising. The first is the color-mix() function, which mixes two colors much like Sass’s mix() function. But color-mix() in CSS allows us to specify a color space, and uses the LCH by default, with superior mixing as a result. The colors don’t have to be LCH when passed in as arguments either, but the interpolation will use the specified color space. We can specify how much of each color to mix, similar to gradient stops:

.my-element {
  /* equal amounts of red and blue */
  background-color: color-mix(in lch, red, blue);
}

.my-element {
  /* 30% red, 70% blue */
  background-color: color-mix(in lch, red 30%, blue);
}

Color Contrast And Accessibility

color-contrast() is another proposed function, which really does have huge implications for picking accessible colors. In fact, it’s designed with accessibility in mind first and foremost. It permits the browser to pick the most appropriate value from a list, by comparing it with another color. We can even specify the desired contrast ratio to ensure our color schemes meet WCAG guidelines. Colors are evaluated from left to right, and the browser picks the first color from the list that meets the desired ratio. If no colors meet the ratio, the chosen color will be the one with the highest contrast.

.my-element {
  color: wheat;
  background-color: color-contrast(wheat vs bisque, darkgoldenrod, olive, sienna, darkgreen, maroon to AA);
}

Because this isn’t supported in any browsers right now, I’ve borrowed this example directly from the spec. when the browser evaluates the expression the resulting color will be darkgreen, as it is the first one that meets the AA contrast ratio when compared to wheat, the color of the text.

Browser Support

The Level 5 Color Specification is currently in Working Draft, meaning no browsers yet support the color-contrast() and color-mix() functions and their syntax is subject to change. But it certainly looks like a bright future for color on the web!

Environmental Impact Of Colors

Did you know that your chosen color palette can have an impact on how much energy your website uses? On OLED screens (which account for most modern TVs and laptops), darker colors will use significantly less energy than light colors — with white using the most energy, and black the least. According to Tom Greenwood, author of Sustainable Web Design, blue is also more energy-intensive than colors in the red and green areas of the spectrum. To reduce the environmental impact of your applications, consider a darker color scheme, using less blue, or enabling a dark-mode option for your users. As an added bonus, a more environmentally friendly choice of colors can also reduce the impact on the battery life of mobile devices.

Tools
  • Hexplorer, Rob DiMarzo
    Learn to understand hex colors with this interactive visualization.
  • LCH color picker, Lea Verou and Chris Lilley
    Get LCH colors and their RGB counterparts.
  • HWB color picker
    Visualize HWB colors and convert to HSL, RGB and hex.
  • Ally Color Tokens, Stephanie Eckles
    An accessible color token generator.
Resources

A Complete Guide to Custom Properties

A custom property is most commonly thought of as a variable in CSS.

.card {
  --spacing: 1.2rem;
  padding: var(--spacing);
  margin-bottom: var(--spacing);
}

Above, --spacing is the custom property with 1.2rem as the value and var(--spacing) is the variable in use.

Perhaps the most valuable reason to use them: not repeating yourself (DRY code). In the example above, I can change the value 1.2rem in one place and have it affect two things. This brings something programming languages do to CSS.

There is a good bit to know about custom properties, so let’s get into it.

Why care about CSS Custom Properties?

  1. They help DRY up your CSS. That is “Don’t Repeat Yourself.” Custom properties can make code easier to maintain because you can update one value and have it reflected in multiple places. Careful though, overdoing abstraction can make have the opposite effect and make code less understandable.
  2. They are particularly helpful for things like creating color themes on a website.
  3. They unlock interesting possibilities in CSS.
  4. The fact that they can be updated in JavaScript opens up even more interesting doors.

Naming custom properties

Custom properties must be within a selector and start with two dashes (--):

/* Nope, not within a selector */
--foo: 1;

body {
  /* No, 0 or 1 dash won't work */
  foo: 1;
  -foo: 1; 

  /* Yep! */
  --foo: 1;

  /* OK, but they're different properties */
  --FOO: 1;
  --Foo: 1;
  
  /* Totally fine */
  --mainColor: red;
  --main-color: red;

  /* Special characters are a no */
  --color@home: red;
  --black&blue: black;
  --black^2: black;
}

Best to stick with letters, numbers, and dashes while making sure the custom property is defined inside of a valid selector.

Properties as properties

You can set the value of a custom property with another custom property:

html {
  --red: #a24e34;
  --green: #01f3e6;
  --yellow: #f0e765;

  --error: var(--red);
  --errorBorder: 1px dashed var(--red);
  --ok: var(--green);
  --warning: var(--yellow);
}

Some people like doing it this way because it allows the name of a custom property to be descriptive and then used in another property with a more functional name, again helping keep things DRY. It can even help make the functional names more readable and understandable.

Valid values for custom properties

Custom properties are surprisingly tolerant when it comes to the values they accept.

Here are some basic examples that you’d expect to work, and do.

body {
  --brand-color: #990000;
  --transparent-black: rgba(0, 0, 0, 0.5);
  
  --spacing: 0.66rem;
  --max-reading-length: 70ch;
  --brandAngle: 22deg;

  --visibility: hidden;
  --my-name: "Chris Coyier";
}

See that? They can be hex values, color functions, units of all kinds, and even strings of text.

But custom properties don’t have to be complete values like that. Let’s look at how useful it can be to break up valid CSS values into parts we can shove into custom properties.

Breaking up values

You can use custom properties to break up multi-part values.

Let’s imagine you’re using a color function, say rgba(). Each color channel value in there can be its own custom property. That opens up a ton of possibilities, like changing the alpha value for a specific use case, or perhaps creating color themes.

Splitting colors

Take HSL color, for example. We can split it up into parts, then very easily adjust the parts where we want. Maybe we’re working with the background color of a button. We can update specific parts of its HSL makeup when the button is hovered, in focus, or disabled, without declaring background on any of those states at all.

button {
  --h: 100;
  --s: 50%;
  --l: 50%;
  --a: 1;

  background: hsl(var(--h) var(--s) var(--l) / var(--a));
}
button:hover { /* Change the lightness on hover */
  --l: 75%;
}
button:focus { /* Change the saturation on focus */
  --s: 75%;
}
button[disabled] {  /* Make look disabled */
  --s: 0%;
  --a: 0.5;
}

By breaking apart values like that, we can control parts of them in a way we never could before. Just look at how we didn’t need to declare all of the HSL arguments to style the hover, focus and disabled state of a button. We simply overrode specific HSL values when we needed to. Pretty cool stuff!

Shadows

box-shadow doesn’t have a shorthand property for controlling the shadow’s spread on its own. But we could break out the box-shadow spread value and control it as a custom property (demo).

button {
  --spread: 5px;
  box-shadow: 0 0 20px var(--spread) black;
}
button:hover {
  --spread: 10px;
}

Gradients

There is no such thing as a background-gradient-angle (or the like) shorthand for gradients. With custom properties, we can change just change that part as if there was such a thing.

body {
  --angle: 180deg;
  background: linear-gradient(var(--angle), red, blue);
}
body.sideways {
  --angle: 90deg;
}

Comma-separated values (like backgrounds)

Any property that supports multiple comma-separated values might be a good candidate for splitting values too, since there is no such thing as targeting just one value of a comma-separated list and changing it alone.

/* Lots of backgrounds! */
background-image:
  url(./img/angles-top-left.svg),
  url(./img/angles-top-right.svg),
  url(./img/angles-bottom-right.svg),
  url(./img/angles-bottom-left.svg),
  url(./img/bonus-background.svg);

Say you wanted to remove just one of many multiple backgrounds at a media query. You could do that with custom properties like this, making it a trivial task to swap or override backgrounds.

body {
  --bg1: url(./img/angles-top-left.svg);
  --bg2: url(./img/angles-top-right.svg);
  --bg3: url(./img/angles-bottom-right.svg);
  --bg4: url(./img/angles-bottom-left.svg);
  --bg5: url(./img/bonus-background.svg);
  
  background-image: var(--bg1), var(--bg2), var(--bg3), var(--bg4);
}
@media (min-width: 1500px) {
  body {
    background-image: var(--bg1), var(--bg2), var(--bg3), var(--bg4), var(--bg5);
  }
}

Grids

We’re on a roll here, so we might as well do a few more examples. Like, hey, we can take the grid-template-columns property and abstract its values into custom properties to make a super flexible grid system:

.grid {
  display: grid;
  --edge: 10px;
  grid-template-columns: var(--edge) 1fr var(--edge);
}
@media (min-width: 1000px) {
  .grid {
     --edge: 15%;
   }
}

Transforms

CSS will soon get individual transforms but we can get it sooner with custom properties. The idea is to apply all the transforms an element might get up front, then control them individually as needed:

button {
  transform: var(--scale, scale(1)) var(--translate, translate(0));
}
button:active {
  --translate: translate(0, 2px);
}
button:hover {
  --scale: scale(0.9);
}

Concatenation of unit types

There are times when combining parts of values doesn’t work quite how you might hope. For example, you can’t make 24px by smashing 24 and px together. It can be done though, by multiplying the raw number by a number value with a unit.

body {
  --value: 24;
  --unit: px;
  
  /* Nope */
  font-size: var(--value) + var(--unit);
  
  /* Yep */
  font-size: calc(var(--value) * 1px);

  /* Yep */
  --pixel_converter: 1px;
  font-size: calc(var(--value) * var(--pixel_converter));
}

Using the cascade

The fact that custom properties use the cascade is one of the most useful things about them.

You’ve already seen it in action in many of the examples we’ve covered, but let’s put a point on it. Say we have a custom property set pretty “high up” (on the body), and then set again on a specific class. We use it on a specific component.

body {
  --background: white;
}
.sidebar {
  --background: gray;
}
.module {
  background: var(--background);
}

Then say we’ve got practical HTML like this:

<body> <!-- --background: white -->

  <main>
    <div class="module">
      I will have a white background.
    </div>
  <main>

  <aside class="sidebar"> <!-- --background: gray -->
    <div class="module">
      I will have a gray background.
    </div>
  </aside>

</body>
Three CSS rulesets, one for a body, sidebar and module. the background custom property is defined as white on body and gray on sidebar. The module calls the custom property and shows an orange arrow pointing to the custom property defined in the sidebar since it is the nearest ancestor.
For the second module, .sidebar is a closer ancestor than body, thus --background resolves to gray there, but white in other places.

The “module” in the sidebar has a gray background because custom properties (like many other CSS properties) inherit through the HTML structure. Each module takes the --background value from the nearest “ancestor” where it’s been defined in CSS.

So, we have one CSS declaration but it’s doing different things in different contexts, thanks to the cascade. That’s just cool.

This plays out in other ways:

button {
  --foo: Default;
}
button:hover {
  --foo: I win, when hovered;
  /* This is a more specific selector, so re-setting 
     custom properties here will override those in `button` */
}

Media queries don’t change specificity, but they often come later (or lower) in the CSS file than where the original selector sets a value, which also means a custom property will be overridden inside the media query:

body {
  --size: 16px;
  font-size: var(--size);
}
@media (max-width: 600px) {
  body {
    --size: 14px;
  } 
}

Media queries aren’t only for screen sizes. They can be used for things like accessibility preferences. For example, dark mode:

body {
  --bg-color: white; 
  --text-color: black;

  background-color: var(--bg-color);
  color: var(--text-color);
}

/* If the user's preferred color scheme is dark */
@media screen and (prefers-color-scheme: dark) {
  body {
    --bg-color: black;
    --text-color: white;
  }
}

The :root thing

You’ll often see custom properties being set “at the root.” Here’s what that means:

:root {
  --color: red;
}

/* ...is largely the same as writing: */
html {
  --color: red;
}

/* ...except :root has higher specificity, so remember that! */

There is no particularly compelling reason to define custom properties like that. It’s just a way of setting custom properties as high up as they can go. If you like that, that’s totally fine. I find it somehow more normal-feeling to apply them to the html or body selectors when setting properties I intend to make available globally, or everywhere.

There is also no reason you need to set variables at this broad of a scope. It can be just as useful, and perhaps more readable and understandable, to set them right at the level you are going to use them (or fairly close in the DOM tree).

.module {
  --module-spacing: 1rem;
  --module-border-width: 2px;

  border: var(--module-border-width) solid black;
}

.module + .module {
  margin-top: var(--module-spacing);
}

Note that setting a custom property on the module itself means that property will no longer inherit from an ancestor (unless we set the value to inherit). Like other inherited properties, there are sometimes reasons to specify them in place (at the global level), and other times we want to inherit them from context (at the component level). Both are useful. What’s cool about custom properties is that we can define them in one place, inherit them behind the scenes and apply them somewhere completely different. We take control of the cascade!

Combining with !important

You can make an !important modifier within or outside of a variable.

.override-red {
  /* this works */
  --color: red !important;  
  color: var(--color);

  /* this works, too */
  --border: red;
  border: 1px solid var(--border) !important;
}

Applying !important to the --color variable, makes it difficult to override the value of the --color variable, but we can still ignore it by changing the color property. In the second example, our --border variable remains low-specificity (easy to override), but it’s hard to change how that value will be applied to the border itself.

Custom property fallbacks

The var() function is what allows for fallback values in custom properties.

Here we’re setting a scale() transform function to a custom property, but there is a comma-separated second value of 1.2. That 1.2 value will be used if --scale is not set.

.bigger {
  transform: scale(var(--scale, 1.2));
}

After the first comma, any additional commas are part of the fallback value. That allows us to create fallbacks with comma-separated values inside them. For example, we can have one variable fall back to an entire stack of fonts:

html {
  font-family: var(--fonts, Helvetica, Arial, sans-serif);
}

We can also provide a series of variable fallbacks (as many as we want), but we have to nest them for that to work:

.bigger {
  transform: scale(var(--scale, var(--second-fallback, 1.2));
}

If --scale is undefined, we try the --second-fallback. If that is also undefined, we finally fall back to 1.2.

Using calc() and custom properties

Even more power of custom properties is unlocked when we combine them with math!

This kind of thing is common:

main {
  --spacing: 2rem;
}

.module {
  padding: var(--spacing);
}

.module.tight {
  /* divide the amount of spacing in half */
  padding: calc(var(--spacing) / 2)); 
}

We could also use that to calculate the hue of a complementary color:

html {
  --brand-hue: 320deg;
  --brand-color: hsl(var(--brand-hue), 50%, 50%);
  --complement: hsl(calc(var(--brand-hue) + 180deg), 50%, 50%);
}

calc() can even be used with multiple custom properties:

.slider {
  width: calc(var(--number-of-boxes) * var(--width-of-box));
}

Deferring the calc()

It might look weird to see calculous-like math without a calc():

body {
  /* Valid, but the math isn't actually performed just yet ... */
  --font-size: var(--base-font-size) * var(--modifier);

  /* ... so this isn't going to work */
  font-size: var(--font-size);
}

The trick is that as long as you eventually put it in a calc() function, it works fine:

body {
  --base-font-size: 16px;
  --modifier: 2;
  --font-size: var(--base-font-size) * var(--modifier);

  /* The calc() is "deferred" down to here, which works */
  font-size: calc(var(--font-size));
}

This might be useful if you’re doing quite a bit of math on your variables, and the calc() wrapper becomes distracting or noisy in the code.

@property

The @property “at-rule” in CSS allows you to declare the type of a custom property, as well its as initial value and whether it inherits or not.

It’s sort of like you’re creating an actual CSS property and have the ability to define what it’s called, it’s syntax, how it interacts with the cascade, and its initial value.

@property --x {
  syntax: '<number>';
  inherits: false;
  initial-value: 42;
}
Valid Types
  • length
  • number
  • percentage
  • length-percentage
  • color
  • image
  • url
  • integer
  • angle
  • time
  • resolution
  • transform-list
  • transform-function
  • custom-ident (a custom identifier string)

This means that the browser knows what kind of value it is dealing with, rather than assuming everything is a string. That means you can animate things in ways you couldn’t otherwise.

For example, say you have a star-shaped icon that you want to spin around with @keyframes and rotate with a transform. So you do this:

.star {
  --r: 0deg;
  transform: rotate(var(--r));
  animation: spin 1s linear infinite;
}

@keyframes spin {
  100% {
    --r: 360deg;
  }
}

That actually won’t work, as the browser doesn’t know that 0deg and 360deg are valid angle values. You have to define them as an <angle> type with @property for that to work.

@property --angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

.star {
  --r: 0deg;
  transform: rotate(var(--r));
  animation: spin 1s linear infinite;
}

@keyframes spin {
  100% {
    --r: 360deg;
  }
}
Demo

Commas in values

This can be a smidge confusing. Maybe not so much this:

html {
  --list: 1, 2, 3;
}

But below, you’ll need a sharp eye to realize the fallback value is actually 1.2, 2. The first comma separates the fallback, but all the rest is part of the value.

html {
  transform: scale(var(--scale, 1.2, 2));
}

Learn more about fallbacks above ⮑

Advanced usage

The Raven is a technique that emulates container queries using math and custom properties. Be prepared, this goes from 0-100 in complexity right out of the gate!

Demo

Resize this demo to see a grid of inline-block elements change number of columns from 4 to 3 to 1.

Here’s a few more favorite examples that show off advanced usage of custom properties:

The initial and whitespace trick

Think of @media queries and how when one thing changes (e.g. the width of the page) you can control multiple things. That’s kind of the idea with this trick. You change one custom property and control multiple things.

The trick is that the value of initial for a custom property will trigger a fallback, while an empty whitespace value will not. For the sake of explanation, it let’s define two globally-scoped custom properties, ON and OFF:

:root {
  --ON: initial;
  --OFF: ;
}

Say we have a “dark” variation class which sets a number of different properties. The default is --OFF, but can be flipped to --ON whenever:

.module {
  --dark: var(--OFF);
}

.dark { /* could be a media query or whatever */
  --dark: var(--ON);
}

Now you can use --dark to conditinally set values that apply only when you’ve flipped --dark to --ON. Demo:

Lea Verou has a great writeup that covers all of this.

Inline styles

It’s totally legit to set a custom property in HTML with an inline style.

<div style="--color: red;"></div>

That will, like any inline style, have a very high level of specificity.

This can be super useful for when the HTML might have access to some useful styling information that would be too weird/difficult to put into a static CSS file. A good example of that is maintaining the aspect ratio of an element:

<div style="--aspect-ratio: 16 / 9;"></div>

Now I can set up some CSS to make a box of that exact size wherever I need to. The full writeup on that is here, but here’s CSS that uses trickery like the ol’ padded box applied to a pseudo element which pushes the box to the desired size:

[style*="--aspect-ratio"] > :first-child {
  width: 100%;
}
[style*="--aspect-ratio"] > img {  
  height: auto;
} 
@supports (--custom: property) {
  [style*="--aspect-ratio"] {
    position: relative;
  }
  [style*="--aspect-ratio"]::before {
    content: "";
    display: block;
    padding-bottom: calc(100% / (var(--aspect-ratio)));
  }  
  [style*="--aspect-ratio"] > :first-child {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
  }  
}

But hey, these days, we have a native aspect-ratio property in CSS, so setting that in the inline style might make more sense going forward.

<div style="aspect-ratio: 16 / 9;"></div>

Hovers and pseudos

There is no way to apply a :hover style (or other pseudo classes/elements) with inline styles. That is, unless we get tricky with custom properties. Say we want custom hover colors on some boxes — we can pass that information in as a custom property:

<div style="--hover-color: red;"><div>
<div style="--hover-color: blue;"><div>
<div style="--hover-color: yellow;"><div>

Then use it in CSS which, of course, can style a link’s hover state:

div:hover {
  background-color: var(--hover-color);
}

/* And use in other pseudos! */
div:hover::after {
  content: "I am " attr(style);
  border-color: var(--hover-color);
}

Custom properties and JavaScript

JavaScript can set the value of a custom property.

element.style.setProperty('--x', value);

Here’s an example of a red square that is positioned with custom properties, and JavaScript updates those custom property values with the mouse position:

Typically you think of JavaScript passing values to CSS to use, which is probably 99% of usage here, but note that you can pass things from CSS to JavaScript as well. As we’ve seen, the value of a custom property can be fairly permissive. That means you could pass it a logical statement. For example:

html {
  --logic: if (x > 5) document.body.style.background = "blue";
}

Then grab that value and execute it in JavaScript:

const x = 10;

const logic = getComputedStyle(document.documentElement).getPropertyValue(
  "--logic"
);

eval(logic);

Custom properties are different than preprocessor variables

Say you’re already using Sass, Less, or Stylus. All those CSS preprocessors offer variables and it’s one of the main reasons to have them as part of your build process.

// Variable usage in Sass (SCSS)
$brandColor: red;

.marketing {
  color: $brandColor;
}

So, do you even need to bother with native CSS custom properties then? Yes, you should. Here’s why in a nutshell:

  • Native CSS custom properties are more powerful then preprocessor variables. Their integration with the cascade in the DOM is something that preprocessor variables will never be able to do.
  • Native CSS custom properties are dynamic. When they change (perhaps via JavaScript, or with a media query), the browser repaints what it needs to. Preprocessor variables resolve to a value when they’re compiled and stay at that value.
  • Going with a native feature is good for the longevity of your code. You don’t need to preprocess native CSS.

I cover this in much more detail in the article “What is the difference between CSS variables and preprocessor variables?”

To be totally fair, there are little things that preprocessor variables can do that are hard or impossible with custom properties. Say you wanted to strip the units off a value for example. You can do that in Sass but you’ll have a much harder time with custom properties in CSS alone.

Can you preprocess custom properties?

Kinda. You can do this, with Sass just to pick one popular preprocessor:

$brandColor: red;
body {
  --brandColor: $brandColor;
}

All that’s doing is moving a Sass variable to a custom property. That could be useful sometimes, but not terribly. Sass will just make --brandColor: red; there, not process the custom property away.

If a browser doesn’t support custom properties, that’s that. You can’t force a browser to do what custom properties do by CSS syntax transformations alone. There might be some kind of JavaScript polyfill that parses your CSS and replicates it, but I really don’t suggest that.

The PostCSS Custom Properties plugin, though, does do CSS syntax transforms to help. What it does is figure out the value to the best of it’s ability, and outputs that along with the custom property. So like:

:root {
  --brandColor: red;
}
body {
  color: var(--brandColor);
}

Will output like this:

:root {
  --brandColor: red;
}
body {
  color: red;
  color: var(--brandColor);
}

That means you get a value that hopefully doesn’t seem broken in browsers that lack custom property support, but does not support any of the fancy things you can do with custom properties and will not even attempt to try. I’m a bit dubious about how useful that is, but I think this is about the best you can do and I like the spirit of attempting to not break things in older browsers or newer browsers.

Availiability

Another thing that is worth noting about the difference between is that with a CSS preprocessor, the variables are available only as you’re processing. Something like $brandColor is meaningless in your HTML or JavaScript. But when you have custom properties in use, you can set inline styles that use those custom properties and they will work. Or you can use JavaScript to figure out their current values (in context), if needed.

Aside from some somewhat esoteric features of preprocessor variables (e.g. some math possibilities), custom properties are more capable and useful.

Custom properties and Web Components (Shadow DOM)

One of the most common and practical ways to style of Web Components (e.g. a <custom-component> with shadow DOM) is by using custom properties as styling hooks.

The main point of the shadow DOM is that it doesn’t “leak” styles in or out of it, offering style isolation in a way that nothing else offers, short of an <iframe>. Styles do still cascade their way inside, I just can’t select my way inside. This means custom properties will slide right in there.

Here’s an example:

Another common occurrence of the shadow DOM is with SVG and the <use> element.

Video: “CSS Custom Properties Penetrate the Shadow DOM”

Browser support

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
4931No1610

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
90879010.0-10.2

You can preprocess for deeper browser support, with heavy limitations.

@supports

If you would like to write conditional CSS for when a browser supports custom properties or not:

@supports (--custom: property) {
  /* Isolated CSS for browsers that DOES support custom properties, assuming it DOES support @supports */
}

@supports not (--custom: property) {
  /* Isolated CSS for browsers that DON'T support custom properties, assuming it DOES support @supports */
}

Credit

Thanks to Miriam Suzanne for co-authoring this with me!


The post A Complete Guide to Custom Properties appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Custom Properties as State

Here’s a fun idea from James Stanley: a CSS file (that presumably updates daily) containing CSS custom properties for “seasonal” colors (e.g. spring is greens, fall is oranges). You’d then use the values to theme your site, knowing that those colors change slightly from day to day.

This is what I got while writing this:

:root {
  --seasonal-bg: hsl(-68.70967741935485,9.419354838709678%,96%);
  --seasonal-bgdark: hsl(-68.70967741935485,9.419354838709678%,90%);
  --seasonal-fg: hsl(-68.70967741935485,9.419354838709678%,30%);
  --seasonal-hl: hsl(-83.70967741935485,30.000000000000004%,50%);
  --seasonal-hldark: hsl(-83.70967741935485,30.000000000000004%,35%);
}

I think it would be more fun if the CSS file provided was just the custom properties and not the opinionated other styles (like what sets the body background and such). That way you could implement the colors any way you choose without any side effects.

CSS as an API?

This makes me think that a CDN-hosted CSS file like this could have other useful stuff, like today’s date for usage in pseudo content, or other special time-sensitive stuff. Maybe the phase of the moon? Sports scores?! Soup of the day?!

/* <div class="soup">The soup of the day is: </div> */
.soup::after {
  content: var(--soupOfTheDay); /* lol kinda */
}

It’s almost like a data API that is tremendously easy to use. Pseudo content is even accessible content these days — but you can’t select the text of pseudo-elements, so don’t read this as an actual endorsement of using CSS as a content API.

Custom Property Flexibility

Will Boyd just blogged about what is possible to put in a custom property. They are tremendously flexible. Just about anything is a valid custom property value and then the usage tends to behave just how you think it will.

body {
  /* totally fine */
  --rgba: rgba(255, 0, 0, 0.1);
  background: var(--rgba);

  /* totally fine */
  --rgba: 255, 0, 0, 0.1;
  background: rgba(var(--rgba));

  /* totally fine */
  --rgb: 255 0 0;
  --a: 0.1;
  background: rgb(var(--rgb) / var(--a));
}

body::after {
  /* totally fine */
  --song: "I need quotes to be pseudo content \A and can't have line breaks without this weird hack \A but still fairly permissive (💧💧💧) ";
  content: var(--song);
  white-space: pre;
}

Bram Van Damme latched onto that flexiblity while covering Will’s article:

That’s why you can use CSS Custom Properties to:

perform conditional calculations

pass data from within your CSS to your JavaScript

inject skin tone / hair color modifiers onto Emoji 

toggle multiple values with one custom property (--foo: ; hack)

Bram points out this “basic” state-flipping quality that a custom property can pull off:

:root {
  --is-big: 0;
}

.is-big {
  --is-big: 1;
}

.block {
  padding: calc(
    25px * var(--is-big) +
    10px * (1 - var(--is-big))
  );
  border-width: calc(
    3px * var(--is-big) +
    1px * (1 - var(--is-big))
  );
}

Add a couple of scoops of complexity and you get The Raven (media queries with custom properties).

I’d absolutely love to see something happen in CSS to make this easier. Using CSS custom properties for generic state would be amazing. We could apply arbitrary styles when the UI is in arbitrary states! Think of how useful media queries are now, or that container queries will be, but compounded because it’s arbitrary state, not just state that those things expose.

Bram covered that as well, mentioning what Lea Verou called “higher level custom properties”:

/* Theoretical! */

.square {
  width: 2vw;
  padding: 0.25vw;
  aspect-ratio: 1/1;

  @if (var(--size) = big) {
    width: 16vw;
    padding: 1vw;
  }
}

.my-input {
  @if(var(--pill) = on) {
    border-radius: 999px;
   }
}

About that naming

Will calls them “CSS variables” which is super common and understandable. You’ll read (and I have written) sentences often that are like “CSS variables (a.k.a CSS Custom Properties)” or “CSS Custom Properties (a.k.a CSS Variables.” Šime Vidas recently noted there is a rather correct way to refer to these things: --this-part is the custom property and var(--this-part) is the variable, which comes right from usage in the spec.

JavaScript Library State… Automatically?

I’m reminded of this Vue proposal. I’m not sure if it went anywhere, but the idea is that the state of a component would automatically be exposed as CSS custom properties.

<template>
  <div class="text">Hello</div>
</template>

<script>
export default {
  data() {
    return {
      color: 'red'
    }
  }
}
</script>

<style vars="{ color }">
.text {
  color: var(--color);
}
</style>

By virtue of having color as part of the state of this component, then --color is available as state to the CSS of this component. I think that’s a great idea.

What if every time you used useState in React, CSS custom properties were put on the :root and were updated automatically. For example, if you did this:

import React, { useState } from 'https://cdn.skypack.dev/react@^16.13.1';
import ReactDOM from 'https://cdn.skypack.dev/react-dom@^16.13.1';

const App = () => {
  const [ activeColor, setActiveColor ] = useState("red");
  return(
    <div className="box">
      <h1>Active Color: {activeColor}</h1>
      <button onClick={() => {setActiveColor("red")}}>red</button>
      <button onClick={() => {setActiveColor("blue")}}>blue</button>
    </div>
  );
}

ReactDOM.render(<App />,
document.getElementById("root"))

And you knew you could do like:

.box {
  border-color: 2px solid var(--activeColor);
}

Because the state automatically mapped itself to a custom property. Someone should make a useStateWithCustomProperties hook or something to do that. #freeidea

Libraries like React and Vue are for building UI. I think it makes a lot of sense that the state that they manage is automatically exposed to CSS.

Could browsers give us more page state as environment variables?

Speaking of state that CSS should know about, I’ve seen quite a few demos that do fun stuff by mapping over things, like the current mouse position or scroll position, over to CSS. I don’t think it’s entirely unreasonable to ask for that data to be natively exposed to CSS. We already have the concept of environment variables, like env(safe-area-inset-top), and I could see that being used to expose page state, like env(page-scroll-percentage) or env(mouseY).


The post Custom Properties as State appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Contextual Utility Classes for Color with Custom Properties

In CSS, we have the ability to access currentColor which is tremendously useful. Sadly, we do not have access to anything like currentBackgroundColor, and the color-mod() function is still a ways away.

With that said, I am sure I am not alone when I say I'd like to style some links based on the context, and invert colors when the link is hovered or in focus. With CSS custom properties and a few, simple utility classes, we can achieve a pretty powerful result, thanks to the cascading nature of our styles:

See the Pen
Contextually colouring links with utility classes and custom properties
by Christopher Kirk-Nielsen (@chriskirknielsen)
on CodePen.

To achieve this, we'll need to specify our text and background colors with utility classes (containing our custom properties). We'll then use these to define the color of our underline, which will expand to become a full background when hovered.

Let's start with our markup:

<section class="u-bg--green">
  <p class="u-color--dark">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, <a href="#">sed do eiusmod tempor incididunt</a> ut labore et dolore magna aliqua. Aliquam sem fringilla ut morbi tincidunt. Maecenas accumsan lacus vel facilisis. Posuere sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper.
  </p>
</section>

This gives us a block containing a paragraph, which has a link. Let's set up our utility classes. I'll be defining four colors that I found on Color Hunt. We’ll create a class for the color property, and a class for the background-color property, which will each have a variable to assign the color value (--c and --bg, respectively). So, if we were to define our green color, we’d have the following:

.u-color--green {
  --c: #08ffc8;
  color: #08ffc8;
}

.u-bg--green {
  --bg: #08ffc8;
  background-color: #08ffc8;
}

If you are a Sass user, you can automate this process with a map and loop over the values to create the color and background classes automatically. Note that this is not required, it’s merely a way to create many color-related utility classes automatically. This can be very useful, but keep track of your usage so that you don’t, for example, create seven background classes that are never used on your site. With that said, here is the Sass code to generate our classes:

$colors: ( // Define a named list of our colors
  'green': #08ffc8,
  'light': #fff7f7,
  'grey': #dadada,
  'dark': #204969
);

@each $n, $c in $colors { // $n is the key, $c is the value
  .u-color--#{$n} {
    --c: #{$c};
    color: #{$c};
  }

  .u-bg--#{$n} {
    --bg: #{$c};
    background-color: #{$c};
  }
}

What happens if we forget to apply a utility class in your markup, though? The --c variable would naturally use currentColor… and so would --bg! Let’s define a top-level default to avoid this:

html {
  --c: #000000;
  --bg: #ffffff;
}

Cool! Now all we need is to style our link. We will be styling all links with our trusty <a> element in this article, but you could just as easily add a class like .fancy-link.

Additionally, as you may know, links should be styled in the "LoVe-HAte" order: :link, :visited, :hover (and :focus!), and :active. We could use :any-link, but browser support isn't as great as CSS custom properties. (Had it been the other way around, it wouldn't have been much of an issue.)

We can start declaring the styles for our links by providing an acceptable experience for older browsers, then checking for custom property support:

/* Styles for older browsers */
a {
  color: inherit;
  text-decoration: underline;
}

a:hover,
a:focus,
a:active {
  text-decoration: none;
  outline: .0625em solid currentColor;
  outline-offset: .0625em;
}

a:active {
  outline-width: .125em;
}

@supports (--a: b) { /* Check for CSS variable support */
  /* Default variable values */
  html {
    --c: #000000;
    --bg: #ffffff;
  }
  
  a {
    /*
      * Basic link styles go here...
      */
  }
}

Let's then create the basic link styles. We'll be making use of custom properties to make our styles as DRY as possible.

First, we need to set up our variables. We want to define a --space variable that will be used on various properties to add a bit of room around the text. The link's color will also be defined in a variable with --link-color, with a default of currentColor. The fake underline will be generated using a background image, whose size will be adjusted depending on the state with --bg-size, set to use the --space value by default. Finally, to add a bit of fun to this, we'll also fake a border around the link when it's :active using box-shadow, so we'll define its size in --shadow-size, set to 0 in it's inactive state. This gives us:

--space: .125em;
--link-color: currentColor;
--bg-size: var(--space);
--shadow-size: 0;

We’ll first need to adjust for the fallback styles. We'll set our color to make use of our custom property, and remove the default underline:

color: var(--link-color);
text-decoration: none;

Let's next create our fake underline. The image will be a linear-gradient with two identical start and end points: the text's color --c. We make sure it only repeats horizontally with background-repeat: repeat-x;, and place it at the bottom of our element with background-position: 0 100%;. Finally, we give it its size, which is 100% horizontally, and the value of --bg-size vertically. We end up with this:

background-image: linear-gradient(var(--c, currentColor), var(--c, currentColor));
background-repeat: repeat-x;
background-position: 0 100%;
background-size: 100% var(--bg-size);

For the sake of our :active state, let's also define the box shadow, which will be non-existent, but with our variable, it'll be able to come to life: box-shadow: 0 0 0 var(--shadow-size, 0) var(--c);

That's the bulk of the basic styles. Now, what we need to do is assign new values to our variables depending on the link state.

The :link and :visited are what our users will see when the link is "idle." Since we already set up everything, this is a short ruleset. While we technically could skip this step and declare the --c variable in the initial assignment of --link-color, I'm assigning this here to make every step of our styles crystal clear:

a:link,
a:visited {
  --link-color: var(--c);
}

The link now looks pretty cool, but if we interact with it, nothing happens… Let's create those styles next. Two things need to happen: the background must take up all available height (aka 100%), and the text color must change to be that of the background, since the background is the text color (confusing, right?). The first one is simple enough: --bg-size: 100%;. For the text color, we assign the --bg variable, like so: --link-color: var(--bg);. Along with our pseudo-class selectors, we end up with:

a:hover,
a:focus,
a:active {
    --bg-size: 100%;
    --link-color: var(--bg);
}

Look at that underline become a full-on background when hovered or focused! As a bonus, we can add a faked border when the link is clicked by increasing the --shadow-size, for which our --space variable will come in handy once more:

a:active {
  --shadow-size: var(--space);
}

We're now pretty much done! However, it looks a bit too generic, so let's add a transition, some padding and rounded corners, and let's also make sure it looks nice if the link spans multiple lines!

For the transitions, we only need to animate color, background-size and box-shadow. The duration is up to you, but given links are generally around 20 pixels in height, we can put a small duration. Finally, to make this look smoother, let's use ease-in-out easing. This sums up to:

transition-property: color, background-size, box-shadow;
transition-duration: 150ms;
transition-timing-function: ease-in-out;
will-change: color, background-size, box-shadow; /* lets the browser know which properties are about to be manipulated. */

We'll next assign our --space variable to padding and border-radius, but don't worry about the former — since we haven't defined it as an inline-block, the padding won't mess up the vertical rhythm of our block of text. This means you can adjust the height of your background without worrying about line-spacing! (just make sure to test your values)

padding: var(--space);
border-radius: var(--space);

Finally, to ensure the styles applies properly on multiple lines, we just need to add box-decoration-break: clone; (and prefixes, if you so desire), and that's it.

When you're done, we should have these styles:

/* Styles for older browsers */
a {
  color: inherit;
  text-decoration: underline;
}

a:hover,
a:focus,
a:active {
  text-decoration: none;
  outline: .0625em solid currentColor;
  outline-offset: .0625em;
}

a:active {
  outline-width: .125em;
}

/* Basic link styles for modern browsers */
@supports (--a: b) {
  /* Default variable values */
  html {
    --c: #000000;
    --bg: #ffffff;
  }
  
  a {
    /* Variables */
    --space: .125em;
    --link-color: currentColor;
    --bg-size: var(--space);
    --shadow-size: 0;

    /* Layout */
    padding: var(--space); /* Inline elements won't affect vertical rhythm, so we don't need to specify each direction */

    /* Text styles */
    color: var(--link-color);/* Use the variable for our color */
    text-decoration: none; /* Remove the default underline */

    /* Box styles */
    border-radius: var(--space); /* Make it a tiny bit fancier &#x2728; */
    background-image: linear-gradient(var(--c, currentColor), var(--c, currentColor));
    background-repeat: repeat-x;
    background-position: 0 100%;
    background-size: 100% var(--bg-size);
    box-shadow: 0 0 0 var(--shadow-size, 0) var(--c, currentColor); /* Used in the :active state */
    box-decoration-break: clone; /* Ensure the styles repeat on links spanning multiple lines */

    /* Transition declarations */
    transition-property: color, background-size, box-shadow;
    transition-duration: 150ms;
    transition-timing-function: ease-in-out;
    will-change: color, background-size, box-shadow;
  }

  /* Idle states */
  a:link,
  a:visited {
    --link-color: var(--c, currentColor); /* Use --c, or fallback to currentColor */
  }

  /* Interacted-with states */
  a:hover,
  a:focus,
  a:active {
    --bg-size: 100%;
    --link-color: var(--bg);
  }

  /* Active state */
  a:active {
    --shadow-size: var(--space); /* Define the box-shadow size */
  }
}

Sure, it's a bit more convoluted that just having an underline, but used hand-in-hand with utility classes that allow you to always access the text and background colors, it's a quite nice progressive enhancement.

It’s up to you to enhance this using three variables for each color, either rgb or hsl format to adjust opacity and such. You can also add a text-shadow to simulate text-decoration-skip-ink!

The post Contextual Utility Classes for Color with Custom Properties appeared first on CSS-Tricks.

Managing Multiple Backgrounds with Custom Properties

One cool thing about CSS custom properties is that they can be a part of a value. Let's say you're using multiple backgrounds to pull off a a design. Each background will have its own color, image, repeat, position, etc. It can be verbose!

You have four images:

body {
  
  background-position:
    top 10px left 10px,
    top 10px right 10px,
    bottom 10px right 10px,
    bottom 10px left 10px;
  
  background-repeat: no-repeat;
  
  background-image:
    url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-top-left.svg),
    url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-top-right.svg),
    url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-bottom-right.svg),
    url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-bottom-left.svg);
  
}

You want to add a fifth in a media query:

@media (min-width: 1500px) {
  body {
    /* REPEAT all existing backgrounds, then add a fifth. */
  }
}

That's going to be super verbose! You'll have to repeat each of those four images again, then add the fifth. Lots of duplication there.

One possibility is to create a variable for the base set, then add the fifth much more cleanly:

body {
  --baseBackgrounds: 
    url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-top-left.svg),
    url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-top-right.svg),
    url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-bottom-right.svg),
    url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-bottom-left.svg);

  background-position:
    top 10px left 10px,
    top 10px right 10px,
    bottom 10px right 10px,
    bottom 10px left 10px;
  
  background-repeat: no-repeat;
  
  background-image: var(--baseBackgrounds);
}
@media (min-width: 1500px) {
  body {
    background-image: 
      var(--baseBackgrounds),
      url(added-fifth-background.svg);
  }
}

But, it's really up to you. It might make more sense and be easier manage if you made each background image into a variable, and then pieced them together as needed.

body {
  --bg1: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-top-left.svg);
  --bg2: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-top-right.svg);
  --bg3: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-bottom-right.svg);
  --bg4: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-bottom-left.svg);
  --bg5: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-bottom-left.svg);
  
  background-image: var(--bg1), var(--bg2), var(--bg3), var(--bg4);
}
@media (min-width: 1500px) {
  body {
    background-image: var(--bg1), var(--bg2), var(--bg3), var(--bg4), var(--bg5);
  }
}

Here's a basic version of that, including a supports query:

See the Pen
Multiple BGs with Custom Properties
by Chris Coyier (@chriscoyier)
on CodePen.

Dynamically changing just the part of a value is a huge strength of CSS custom properties!

Note, too, that with backgrounds, it might be best to include the entire shorthand as the variable. That way, it's much easier to piece everything together at once, rather than needing something like...

--bg_1_url: url();
--bg_1_size: 100px;
--bg_1_repeat: no-repeat;
/* etc. */

It's easier to put all of the properties into shorthand and use as needed:

body {  
  --bg_1: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-top-left.svg) top 10px left 10px / 86px no-repeat;
  --bg_2: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-top-right.svg) top 10px right 10px / 86px no-repeat;
  --bg_3: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-bottom-right.svg) bottom 10px right 10px / 86px no-repeat;
  --bg_4: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/angles-bottom-left.svg) bottom 10px left 10px  / 86px no-repeat;
    
  background:
    var(--bg_1), var(--bg_2),var(--bg_3),var(--bg_4);
}

Like this.

The post Managing Multiple Backgrounds with Custom Properties appeared first on CSS-Tricks.

Did you know that CSS Custom Properties can handle images too?

So you might be aware of CSS Custom Properties that let you set a variable, such as a theme color, and then apply it to multiple classes like this:

:root {
  --theme: #777;
}

.alert {
  background: var(—-theme);
}

.button {
  background: var(—-theme);
}

Well, I had seen this pattern so often that I thought Custom Properties could only be used for color values like rgba or hex – although that’s certainly not the case! After a little bit of experimentation and sleuthing around, I realized that it’s possible to use Custom Properties to set image paths, too.

Here’s an example of that in action:

:root {
  --errorIcon: url(error.svg)
}

.alert {
  background-image: var(--errorIcon);
}

.form-error {
  background-image: var(--errorIcon);
}

Kinda neat, huh? I think this could be used to make an icon system where you could define a whole list of images in the :root and call it whenever you needed to. Or you could make it easier to theme certain classes based on their state or perhaps in a media query, as well. Remember, too, that custom properties can be overridden within an element:

:root {
  --alertIcon: url(alert-icon.svg)
}

.alert {
  background-image: var(--alertIcon);
}

.form-error {
  --alertIcon: url(alert-icon-error.svg)
  background-image: var(--alertIcon);
}

And, considering that custom properties are selectable in JavaScript, think about the possibilities of swapping out images as well. I reckon this might useful to know!

The post Did you know that CSS Custom Properties can handle images too? appeared first on CSS-Tricks.

CSS Variables + calc() + rgb() = Enforcing High Contrast Colors

As you may know, the recent updates and additions to CSS are extremely powerful. From Flexbox to Grid, and — what we’re concerned about here — Custom Properties (aka CSS variables), all of which make robust and dynamic layouts and interfaces easier than ever while opening up many other possibilities we used to only dream of.

The other day, I was thinking that there must be a way to use Custom Properties to color an element's background while maintaining a contrast with the foreground color that is high enough (using either white or black) to pass WCAG AA accessibility standards.

It’s astonishingly efficient to do this in JavaScript with a few lines of code:

var rgb = [255, 0, 0];

function setForegroundColor() {
  var sum = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) / 1000);
  return (sum > 128) ? 'black' : 'white';
}

This takes the red, green and blue (RGB) values of an element’s background color, multiplies them by some special numbers (299, 587, and 144, respectively), adds them together, then divides the total by 1,000. When that sum is greater than 128, it will return black; otherwise, we’ll get white. Not too bad.

The only problem is, when it comes to recreating this in CSS, we don't have access to a native if statement to evaluate the sum. So,how can we replicate this in CSS without one?

Luckily, like HTML, CSS can be very forgiving. If we pass a value greater than 255 into the RGB function, it will get capped at 255. Same goes for numbers lower than 0. Even negative integers will get capped at 0. So, instead of testing whether our sum is greater or less than 128, we subtract 128 from our sum, giving us either a positive or negative integer. Then, if we multiply it by a large negative value (e.g. -1,000), we end up with either very large positive or negative values that we can then pass into the RGB function. Like I said earlier, this will get capped to the browser’s desired values.

Here is an example using CSS variables:

:root {
  --red: 28;
  --green: 150;
  --blue: 130;

  --accessible-color: calc(
    (
      (
        (var(--red) * 299) +
        (var(--green) * 587) +
        (var(--blue) * 114) /
        1000
      ) - 128
    ) * -1000
  );
}

.button {
  color:
    rgb(
      var(--accessible-color),
      var(--accessible-color),
      var(--accessible-color)
    );
  background-color:
    rgb(
      var(--red),
      var(--green),
      var(--blue)
    );
}

If my math is correct (and it's very possible that it's not) we get a total of 16,758, which is much greater than 255. Pass this total into the rgb() function for all three values, and the browser will set the text color to white.

At this point, everything seems to be working in both Chrome and Firefox, but Safari is a little cranky and gives a different result. At first, I thought this might be because Safari was not capping the large values I was providing in the function, but after some testing, I found that Safari didn't like the division in my calculation for some reason.

Taking a closer look at the calc() function, I noticed that I could remove the division of 1,000 by increasing the value of 128 to 128,000. Here’s how that looks so far:

:root {
  --red: 28;
  --green: 150;
  --blue: 130;

  --accessible-color: calc(
    (
      (
        (var(--red) * 299) +
        (var(--green) * 587) +
        (var(--blue) * 114)
      ) - 128000 /* HIGHLIGHT */
    ) * -1000
  );
}

.button {
  color:
    rgb(
      var(--accessible-color),
      var(--accessible-color),
      var(--accessible-color)
    );
  background-color:
    rgb(
      var(--red),
      var(--green),
      var(--blue)
    );
}

Throw in a few range sliders to adjust the color values, and there you have it: a dynamic UI element that can swap text color based on its background-color while maintaining a passing grade with WCAG AA.

See the Pen
CSS Only Accessible Button
by Josh Bader (@joshbader)
on CodePen.

Putting this concept to practical use

Below is a Pen showing how this technique can be used to theme a user interface. I have duplicated and moved the --accessible-color variable into the specific CSS rules that require it, and to help ensure backgrounds remain accessible based on their foregrounds, I have multiplied the --accessible-color variable by -1 in several places. The colors can be changed by using the controls located at the bottom-right. Click the cog/gear icon to access them.

See the Pen
CSS Variable Accessible UI
by Josh Bader (@joshbader)
on CodePen.

There are other ways to do this

A little while back, Facundo Corradini explained how to do something very similar in this post. He uses a slightly different calculation in combination with the hsl function. He also goes into detail about some of the issues he was having while coming up with the concept:

Some hues get really problematic (particularly yellows and cyans), as they are displayed way brighter than others (e.g. reds and blues) despite having the same lightness value. In consequence, some colors are treated as dark and given white text despite being extremely bright.

What in the name of CSS is going on?

He goes on to mention that Edge wasn’t capping his large numbers, and during my testing, I noticed that sometimes it was working and other times it was not. If anyone can pinpoint why this might be, feel free to share in the comments.

Further, Ana Tudor explains how using filter + mix-blend-mode can help contrast text against more complex backgrounds. And, when I say complex, I mean complex. She even goes so far as to demonstrate how text color can change as pieces of the background color change — pretty awesome!

Also, Robin Rendle explains how to use mix-blend-mode along with pseudo elements to automatically reverse text colors based on their background-color.

So, count this as yet another approach to throw into the mix. It’s incredibly awesome that Custom Properties open up these sorts of possibilities for us while allowing us to solve the same problem in a variety of ways.

The post CSS Variables + calc() + rgb() = Enforcing High Contrast Colors appeared first on CSS-Tricks.