Go back

JavaScript in CSS — A subtle approach

You know how we have CSS in JS patterns when we're building apps on the web, yeah? There's a possibility of having JavaScript in CSS, in the nearest future.

React is among the many tools/libraries that adopted the CSS in JS pattern when it comes to styling Single Page Apps on the web. And one particular tool that brings the feature out of the box, is styled-components.

Reusability of styles

From being able to create functional and reusable — styled — components to passing props to these components, which sometimes can be used to read/get state values from their parent containers

import styled from 'styled-components'
 
export const FlexContainer = styled.div`
  width: 100%;
  display: flex;
  height: 100vh;
  flex-wrap: wrap;
  justify-content: space-between;
`

Importing the component above and wrapping it around any element(s) makes them flex items, and they'd behave just the same way when you're using the normal approach of styling. Do not forget, this same approach can be implemented when you write vanillajs.

From the perspective of vanillajs — DOM manipulation

With DOM manipulation, you can get the reference of an element in the DOM tree, store it as a variable, and manipulate the style, say, for example, when an event is triggered. Your case may be a click event, mine may be a mouse event or any event at all.

Say you want to modify the style of an element when an event is fired. Let's say you're subscribing to a click event, and you want to switch between two states — open or close — for that element, we know how the mental model of implementing this goes, in React.

But, there's a subtle approach to DOM manipulation. You get the reference of the element and update its style with the style Web API.

const element = document.querySelector("#menu-element");
const hamburgerMenuIcon = document.querySelector("#menu-icon");
 
hamburgerMenuIcon.addEventListener("click", function() {
  element.style.display: "block"
})

The snippet above assumes that the initial state of the menu-element has a display state that is set to none, and as such when the click event is fired, the callback function modifies the element's display state.

JavaScript in CSS — what's the possibility?

So, what exactly is the essence of the story I've been selling to you for a while now? Well, okay, please hear me out; I came across a tweet from Wes Bos on Twitter, about how we can use custom CSS properties (variables) to store state.

That wasn't even the reason why I was astonished. I felt astonished because, When I went through the contents of the snippet, I could understand what was going on. So, the aim of this article isn't to sell the idea of JS in CSS to you. No, not at all. This is just me trying to explain what is going on in the snippet below to you

<html>
  <body>
    <div style="--count: 0; --name: wes;">
      <h2>Hello <span data-bind="--count"></span></h2>
      <button
        onclick="setState(event, 'count', (count => parseInt(count) + 1 ))"
      >
        + increment
      </button>
      <button
        onclick="setState(event, 'count', (count => parseInt(count) - 1 ))"
      >
        - decrement
      </button>
      <p>The current count is <span data-bind="--count"></span></p>
    </div>
 
    <script>
      function setState(event, key, callback) {
        const state = getComputedStyle(event.currentTarget)
        const prop = `--${key}`
        const value = state.getPropertyValue(prop)
        const element = event.currentTarget.closest(`[style*="${prop}"]`)
        const updatedValue = callback(value)
        element.style.setProperty(prop, updatedValue)
 
        // return the result by updating the UI
        element.querySelectorAll(`[data-bind="${prop}"]`).forEach((el) => {
          el.innerText = updatedValue
        })
      }
 
      // on initial render -- page load
      document.querySelectorAll(`data-bind`).forEach((el) => {
        setState(
          { currentTarget: el },
          el.dataset.bind.replace('--', ''),
          (val) => val
        )
      })
    </script>
  </body>
</html>

In summary, what the snippet above does is this; It renders two buttons that you can use to either increase or decrease the current count, which is tracked with the CSS variable, --count. It also renders the name, "wes" with the --name variable in the first span element.

The setState function takes in three arguments: an event object, a key, and a callback function. The function uses the getComputedStyle API to get the current state of the element that triggered the event. The getPropertyValue method is then used to obtain the value of the key (in this case, the counter value).

The function then finds the closest ancestor element that has the property set and updates its value with the returned value from the callback function. Then it goes on to update the UI by searching for elements with a data-bind attribute and updating their inner text to match the updated value.

The function below the "initial render" comment is executed when the page loads. It searches for all elements with a data-bind attribute and calls the setState function on each of them, passing the element and the value of the data-bind attribute as arguments.

data-bind? what is this exactly? 😕

I think one thing that stood out for me, was the data-bind attribute, this is my first time seeing any attribute like it. After a bit of research, here's how I think it works;

The attribute binds the value of a CSS property to an HTML element. In the code snippet above, the data-bind attribute is used to bind the value of the counter to a span element, allowing the updated value to be displayed on the page.

The attribute is used to identify which elements should be updated in the UI when the value of a CSS property changes. This makes it easier to manage updates to the user interface and to keep the styles and values in sync.

The essence of this attribute is to provide a way to connect the values of CSS properties to elements in the markup, making it easier to manipulate and display the values dynamically.

final thoughts

Once again, and in summary: the snippet demonstrates how you can update CSS properties using JavaScript, update the UI with the new values, and render the initial state of the page on load. I can remember clearly how Wes talked about the aim of this snippet. Oh, wait! Let me just quote him 👇

I am once again here to explain that this is (mostly?) a joke. Though, it is neat that CSS Custom properties 1. cascade down the DOM tree 2. Can be any value 3. Can easily be accessed at any level via getComputedStyle without prop drilling. a joke where you learn something!

I hope that you've learned something from this Joke, too.