The simplest case of this, and one that’s a long-established best practice, is to use well-defined CSS classes, designed for interaction. The JS side of things then adds and removes them as needed in order to trigger event-driven visual changes:
const button = document;button;
js-prefix, as used above, is a good choice.
Adding and removing classes works great for boolean states, but sometimes you need more complex data passing. One example is if you’re reacting to user input (such as a click or touch) and need to take into account the input coordinates in order to display something.
Let’s say we have a container, and we want to represent the last location the user clicked inside of it. If we have an auxiliary element inside that container, we could move it around by doing:
Up until now, there wasn’t any great way to solve this, but with custom properties we can easily bring back the missing abstraction layer:
const container = document;container;
Tip: Have you ever needed to change styles for a pseudo-element (such as
One variable, many changes
const thingsToUpdate ='playButton' 'background-color''title': 'color''progress': 'background-color';for let id property of thingsToUpdatedocumentstyle;
Or in HTML:
const colorList =document;for let el of colorListelstyle;const backgroundList =document;for let el of backgroundListelstyle;
Either way, this would make maintenance harder, as this parallel list of affected elements and properties would need to be kept up to date.
Yet another option would be to inject a new stylesheet onto the page, which would override the default colors. This approach is probably slightly better (albeit somewhat hacky), but it would still require overriding a number of styles and keeping some sort of template, which would also need maintenance:
With custom properties, this becomes much simpler; just determine the highest element in the tree that you want to modify, and let cascading do the rest:
This approach doesn’t require your script to have any awareness of which elements are affected, and doesn’t force you to maintain any sort of template for your changes. And as a bonus, it’s significantly simpler than any of the previous approaches!
Why this is important
This means that any runtime changes are restricted to a set of well-defined entities, which are designed for interaction. By doing this, you’ll reduce the scope for bugs and unexpected behavior, as well as make it easier to test styling and behavior separately.
Maintenance also becomes easier, since you’re able to modify and maintain styling and logic independent of each other, while sticking to the right tools for the job: CSS for styling and JS for logic.
On the next article, we’ll take best practices even further and look at the benefits of designing modular and componentized CSS with custom properties. See you then!