CSS Custom Properties (CSS Variables) Sample

Available in Chrome 49+ | View on GitHub | Browse Samples

Background

CSS custom properties allow you to store and retrieve values from properties you define yourself.

They follow the same rules as other CSS properties, so you're able to define and use them at multiple levels, following standard CSS cascading and specificity rules.

Combining custom variables with calc(), in particular, allows for some very interesting possibilities.

Live Output

CSS Variable Demo

Spacing unit:

Columns:

Margins:

Red
Click the buttons on the cards to set the default color scheme for the whole sample.
Pink
The colors on the cards are not affected because they're individually defined at the card level, and thus prioritised according to standard CSS rules.
Purple
Use the controls above to adjust some properties that affect the entire page.
Cyan
Every size on the page is calculated as a multiple of the spacing unit, so setting the property to a different value resizes all elements.
Teal
Changing the number of columns is done by recalculating the relative size of cells on the grid.
Green
Margins adjusts both the spacing around the grid and the gutters inside of it.

CSS Snippet

:root {
  /*************************** Page-wide variables ****************************/

  /* Base spacing unit. */
  --spacing-unit: 6px;

  /* Margin size. No unit, because it's a multiple of the spacing unit. */
  --margins: 2;

  /* Colors. */
  --primary-color: #5E35B1;
  --primary-color-text: #FFF;

  /* Number of columns to show. */
  --grid-columns: 3;

  /***************************** Calculated values ****************************/
  /* We don't use calc here because we don't want to resolve the values yet.
     You can think of these as simple textual replacements. */

  /* The size of the margin around the card grid. */
  --margin-size: (var(--margins) * 2);
  /* How much internal padding each cell should have */
  --cell-padding: (4 * var(--spacing-unit));
  /* How big the space between cells should be */
  --grid-gutter: (var(--margins) * var(--spacing-unit));
  /* How big the space should be around the grid */
  --grid-margin: (var(--margin-size) * var(--spacing-unit));
  /* Calculated cell margin */
  --cell-margin: (var(--grid-gutter) / 2);
}

.header {
  display: block;
  position: relative;
  height: calc(28 * var(--spacing-unit));
  /* Use a default value for the background color, by passing it in as the 2nd
     parameter to var(). We don't strictly need it in this case, since we've
     defined it at the document level, but this illustrates common usage. */
  background-color: var(--primary-color, #5E35B1);
  padding-left: calc(4 * var(--spacing-unit));

  transition: background-color 1s;
}

.title {
  position: relative;
  top: calc(8 * var(--spacing-unit));
  font-family: 'Roboto', 'Helvetica', sans-serif;
  font-size: calc(4 * var(--spacing-unit));
  font-weight: 400;
  color: var(--primary-color-text);

  transition: color 1s;
}

.shade {
  display: block;
  box-sizing: border-box;
  position: absolute;
  padding-left: calc(1 * var(--spacing-unit));
  bottom: 0;
  left: 0;
  width: 100%;
  height: calc(8 * var(--spacing-unit));
  background-color: rgba(0, 0, 0, 0.2);
}

.grid {
  /* Resets */
  margin: 0;
  border: 0;
  padding: 0;

  display: flex;
  flex-flow: row wrap;
  align-items: stretch;

  padding: calc(var(--grid-margin) - var(--cell-margin));

  background-color: var(--grid-color);
}

.cell {
  font-family: 'Roboto', 'Helvetica', sans-serif;
  color: rgb(97, 97, 97);
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  margin: calc(var(--cell-margin));
  background-color: var(--cell-color);
  width: calc(100% / var(--grid-columns) - var(--grid-gutter));
  box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),
      0 3px 1px -2px rgba(0,0,0,.2),
      0 1px 5px 0 rgba(0,0,0,.12);
}

.cell-title {
  font-size: calc(3 * var(--spacing-unit));
}

.cell-header {
  display: flex;
  align-items: center;
  color: var(--primary-color-text);
  box-sizing: border-box;
  background-color: var(--primary-color);
  padding-left: calc(var(--cell-padding));
  height: calc(12 * var(--spacing-unit));
}

.cell-content {
  font-size: calc(2.5 * var(--spacing-unit));
  padding: calc(var(--cell-padding));
  flex-grow: 1;
}

.cell-actions {
  padding: calc(2 * var(--spacing-unit));
  border-top: 1px solid rgba(0, 0, 0, 0.12);
}

.picker-button {
  position: relative;
  font-family: 'Roboto', 'Helvetica', sans-serif;
  font-size: calc(2 * var(--spacing-unit));
  display: inline-block;
  box-sizing: border-box;
  border: none;
  border-radius: 2px;
  color: var(--primary-color);
  text-decoration: none;
  padding: calc(2 * var(--spacing-unit));
  text-decoration: none;
  background: transparent;
  cursor: pointer;
  overflow: hidden;
  text-transform: uppercase;

  transition: background-color 0.2s;
}

.picker-button:focus {
  outline: none;
}

.picker-button:active {
  background-color: #DDD;
}

.controls {
  display: flex;
  flex-direction: column;
  position: absolute;
  top: 4px;
  right: 4px;
  font-family: sans-serif;
  font-size: 12px;
  padding: 8px;
  background-color: rgba(200, 200, 200, 0.8);
}

.control {
  display: flex;
  align-items: center;
  margin: 0 0 8px 0;
}

.control-key {
  flex-grow: 1;
  margin-right: 16px;
}

.control-value {
  width: 152px;
}

JavaScript Snippet

'use strict';

// Auxiliary method. Retrieves and sanitises the value of a custom property.
var getVariable = function(styles, propertyName) {
  return String(styles.getPropertyValue(propertyName)).trim();
};

// Auxiliary method. Sets the value of a custom property at the document level.
var setDocumentVariable = function(propertyName, value) {
  document.documentElement.style.setProperty(propertyName, value);
};

// Sets the document color scheme to the color scheme of the clicked element.
// This illustrates how it's easy to make a change affecting a large number of
// elements by simply changing a few custom properties.
var chooseDefaultColor = function(event) {
  // Get the styles for the event target (the clicked button), so we can see
  // what its custom properties are set to.
  var styles = getComputedStyle(event.target);

  // Get the values for the button's colours...
  var primary = getVariable(styles, '--primary-color');
  var text = getVariable(styles, '--primary-color-text');
  // ... and apply them to the document.
  setDocumentVariable('--primary-color', primary);
  setDocumentVariable('--primary-color-text', text);
};

// Initialise page controls.
window.addEventListener('load', function() {
  // Get the styles for the document.
  // This is where we've chosen to store all the global variables we use.
  var styles = getComputedStyle(document.documentElement);

  var quantum = document.getElementById('quantum');
  var gutter = document.getElementById('gutter');
  var columns = document.getElementById('columns');

  // Set up event handlers for buttons.
  var buttons = document.querySelectorAll('.picker-button');
  for (var i = 0; i < buttons.length; i++) {
    buttons[i].addEventListener('click', chooseDefaultColor);
  }

  // Retrieve initial custom property values and update controls.
  quantum.value = getVariable(styles, '--spacing-unit').replace('px', '');
  gutter.value = getVariable(styles, '--margins');
  columns.value = getVariable(styles, '--grid-columns');

  // Set up event handlers for having the sliders update the custom properties
  // at the document level.
  quantum.addEventListener('input', function() {
    setDocumentVariable('--spacing-unit', quantum.value + 'px');
  });

  gutter.addEventListener('input', function() {
    setDocumentVariable('--margins', gutter.value);
  });

  columns.addEventListener('input', function() {
    setDocumentVariable('--grid-columns', columns.value);
  });
});