Lesson 4: Variations
In the context of CSS and front-end development, variations (sometimes referred to as mutations), are the ways in which elements change slightly depending on their context. They’re how we adapt UI to behave in different ways.
In this lesson we’re going to cover:
- What constitutes a variation.
- Extracting variations from existing styles.
- Strategies for managing variations.
What constitutes a variation? #
A variation is comprised of three main ingredients:
- A base set of styles that define how an element looks by default.
- Variation styles which override a small subset of the base styles.
- A “hook” that causes variation styles to kick in.
Ancestor-controlled variations #
Now we know how to identify variations, let’s start looking at a few strategies for sensibly managing them.
Say we want to display two buttons on a page; we want them to look the same, except the second one should be red. If we decided to take an “ancestor-controlled” approach, we might end up with something like this:
We have a ruleset that defines the default styles of a
.button, and also a ruleset which changes the background colour of
.button elements when they’re nested inside a
.container. The ancestor element acts as the hook that controls the variation in elements nested inside it.
This method is commonly employed to manage variations at a page level. For example, adding a
.home-page class to the
<body> element, and using it as the ancestor like in the above example could be used to vary elements specifically on the home page.
Lets look at some of the strengths of this strategy:
- It’s quite intuitive and simple to understand.
- It’s usually easy to introduce into a small codebase.
- Often you won’t have to edit your HTML to introduce ancestor-controlled variations. You just need to pick an appropriate ancestor to control the variation.
Although quite simple, this solution has a few drawbacks:
- It couples our CSS to the structure of our HTML. In other words, if we change how our HTML is laid out there’s a chance that the variation styles might break. Using the above
.container .buttonexample, if we pulled the
.buttonelement out of the
.containerancestor element, it suddenly doesn’t work. As the author of the styles, we may be aware of the impact that that would have, but a teammate who comes to change the HTML might not do.
- Every time we add an ancestor class to a selector, it increases it’s specificity. Like we established in Lesson 1, minimising specificity and using load order to control overrides is more likely to result in a healthy CSS codebase in the long-term.
- It makes our UI components less modular and well-encapsulated. If we’re looking to create reusable, self-contained UI components, then it’s not a great idea to hand over control to ancestor elements. Ideally, a component should control the styles that affects it, not the context it’s placed in.
- Styles become less portable. We’re not going to be able place elements under a different ancestor and preserve the variations styles all that easily doing things this way.
- We may end up styling things we didn’t mean to. For example, the above
.container .buttonselector will style any and all
.buttonelements placed inside
.containerhas many descendants, then there’s a greater chance of another
.buttonelement being affected. That might be a good thing and something we’re deliberately trying to do, or it might be something we end up doing accidentally which breaks our design.
Class-controlled variations #
Class-controlled variations are where we add “modifier” or “mutator” classes to our elements to trigger the visual variations.
- You have to deliberately add the class to trigger the variation. This means it’s very difficult to accidentally style elements with the variation styles, since you need to take the step of adding it to the markup.
- TODO: add more advantages
- TODO: add more disadvantages
Extracting variations #
This solution does the job, however it isn’t ideal as most of the styles are duplicated across the two classes. On closer inspection, the only thing that varies between them is their
This is where variation styles can help:
- First, we need to decide what our element should look like by default. Let’s say, in this scenario, our button should be white.
- Double check the naming of your elements still makes sense. Let’s rename
.white-buttonto a more generic
.buttonclass, as it’s no longer the case the button will always be white.
- Then we extract the subset of rules that vary into their own ruleset. We’ll grab the
background-color: red;and put it in a new
- We then hook the new variation onto the elements that we’d like to vary. We change the second button’s
.red-buttonclass over to the default
.buttonclass and also add the
.red-variationclass to hook up the variation.
- Next, we clean up any redundant styles that aren’t needed anymore. The
.red-buttonclass can now be deleted. Generally, it’s important to be careful here; make sure you’ve moved over all impacted elements over to your variation class before doing this.
Voila! We’ve taken two rulesets, which were almost identical, extracted what varied between them, and DRY-ed up our styles considerably. What’s more, we’ve made the button styles more generic, whilst maintaining the flexibility to customise the background colour should we wish.
- Lesson 1: Specificity
- Lesson 2: Scope
- Lesson 3: Naming
- Lesson 4: Variations ← You're here
- Lesson: BEM Coming Soon
- Lesson: SCSS Coming Soon
positionDeep Dive Coming Soon
- Lesson: Reusability Coming Soon
- Lesson: Units Coming Soon
- Lesson: Media Queries Coming Soon
displayDeep Dive Coming Soon
- Lesson: Flexbox Coming Soon
- Lesson: Grid Coming Soon
- Lesson: Inheritance Coming Soon
- Lesson: Layout vs Aesthetics Coming Soon
- Lesson: Fixing Bad CSS Coming Soon
- Lesson: Composability Coming Soon
- Lesson: File Organisation Coming Soon
- Lesson: Accessibility Coming Soon