astrotim / using-css-custom-properties

Notes about using CSS Custom Properties

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Using CSS Custom Properties

Notes for a talk on the topic.

Key takeaways

  • Fallback
  • Variable stacks
  • Inheritance & proximity
  • initial keyword
  • Component variants
  • Theming & dark mode
  • DRY & the logic fold
  • Taming source order
  • JavaScript & Houdini

Definiton

CSS Custom Properties are property names prefixed with -- containing a value that can be used in other declarations using the var() function.

Global variables

Like preprocessor variables (Sass, PostCSS):

$colour-primary: #fdc605;

.primary-thing {
   background-color: $colour-primary;
}

We can define global variables for reuse:

:root {
  --colour-primary: #fdc605;
}

.primary-thing {
   background-color: var(--colour-primary);
}

Scope and the cascade

Custom properties are scoped to the elements they are declared on, and participate in the cascade, so normal CSS behaviour such as specificity applies.

Codepen scope and cascade example.

Fallback

The var() function accepts a fallback value:

.thing {
  color: var(--thing-text, black);
}

But it only accepts a single fallback - a limitation is required to support fallbacks with a comma inside them, such as background-image or font stacks.

Variable stacks

We can workaround this by nesting another var as the fallback, allowing us to create 'variable stacks`.

/* link.css */
border-color: var(--link-hover, var(--link-text, var(--colour-assistant)));

We've done this in the Nucleus branding API - more details below.

Inheritance and proximity

In the absense of a 'local' custom property, the var() function will inherit the value from the property in closest proximity.

This allows for using custom properties other than where they are defined.

initial and invalid

Setting a custom property value to initial will make it fallback to the next available value.

If a value is invalid for a given property, such as 20px for color, this sets the entire property value to initial.

Explain it with a rainbow 🌈.

Component variants

Using the inheritance behaviour, we can expose sets of CSS Custom Properties to create simple APIs for component variants.

The "detail header" example.

Theming

Custom properties enable theming independent of CSS structure. We can make use of the fallback values to set defaults and expose named custom properties for specific overrides.

This is the basis of the Nucleus branding API. Each branded component implements one of the three base palette colours as a default: primary, accent or assistant. These colours can then be overridden where necessary.

/* button.css */
border-color: var(--button-fill, var(--colour-accent));

/* link.css */
color: var(--link-text, var(--colour-assistant));

Theme config

To set up a new theme, an application simply needs to define the palette of three colours:

:root {
  --colour-primary: #fdc605;
  --colour-accent: #fdc605;
  --colour-assistant: #0058cc;
}

Dark mode

For backgrounds, borders and utility text, we similarly defined a palette of reversible greys which can be redefined for a dark context

/* light mode */
:root {
  --tint-1: hsl(0, 0%, 20%);
  --tint-2: hsl(0, 0%, 40%);
  --tint-3: hsl(0, 0%, 60%);
  --tint-4: hsl(0, 0%, 80%);
  --tint-5: hsl(0, 0%, 90%);
  --tint-6: hsl(0, 0%, 95%);
}

/* dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --colour-assistant: #4d9aff;
    --tint-2: hsl(0, 0%, 60%);
    --tint-6: hsl(0, 0%, 10%);
  }
}

/* or */
.dark-section {
  --colour-assistant: #4d9aff;
  --tint-2: hsl(0, 0%, 60%);
  --tint-6: hsl(0, 0%, 10%);
}

The logic fold

This guy called Mike has coined a term for an interesting concept based on using CSS Custom Properties: the logic fold.

The idea here is that the relevant variable values are positioned at the top of the file where it can be made easier to trace how a custom property changes. Then "below the fold" is the declarative CSS which implements these variables.

Button example

CodePen link

DRY: reducing repetition

Similar to using preprocessed variables, custom properties allow for removing repetition resulting in more readable code.

CodePen example - grid columns.

Source order independence

When using an authoring system such as CSS Modules in conjunction with Webpack, one does not have control over the final source order of the compiled CSS file. This is determined by the order in which imports are resolved during the build process.

While the encapsulation afforded by CSS Modules is greatly beneficial, one annoying side effect is that two selectors of equal specificity may be compiled in a different order, with application styles coming before library styles. The hacky workaround to ensure the correct styles are applied is to chain the selector to itself to 'win' by specificity.

Custom properties allow us to avoid needing to reach for this hack.

CodePen example - taming source order

Simplification

Something like a linear-gradient can have some pretty hostile syntax. This can be greatly simplified with custom properties.

CodePen example - gradients.

With JavaScript

element.style.setProperty("--variable-name")

Example

Credit to Lea Verou for this code.

var root = document.documentElement;

document.addEventListener("mousemove", evt => {
	let x = evt.clientX / innerWidth;
	let y = evt.clientY / innerHeight;

	root.style.setProperty("--mouse-x", x);
	root.style.setProperty("--mouse-y", y);
});

CodePen demo link

Houdini

Houdini is a set of low-level APIs that exposes parts of the CSS engine, giving developers the power to extend CSS by hooking into the styling and layout process of a browser’s rendering engine.

In short, Houdini gives a developer building blocks in JavaScript to allow writing new kinds of CSS

.bg {
  --square-odd: skyblue;
  --square-even: white;
  background-image: paint(checkerboard);
}

CodePen example - CSS Paint API

References

About

Notes about using CSS Custom Properties