Pass API Data to the Stylesheet with CSS Variables

Both on this website and at work I'm lucky to have pretty broad and varied brand colours palettes to work with. From a design perspective that helps keep things fresh, but it can be a little tricky when it comes to coding certain layouts. For example, consider a grid of company logos where you want to set a different background colour for each item. You could randomise the output easily enough with a little JavaScript, but you'd probably end up with some combinations that didn't look great. Instead, it would be best to allow a content editor to control the background colour directly, ideally via the CMS (we'll assume that's where the logos are coming from).

The initial setup remains fairly simple:

  1. Ingest the relevant "logo grid" API endpoint to an array;[1]
  2. Loop through the array and create a <figure> element for each logo;
  3. Assign each element a set class based on the colour value passed via the CMS.

That will definitely do what we want, but let's say you have 10 brand colours; that's a fair number of CSS classes! Sure, you could set these as global utility classes, but if we assume you're already setting your brand colours via CSS variables then that feels a lot like duplication. Wouldn't it be better to have a single logo class and dynamically change the background-color?

I seem to keep bumping into this same pattern and have ended up with a fairly standard response, so figured it was worth writing up for next time. Now it does come with a minor caveat: you need to make use of inline styles. But probably not in the way you think.

The trick isn't to set the background-color inline (though that would obviously work) but instead to redefine a CSS variable within an inline style element. Why do it that way? We'll come back to that, but think of CSS variables as an easier way to effectively pass data from the CMS to the stylesheets, which I think is pretty neat.

What you end up with is something like this:[2]

<figure style={{ "--logo-bg-colour": `var(--${logo.colour})` }}>
    <img src={logo.url} alt={logo.title} /> 
    <figcaption> {logo.caption} </figcaption>
</figure>

To break that down, you still generate the <figure> element in the same way, but you define a variable within the style attribute by using the format --css-variable-name. In the above example I'm then referencing a global CSS variable, defined on my :root element in the main stylesheet, which contains my brand colour. That way I don't need to do anything funky with converting CMS output into hex codes, I just ensure the CMS uses the same naming convention as my stylesheet and I'm done.

Then, in the stylesheet itself, I just have a class which defines the default value for the variable (in this case called logo-bg-colour) like so:

.logo-item { 
    --logo-bg-colour: var(--blue); 
    background-color: var(--logo-bg-colour);
}

So if the content editor doesn't set a colour value the logo will get a blue background, otherwise it will use whatever value is passed over the API. Heck, whilst this is using preset brand colours, there's nothing stopping you having a literal colour picker in your CMS and allowing them to choose any hex value at all!

Why not just use background colour directly?

We could set background-color on the inline rule instead of mucking around with CSS variables at all, so why not just do that? Well, two reasons:

  1. Because you're using CSS variables rather than inline styles, they can be overridden more easily;
  2. It maintains the separation of concerns as much as possible.

Okay, the second one is a bit pedantic, but what about that first point? Well, let's say you want every third logo in your grid to have a grey background. If you'd set the background colour inline then you'd be a bit stuck. There are a few options available:

  1. You can use the dreaded !important tag in your stylesheet;
  2. You could set a company rule whereby content editors had to set those logos as grey in the CMS;
  3. Or you could write a conditional clause into your loop so that every third logo ignored the CMS value and applied a grey background instead.

We obviously want to avoid 1 at any cost and 2 would likely be impractical (and fairly inefficient). So 3 it is, right? It's not ideal but it would work as intended. However, let's consider number 1 for a second longer; it works because it utilises the cascade. Inline styles typically trump everything, except for !important styles. But CSS variables offer us a way to escape that high-level cascade position.

In our above code, the cascade level of our variable is the rule on which it's defined/applied (here they happen to be the same). For us that means it's at "class" level, which is pretty low. Any increase in specificity above that level will override it, which is ideal in our use case:

.logo-item { 
    --logo-bg-colour: var(--blue); 
    background-color: var(--logo-bg-colour);
}

.logo-item:nth-of-type(3n) { 
    background-color: grey;
}

The second rule now has a specificity of 2 whilst the first is only 1. So, based on the cascade, every third .logo-item will have a grey background. That's not only a few lines less JavaScript to parse at build time, but also helps with that second reason: the seperation of concerns. As much of our visual code is taking place in the stylesheet as possible, where you'd expect to find it. It's a win-win!

Explore Other Articles

Older

Living That Journal Life

I've wanted to diversify the content on here for a while and give myself the green light to write more personal stuff. To that end, I've created a new journal section. I hope people enjoy it, but that really isn't the point 😉

Conversation

Want to take part?

Comments are powered by Webmentions; if you know what that means, do your thing 👍

Footnotes

  • <p>I keep running into the same problem: how to set a style attribute in the CMS and have that be dynamically rendered on the front-end, without relying on inline styles. Turns out it's a great use case for CSS variables!</p>
  • Murray Champernowne.
Article permalink