One of the things that front-end and back-end developers alike loathe is the dreaded scenario where a content author wants to be able to add their own styles to content. No, not set a skin or theme to the content. They insist that an <h1>
be any of 16+ million colors. The <img> should have varying levels of opacity. Or maybe they need to adjust the font-size
in accordance with the weather.
So, what is a sane way to deal with the content-author’s desire to have free-form styles for a piece of content? Is there an approach that won’t send the front-end developer into comic-sans-powered convulsions, or the back-end developer into a fit of bourbon-fueled rage? Yes! With the power of CSS variables, we can solve a common CMS problem in a way that doesn’t end in AA-meetings and apology letters.
How did we solve this problem in the past?
In the past, we dealt with the issue of content-managed free-form styles with the following approach:
- Make the field a plain-text field in the CMS (If this is Tridion, It’s a piece of cake to do)
- Make a plain text field where the content author entered in the value of the CSS property she wanted to control
- In the front-end, either:
- Output the style in a
style=""
attribute on a specific element - Output the style in a
<style>
block somewhere within that component template
- Output the style in a
What content-managed Styles usually looked like
In the end, the output of this component template might look something like this:
<header class="header" style="background-color: #993333;"><img class="header__media header__media--img" src="http://placebear.com/1500/500" /> <div class="header__hgroup"> <h1 class="header__title" style="color: #daddad;">This is a title</h1> <h2 class="header__subtitle" style="color: #454545;">This is a smaller title</h2> </div> <div class="header__rtf rtf"> Here's a really smooth description and text you can use if you want </div> </header>
Your alternative, as I mentioned, is that <style>
block, which might look nominally cleaner, but requires the template developer to get a TCM ID (or some other unique identifier) and output it on the component:
<!-- Add an id to the outermost container, generated by CMS--> <header id="tcm-123" class="header"> <!--This block generated by a CMS --> <style type="text/css"> #tcm-123 { background-color: #993333; } #tcm-123 .header__title { color: #daddad; } #tcm-123 .header__subtitle { color: #454545; } </style> <img class="header__media header__media--img" src="http://placebear.com/1500/500" /> <div class="header__hgroup"> <h1 class="header__title">This is a title</h1> <h2 class="header__subtitle">This is a smaller title</h2> </div> <div class="header__rtf rtf"> Here's a really smooth description and text you can use if you want </div> </header>
What content-managed styles usually felt like
Sadness, disappointment, and regret —kind of all at once. ¿Dissadregretment?
Not only do we not feel good doing it, we know there’s major pitfalls: What if we want to content-manage other styles for fields? What if multiple fields should use the same style? What if the front-end developer and back-end developer get hit by a bus (quite possibly en-route to a support group on account of the dissadregretment).
Every time we need to add a new style-managed field, we have to update the template. It’s possible to set multiple fields to use the same style, but the front-end developer has to know that up-front, because refactoring that code is a real pain. The way this approach works, it requires both front-end and back-end to constantly be synchronizing with each other on how to solve this problem.
We need a better way.
A Better Way
CSS is now a living spec. There’s really no such thing as CSS3; it’s a whole bunch of different modules that get developed, improved, and released into browsers. One such module is CSS Variables.
First, a quick illustration:
.header { --headerBGColor: #333; /*Variable assignment*/ background-color: var(--headerBGColor); /*Variable Usage*/ }
Breaking down the syntax
There’s not much to the syntax:
--[variableName]: [value of variable]
var(--[variableName])
That’s it. --
tells the browser that this is a variable, and var()
is how you use it.
Breaking down the mechanics
CSS Variables behave exactly like CSS properties. What that means is that they are part of the cascade; they’re inherited, and they can be overwritten. If you’re a back-end developer, that might sound a bit odd. So I’ll illustrate with some programming terms:
First we’ll establish the markup:
<!--lexical scope--> <header class="header"><!-- has access to parent scope, also starts new lexical scope --> <h1 class="header__title">I'm a title</h1> </header><!-- /end of lexical scope --> <!-- lexical scope--> <article class="article"> </article> <!-- /end of lexical scope -->
And now let’s jump to some CSS:
.header, .article { min-height: 2em; } .header { --headerTextColor: #cabbac; /*variable is in the lexical scope of .header*/ color: var(--headerTextColor); /* accessible because it's in scope */ } .header__title { color: var(--headerTextColor); /* accessible because .header__title is in the scope of .header */ } .article { color: var(--headerTextcolor); /*ERROR: variable not accessible because this is a new scope*/ }
So this is pretty cool, right‽ CSS now offers scoped variables!
What does this means for content management
This is kind of a game changer for content management. The ability to create scoped variables means that there is a new way forward when front-end and back-end are up against the wall in providing content-authorable styles.
We can use CSS Variables to provide a cleaner, more flexible way for a back-end template to output content-author values into the front-end.
Go back to the original markup
Let’s take a look at the original, unmodified markup:
<header class="header"> <img class="header__media header__media--img" src="http://placebear.com/1500/500" /> <div class="header__hgroup"> <h1 class="header__title">This is a title,</h1> <h2 class="header__subtitle">This is a smaller title</h2> </div> <div class="header__rtf rtf"> Here's a really smooth description. </div> </header>
Write the right variables in the right scope
In our stylesheet, we write the variables in their own ruleset, completely separate from any other rules.
.header { --headerBGColor: #333; --headerTextColor: #cabbac; --headerTitleColor: var(--headerTextColor); --headerSubtitleColor: var(--headerTextColor); --headerRTFColor: var(--headerTextColor); }
We’ve created a “master” variable, and we’ve set that master variable to be the value of our other variables. This is the same as setting a color
on the .header
and then setting color:inherit
on the children. The difference being that we can now have a mechanism to “uninherit” without invoking a specificity change; we can change this color by simply changing the variable’s value instead of writing a new CSS rule.
Ok, so we now have variables, and they are permanently scoped to the element (and the CMS component).
Next, we use the variables in our stylesheet
We write rulesets that are separate from any other rulesets, whose only job is to apply these content-manageable styles:
.header { background-color: var(--headerBGColor); } .header__title { color: var(--headerTitleColor); } .header__subtitle { color: var(--headerSubtitleColor); } .header__rtf, .header__rtf * { color: var(--headerRTFColor); }
Sweet, our front-end is now set up to be configurable from the CMS!
Going back to what the component template
Look at what the component template has to do now! It’s… a lot more simple:
<header class="header"><style type="text/css"> .header { --headerBGColor: #993333; /*Field: backgroundColor*/ --headerTitleColor: #daddad; /*Field: titleColor*/ --headerSubtitleColor: #454545; /*Field: subtitleColor */ --headerRTFColor: var(--headerTextColor); /*NO FIELD*/ } </style> <img class="header__media header__media--img" src="http://placebear.com/1500/500" /> <div class="header__hgroup"> <h1 class="header__title">This is a title</h1> <h2 class="header__subtitle">This is a smaller title</h2> </div> <div class="header__rtf rtf"> Here's a really smooth description and text you can use if you want </div> </header>
How has this improved?
- No need to get and output the TCM ID from Tridion (or whatever the Unique ID is for your CMS)
- You don’t have to write a long
<style>
with new rulesets - If all text colors need to be managed by one field, that means removing three variables and adding one (
--headerTextColor: #c0ffee
) - If another field (like
--headerRTFColor
) needs to be style-managed, that doesn’t even amount to adding a new line to the template
The one known ‘gotcha’
Browser support for CSS Variables is very good. The only browser that doesn’t support CSS Variables is Internet Explorer 11.
Give me a minute while I find my “surprised” face.
:|
Don’t let this be a deterrent! There are a lot of “polyfills” (tools that will fill in a feature where it isn’t supported). There are, by my count, at least three CSS Variable polyfills:
- Aaron Barker’s Codepen example
- Jonathon Ong’s Polyfill
- Sah Assar’s CSS Polyfill, which might be able to work in IE8 if it got some lovin’
Demo Or it didn’t happen
I haven’t yet had a chance to implement this with a client. But I do have a static example of CSS Variables in action.
The HTML comments and the CSS comments illustrate exactly how I’d recommend this be implemented today.
BTW, a big hat tip goes to Tommy Hodgins (@innovati)for turning me on to CSS Variables and theorizing with me.