loading...

CSS-vars-hook: How to manipulate CSS Custom properties in React

morewings profile image Dima Vyshniakov ・3 min read

Problem

As a developer I need to manipulate HTML element's style from within React components. Changing color, size, position, animation etc.

Classic solution

Vanilla React gives me an ability to change an element's class name or style attribute. Like we did in good old jQuery times.

const Component = () => {
  return (
    <div
      className="foo"
      style={{color: 'chucknorris'}}>
      Hello
   </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

This approach has two major problems:

  1. CSS class method is convenient if you have enumerable amount of values to put in CSS. So they can be described by limited amount of CSS classes like button-primary, button-secondary etc. But CSS classes will not help you much, when you come into applying non-enumerable range of values, e.g. you want to animate element position. Simply because you can't create class name for every possible coordinates.

  2. style attribute is more flexible. You can apply any value to it during runtime and the browser will repaint the document accordingly. This solution also doesn't play well. It may and will conflict with existing CSS, since style attribute rules have preference over rules defined in the stylesheet. Also, you can not use Media queries, pseudo-classes or pseudo-elements here.

CSS in JS

CSS in JS pattern became popular last years. There are plenty of libraries today (Styled Components, Aphrodite, JSS and others), which allow you to define CSS rules inside JavaScript code, thus making it accessible during your component's runtime.

import React from 'react';
import styled from 'styled-components';

const Component = styled.div`
  /* color value changes depending on props.danger value */
  color: ${props => props.danger ? 'red' || 'green'};
`
Enter fullscreen mode Exit fullscreen mode

While being efficient and widely adopted CSS in JS also has problems I would like to address.

  1. Additional layer of complexity. Putting a CSS in JS library into use adds an extra layer to your front-end stack, which can sometimes be unnecessary. It’s not always worth the hassle, especially in the case of a simpler project.

  2. Hard to debug. Generated class names and selectors significantly worsen code readability, if you use your browser’s devtools for debugging. This also makes the learning curve much more steep and hard to master for beginners.

  3. Mishmash of languages. Having two programming languages in a single module was never a good idea (I'm looking at you, JSX). Add CSS in JS library and congrats, now you have three of them.

css-vars-hook

Example

Let me show you the code first.

This pen demonstrates how to manipulate background color from inside React component using css-vars-hook. css-vars-hook is a tiny package allows applying CSS Variables to HTML Elements, rendered by React component.

You can provide any valid color value and change box color accordingly.

To make it work you need to add --boxColor variable to your CSS.

.demo-box {
  background: var(--boxColor);
  /*...*/
}
Enter fullscreen mode Exit fullscreen mode

Features

  • Native. Uses vanilla CSS, no need to implement additional processing.
  • Fast. CSS variables manipulation doesn't trigger component reconciliation.
  • Simple. It's just CSS and React interoperation.

Interface

Library exposes two exports: useTheme and useVariable

useTheme applies multiple CSS properties to a given Html Element.

import {useTheme} from 'css-vars-hook';

const {
  /* Theme container element setter. <div ref={setRef} /> */
  setRef,
  /* React ref. Use as theme container element getter only. */
  ref,
  /* Object containing style properties {'--foo': 'bar'}.
Apply on target element to prevent flash 
of unstyled content during server-side rendering.
  <div style={style} ref={setRef} /> */
  style,
  /* Get variable value. function(variableName: string) => string */
  getVariable,
  /* Set variable value. function(variableName: string, value: (string|number)) => void */
  setVariable,
  /* Remove variable. function(variableName: string) => void */
  removeVariable
} = useTheme({foo: 'bar'});
Enter fullscreen mode Exit fullscreen mode

useVariable applies a single CSS property to a given HTML Element.

import {useVariable} from 'css-vars-hook';

const {
  ref,
  setRef,
  style,
  setVariable,
  getVariable,
  removeVariable
} = useVariable('variableName', 'value');
Enter fullscreen mode Exit fullscreen mode

Server-side rendering (SSR)

Since CSS variables are applied after initial render, you need to apply style attribute to the target HTML Element to prevent flash of unstyled content.

// ...
const Component = () => {
  const { setRef, style } = useTheme({foo: 'bar'});
  return (
    <div ref={setRef} style={style}>Hello world!</div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Discussion

pic
Editor guide