Skip to content

Instantly share code, notes, and snippets.

@danpoynor
Last active August 8, 2021 22:14
Show Gist options
  • Save danpoynor/0bf352c0b679ff6741a61df4b7ff44f0 to your computer and use it in GitHub Desktop.
Save danpoynor/0bf352c0b679ff6741a61df4b7ff44f0 to your computer and use it in GitHub Desktop.
Ways to use CSS Custom Properties

Uses for CSS Custom Properties

Example code using CSS Custom Properties.


Globally scoped variable

:root {
  --main-bg-color: brown;
}

Locally scoped variable, scoped to the element

element {
  --main-bg-color: brown;
}
element {
  background-color: var(--main-bg-color);
}

The default value for a Custom Propery is invalid value until it is assigned a value

The initial value of a custom property is an empty value; that is, nothing at all.

This is equivilant to setting background-color to an invalid value. In case an invalid value is assigned, the browser will:

  1. first check if there is an inherited value assigned to any parent elements it can use. Note: The property needs to be one that’s inherited by default, such as color, it would compute to the inherited value rather than the initial value.
  2. assign it the browsers default initial value to use.
p {
  background-color: var(--x);
}

Note directly assigning a non-valid value will cause a syntax error and the line will be ignored.

p {
  background-color: 16px;
}

Note: If a property contains one or more var() functions, and those functions are syntactically valid, the entire property’s grammar must be assumed to be valid at parse time. It is only syntax-checked at computed-value time, after var() functions have been substituted.


Use with fallback values

.two {
  /* Red if --my-var is not defined */
  color: var(--my-var, red);
}

.three {
  /*
  pink if --my-var and --my-background are not defined.
  Note this is known to cause performance issues as it takes a longer time to parses through the values.
  */
  background-color: var(--my-var, var(--my-background, pink));
}

.three {
  /* Invalid: "--my-background, pink" */
  background-color: var(--my-var, --my-background, pink);
}

Use in Javascript

JS

// get variable from inline style
element.style.getPropertyValue("--my-var");

// get variable from wherever
getComputedStyle(element).getPropertyValue("--my-var");

// set variable on inline style
element.style.setProperty("--my-var", jsVar + 4);

Examples of values that can be assigned

The value may contain any character except some characters with special meaning like newlines, unmatched closing brackets, i.e. ), ], or }, top-level semicolons, or exclamation marks.

The prohibition on top-level "!" characters does not prevent !important from being used, as the !important is removed before syntax checking happens and makes the custom property "important" in the CSS cascade.

Note: While the value must represent at least one token, that one token may be whitespace. This implies that --foo: ; is valid, and the corresponding var(--foo) call would have a single space as its substitution value, but --foo:; is invalid.

:root {
  --somekeyword: left;
  --somecolor: #0000ff;
  --somecomplexvalue: 3px 6px rgb(20, 32, 54);
}

The following is a valid custom property:

--foo: if(x > 5) this.width = 10;

While this value is obviously useless as a variable, as it would be invalid in any normal property, it might be read and acted on by JavaScript.


Use with :lang() to do simple i18n

A real-world example of custom property usage is easily separating out strings from where they’re used, to aid in maintenance of internationalization:

:root,
:root:lang(en) {--external-link: "external link";}
:root:lang(de) {--external-link: "externer Link";}

a[href^="http"]::after {
  content: " (" var(--external-link) ")"
}

The variable declarations can even be kept in a separate file, to make maintaining the translations simpler.


Resolving Dependency Cycles

This example shows an invalid instance of variables depending on each other:

:root {
  --one: calc(var(--two) + 20px);
  --two: calc(var(--one) - 20px);
}

Both --one and --two now compute to their initial value, rather than lengths.

Given the following structure, these custom properties are not cyclic, and all define valid variables:

<one><two><three /></two></one>
one   { --foo: 10px; }
two   { --bar: calc(var(--foo) + 10px); }
three { --foo: calc(var(--bar) + 10px); }

The element defines a value for --foo. The <two> element inherits this value, and additionally assigns a value to --bar using the foo variable. Finally, the <three> element inherits the --bar value after variable substitution (in other words, it sees the value calc(10px + 10px)), and then redefines --foo in terms of that value. Since the value it inherited for --bar no longer contains a reference to the --foo property defined on <one>, defining --foo using the var(--bar) variable is not cyclic, and actually defines a value that will eventually (when referenced as a variable in a normal property) resolve to 30px.


The calc() function can be used to add unit types to numerical values, like so:

.foo {
  --gap: 20;
  margin-top: calc(var(--gap) * 1px);
}

More examples needed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment