CSS Custom Properties, the right way to handle CMS-driven styles
Monday, September 15, 2025 · 6 minute read
CSS Custom Properties, the right way to handle CMS-driven styles

As a web developer that focuses heavily on frontend, I've always appreciated JavaScript for the straightforward rendering of dynamic UI, especially with React, Svelte or other Javascript frameworks and their component-like architecture. These components can then be further customised by adding something like a Content Management System (CMS) to the mix. By adding a CMS, we introduce another layer of complexity: dynamic producer-authored content, the data that a CMS producer configures. For an engineer, this raises questions such as:

  • How does your component and its styles adapt to the authored content?
  • Should we render content server-side or client-side?
  • What effect does this have on performance, if any?

In this post, I'm going to go over the common mistakes that developers make when applying logic and styling to their components based on authored content, as well as the efficient and intuitive solutions for these problems. We're going to be assuming that the authored content is rendered through dynamic HTML template languages, such as Jinja or Sightly.

A common rule of thumb with any type of web development is that where possible, we, the host, should always server-side render components. This means that the "work" and "responsibility" of any calculations needed (this is grossly oversimplifying server-side rendering, but you get the gist) for rendering falls on us. The user handles only dynamic changes, such as open and closing a dropdown.

Let's start with an easy example, where we want to server-side render a button that changes level (i.e. primary, secondary) depending on what a producer wants.

Button Levels

Button HTMLButton CSS

 

In this scenario, our CSS will already be compiled, meaning that the browser is ready with the style the second the stylesheet is ready. This makes sense as we know that the possibilities of the button can only be primary and secondary. If we want to introduce a tertiary button, we just add to the existing CSS.

We now have a server side rendered button, straight from authored preferences in the CMS.

Common mistakes

Let's introduce some complexity. What if a producer wants to style a hero banner with a gradient that becomes gradually more opaque from left to right?

hero banner

I'm going to show two common ways that this style is, in my opinion, wrongly applied.

Example 1: JavaScript

Hero example 1 HTML

Hero example 1 typescript

In the above example, we're:

  1. Rendering the data from the CMS as a dataset tag
  2. Reading from the element's dataset in JavaScript to find the opacity and base colour
  3. Setting the opacity and base colour on the banner.

Due to the reliance on Javascript on page load, we'll experience a brief "flash" or default styling; this is essentially the time it takes for DOMContentLoaded to load the JavaScript. To avoid this, we could add something like a skeleton loader... but if we already know the opacity and colour, why can't we have the banner ready immediately on page load?

It's a lot of JavaScript code to just initialise a style. Because of this, we're losing our CSS linting since we're doing a form of CSS-in-JS, and we lose the "cascading" property of CSS since we're in-lining all our CSS through JS.

In sumary, this implementation performs poorly, is difficult to read, and introduces unintended side effects.

Example 2: Inline CSS

Hero example 2

This example is much better performance wise. The styles are applied inline through the style attribute, meaning that the hero styles are available immediately on page load and no additional JavaScript is used; thus, there is no need for a skeleton loader. However, as we apply the styles inline, they still have a specificity that overrides all applied classes, leading to the loss of the "cascading" property of stylesheets. We also lose our CSS linting as we're in-lining styles, as well as repeating code and having poor readability.

At this point, although we've solved performance issues and most of the side effects, there's still plenty of room for improvement.

The solution (it's beautiful)

Now this, this is pretty. 

Solution HTMLSolution CSS

By using custom CSS variables, these variables are set on the element itself, meaning the scope of the variable can ONLY affect the element it's placed on and its descendants. We preserve the performance gain from the last solution, it's a JavaScript free solution as the variables are still applied server side and CSS immediately calculates the gradient. Additionally, by having actual CSS, it's automatically linted for code quality, as well as logical separation from JavaScript and HTML. It's exactly where it needs to be. We've introduced back the cascading property of stylesheets by not relying on inline styles, but instead the class that the custom variables are applied to. These can be overridden by higher specificity styles, exactly as developer would expect. You can now read this code and understand immediately what is happening, instead of having to drill down to understand the component before making changes. 

As with all solutions, there's some potential downsides. For very simple tasks, such as maybe setting a single value, additional css might be overkill for your solution, you get slight performance from setting the style inline as there's no CSS reliance, so don't over-engineer it. This solution really shines when you have a heap of styles being applied, or inherited styles that are needed by descendants, like palette styles or templates.

In conclusion...

In this post I've gone over a custom css variable solution to apply producer authored styles to components that brings performance upgrades, a cleaner codebase and logical meaning. I really hope this helps in some way, as this is such a huge part of my development process now, identifying areas that require more logical meaning and performance, and making it better. 

Over the past year, I've had the pleasure at my workplace of working with my company's design team and component library builders, Able. This solution was something that they intentionally guided me towards, and allowed me to discover myself. Thank you Paul and Tom for your mentorship, it's made me a much better developer and really lit a fire inside me to search for beautiful design philosophies such as this.

This was a long post, and mainly a brain dump as I don't want to lose this, but in short - don't always look for an easy, tacky solution, particularly with JavaScript. Although I focus mostly on CSS and raw HTML in this post, it's equally as applicable to frameworks like React. Your classes should do exactly what CSS is built for, styling, and your JavaScript should do what it's built for, logic. A developer shouldn't have to click through 3 different languages and 10 different elements to understand how, or what style is being applied. Good code, attention to detail and high quality is important, not just making it work.

FrontendCMSServer-Side Rendering
CSS Custom Properties, the right way to handle CMS-driven styles | Haolin Wu - Blog