Chapter 5. CSS

One of the greatest things about this industry is that we can freely sit down with fellow developers and have a cup of coffee. That might not seem like a big deal, but let me tell you, it is! We work in an industry based on open standards, open source software, open information, and open learning. The tools and techniques we use may be changing at an incredibly fast pace, but working in such an open industry is the key reason we’re able to keep up with them. Now this may surprise you, but there are other industries where you would never, ever sit down with a fellow practitioner to talk shop, unless they were paying you to do it. In these industries, every piece of knowledge, every trick, every preset, every macro, every document, and every shortcut is up for sale, and the last thing you’d want to do is sit down with a potential competitor and freely trade that information.

Now compare this to the Web. We thrive on sharing knowledge. We publish blogs, record video tutorials, create public code repos, write on Stack Overflow, respond to questions on IRC, and distribute CodePens, Gists, Sassmeisters, JSbins, and Pastebins, all so that others can learn the things that we know. Getting a cup of coffee and discussing your views on current web practices and new CSS frameworks is the most basic expression of how we share knowledge and how we learn in this industry.

So yeah! We work in an industry where inviting an associate out for a cup of coffee is not only acceptable, but also a valuable practice! These cups of coffee can lead to learning new things, new business connections, new jobs, and even great friends. Suffice it to say, many years of talking shop over cups of coffee, beer, tea, or kombucha has led me to believe that the simple social interface of sharing a beverage is one of the greatest assets we have in this industry.

The great thing about having consistent caffeine- or alcohol-fueled conversations with other developers is that we always have a pulse on what people are excited about, and in what direction they are headed. This is important to me not because I depend on others to know what to do next, but rather because I am able to validate the things I am learning and the discoveries I myself am making. Every year I look back at how my approach to CSS has evolved. So whether my newest obsession is preprocessors, build tools, style guide–driven design, or component-based design systems, I’m always excited to find that others are coming to the exact same discoveries.

The CSS I am writing today looks nothing like what I was writing even three years ago. After each site build, I learn something new and try to apply that to my next project. It’s a very organic process of gathering the things I’ve learned with the things I’ve read, and trying to apply an improved approach to the unique problems I face. Each iteration brings a marked improvement in my technique, and my understanding of a scalable and maintainable design system.

Along with each iteration are the excited conversations with various coworkers, industry colleagues, or random conference goers. With each of those conversations, I’m consistently amazed that I was not the only person to have a euphoric epiphany about using data-attributes, or opt-in styles to handle my component variations.

As a frontend architect, you might not need to know every tiny CSS bug in some obscure version of Opera mini, but you do need to understand the major trends in CSS, and be able to put together a plan that will set your team up for success. If you haven’t been keeping up on the current CSS trends, or your euphoric epiphanies aren’t coming as fast as you wish they would, try grabbing a few more cups of coffee with fellow developers, or spend a bit more time doing the “social track” at your next conference. But before you do that, read the following sections so you can get caught up on where we were in CSS a few years ago, why it didn’t work, and where we are now.

Specificity Wars and the Pains of Inheritance

It wasn’t that many years ago when we were still dealing with the 100% dynamic or 100% static markup that I described in the previous chapter. Regardless of the side of the spectrum we were on, the effect on our CSS was that we almost always started from a global scope and worked our way down, getting more specific with each new level of the cascade. We’d start with general styles on each element, like our header and paragraph tags, and then apply specific styles to the elements inside of the various sections of our page:

<body>
  <div class="main">
    <h2>I'm a Header</h2>
  </div>
  <div id="sidebar">
    <h2>I'm a Sidebar Header</h2>
  </div>
</body>

<style>
h2 {
  font-size: 24px;
  color: red;
}

#sidebar h2 {
  font-size: 20px;
  background: red;
  color: white;
}
</style>

Now every H2 is red, except for the sidebar where the H2 is white with a red background.

This concept was easy to understand and made a ton of sense if you were coming from a print background. Every time you put an H2 in the sidebar, it would be styled the same—well, that is until we came across a calendar widget in the sidebar that should be using the original header colors and no background. But that’s OK! We could just add another class and overwrite the offending sidebar styles, right?

<body>
  <div id="main">
    <h2>I'm a Header</h2>
  </div>
  <div id="sidebar">
    <h2>I'm a Sidebar Header</h2>
    <div class="calendar">
      <h2>I'm a Calendar Header</h2>
    </div>
  </div>
</body>

<style>
h2 {
  font-size: 24px;
  color: red;
}

#sidebar h2 {
  font-size: 20px;
  background: red;
  color: white;
}

#sidebar .calendar h2 {
  background: none;
  color: red;
}
</style>

The issues with this approach are numerous:

Specificity
Whether you’re dealing with ID tags or just long selectors, overwriting a selector always requires some attention to the level of specificity.
Resetting colors
To get back to the original H2 color we have to specify it again, as well as overwrite the background.
Location dependence
Now our calendar styles are dependent on being in the sidebar. Move the calendar to the footer and the header size will change.
Multiple inheritance
This single H2 is now getting styles from three different sources. This means we can’t change the body or sidebar H2s without affecting the calendar.
Further nesting
The calendar widget might have other H2s inside of individual calendar entries. Those H2s will need an even more specific selector, and will now be dependent on four sources for its styles.

A Modern, Modular Approach

Our discussion of HTML in Chapter 4 foreshadowed a few of the modern, modular tenets that the majority of frameworks are employing to deal with the problems we saw in the approach just described. While OOCSS, SMACSS, and BEM have differing opinions about the exact markup to use, each offers advice about how to write your CSS that will be valuable regardless of the approach you take. Let’s take a quick look at a few of those key tenets and how they help solve the problems we had before.

OOCSS brings the idea of separating container from content, where we learn to stop using location as a style qualifier. There is nothing wrong with having a sidebar on your site, and to style that sidebar in whatever way you’d like, but the influence of those sidebar styles stops once you get down to the contents of the sidebar. #sidebar h2 means that every H2 element placed in the sidebar is going to have to either accept or fight off the styles applied by that selector. .my-sidebar-widget-heading means that a single heading in the sidebar can opt in to that style, while other modules in the sidebar have headings that are left completely untouched.

SMACSS brings us the idea of separating our layout and our components into completely different folders, further dividing the role of the sidebar and the role of the calendar module. Now that we have defined the sidebar’s role to that of just layout, we don’t even allow element styles inside of that Sass partial. If you are going to place something in the sidebar and want to have it styled, that element needs to be part of a component, and defined in the component folder.

BEM, while not necessarily a CSS methodology, teaches us the value of having a single source of truth for every class used in our markup. Instead of classes that ebb and flow depending on their context or proximity to other selectors, each BEM class can be traced back to a single set of CSS properties unique to that selector:

<body>
  <div class="main">
    <h2 class="content__title>"I'm a Header"</h2>
  </div>
  <div class="sidebar">
    <h2 class="content__title--reversed>
"I'm a Sidebar Header"
    </h2>
    <div class="calendar">
      <h2 class="calendar__title>"I'm a Calendar Header"</h2>
    </div>
  </div>
</body>

<style>

/* Components folder */
.content__title {
  font-size: 24px;
  color: red;
}

.content__title--reversed {
  font-size: 20px;
  background: red;
  color: white;
}

.calendar__title {
  font-size: 20px;
  color: red;
}

/* Layout folder */
.main {
  float: left;
  ...
}
.sidebar {
  float: right;
  ...
}
</style>

The issues we had with our original location-based styles are fixed:

Specificity
Changing your IDs to classes is a good start in stopping the specificity wars, and flattening every selector’s specificity to “1” allows us to stop using the specificity “winner” to determine the applied styles.
Resetting colors
Even better than lowering specificity is using using a single selector to apply styles to each element. This way your module styles never have to fight with your sidebar or site-wide styles.
Location dependence
With no styles scoped to a single layout, we don’t have to worry about what happens to the calendar when we move it from the sidebar to the main section.
Multiple inheritance
With each of the three headings getting its own unique class, we are free to change any of them without fear of affecting the others. If you want to make changes across multiple selectors, look into preprocessor variables, mixins, or extends to handle that for you.
Further nesting
Even at the calendar level, we still haven’t applied a single style to our H2 elements. There’s no need to override base, sidebar, or calendar header styles before styling new H2s in our calendar module.

Other Principles to Help You Along the Way

Single Responsibility Principle

Throughout my time at Red Hat, I found that a few other things helped me immensely when architecting our approach to writing CSS.

The single responsibility principle states that everything you create should be created for a single, focused reason. The styles you apply to a given selector should be created for a single purpose, and should do that single purpose extremely well.

This doesn’t mean you should have individual classes for padding-10, font-size-20, and color-green. The single purpose we’re talking about is not the styles that they apply, but rather where the styles are applied. Let’s look at the following example:

<div class="calendar">
  <h2 class="primary-header">This Is a Calendar Header</h2>
</div>

<div class="blog">
  <h2 class="primary-header">This Is a Blog Header</h2>
</div>

.primary-header {
  color: red;
  font-size: 2em;
}

While the preceding example appears to be quite efficient, it has clearly broken our single responsibility principle. The class of .primary-header is being applied to more than one, unrelated element on the page. The “responsibility” of the primary-header is now to style both the calendar header and the blog header. This means that any change to the blog header will also affect the calendar header unless you do the following:

<div class="calendar">
  <h2 class="primary-header">This Is a Calendar Header</h2>
</div>

<div class="blog">
  <h2 class="primary-header">This Is a Blog Header</h2>
</div>

.primary-header {
  color: red;
  font-size: 2em;
}

.blog .primary-header {
  font-size: 2.4em;
}

This approach, while effective in the short term, brings us back to several of the problems we had at the beginning of the chapter. This new header style is now location dependent, has multiple inheritances, and introduces a game of “winning specificity.”

A much more sustainable approach to this problem is to allow each class to have a single, focused responsibility:

<div class="calendar">
  <h2 class="calendar-header">This Is a Calendar Header</h2>
</div>

<div class="blog">
  <h2 class="blog-header">This Is a Blog Header</h2>
</div>

.calendar-header {
  color: red;
  font-size: 2em;
}

.blog-header {
  color: red;
  font-size: 2.4em;
}

While it’s true that this approach can cause some duplication (declaring the color red twice), the gains in sustainability greatly outweigh any duplicated code. Not only will this additional code be a trivial increase in page weight (gzip loves repeated content), but there is no guarantee that the blog header will remain red, and enforcing the single responsibility principle throughout your project will ensure that further changes to the blog header are done with little work or possible regressions.

Single Source of Truth

The single source of truth approach takes the single responsibility theory to the next level in that not only is a class created for a single purpose, but also the styles applied to that class come from one single source. In a modular design, the design of any component must be determined by the component itself, and never imposed on it by a parent class. Let’s take a look at this in action:

<div class="blog">
  <h2 class="blog-header">This Is a Blog Header</h2>
  ...
  <div class="calendar">
    <h2 class="calendar-header">This Is a Calendar Header</h2>
  </div>
</div>
/* calendar.css */
.calendar-header {
  color: red;
  font-size: 2em;
}

/* blog.css */
.blog-header {
  color: red;
  font-size: 2.4em;
}

.blog .calendar-header {
  font-size: 1.6em;
}

The intention of these styles is to decrease the size of the calendar header when it is inside of a blog article. From a design standpoint, that might make perfect sense, and what you end up with is a calendar component that changes appearance depending on where it is placed. This conditional styling is what I like to call a “context,” and is something I use quite extensively throughout my design systems.

The main problem with this approach is that the decreased font size originates from the blog component, and not from within the calendar’s single source of truth, the calendar component file. In this case, the truth is scattered across multiple component files. The problem with having multiple sources of truth is that it makes it very difficult to anticipate how a component is going to look placed on the page. To mitigate this problem, I suggest moving the contextual style into the calendar module code:

<div class="blog">
  <h2 class="blog-header">This Is a Blog Header</h2>
  ...
  <div class="calendar">
    <h2 class="calendar-header">This Is a Calendar Header</h2>
  </div>
</div>
/* calendar.css */
.calendar-header {
  color: red;
  font-size: 2em;
}

.blog .calendar-header {
  font-size: 1.6em;
}

/* blog.css */
.blog-header {
  color: red;
  font-size: 2.4em;
}

With this approach, we are still able to decrease the size of the calendar header when it is inside of a blog article, but by placing all of the calendar-header contextual styles into the calendar file, we can see all of the possible variations of the calendar header in a single location. This makes updating the calendar module easier (as we know all of the conditions in which it might change), and allows us to create proper test coverage for each of the variations.

Component Modifiers

While the single source of truth approach does improve clarity by placing the context inside of the component file, it can become difficult to keep track of several different contexts. If you find that the calendar header is smaller inside of dozens of different contexts, it might be time to switch from contextual modifiers to modifier classes.

Component modifiers (also called skins or subcomponents, depending on the methodology you subscribe to) allow you to create multiple variations of a component to be used in various circumstances. They work in a very similar way to contexts, but the qualifying class is part of the component rather than a parent of the component:

<div class="blog">
  <h2 class="blog-header">This Is a Blog Header</h2>
  ...
  <div class="calendar calendar--nested">
    <h2 class="calendar-header">This Is a Calendar Header</h2>
  </div>
</div>
/* calendar.css */
.calendar-header {
  color: red;
  font-size: 2em;
}

.calendar--nested .calendar-header {
  font-size: 1.6em;
}

/* blog.css */
.blog-header {
  color: red;
  font-size: 2.4em;
}

In the preceding example, we have created a calendar--nested modifier using the traditional BEM syntax. This class by itself does nothing, but when it is applied to the calendar component, the elements inside of the component can use it as a local context and change their appearance.

With this approach, we can use this modified calendar skin whenever we want, and we will get that smaller header (along with other changes if we want). This keeps all of your component variations in a single file, and allows you to use them (or not use them) whenever you need, not making them dependent on some random parent class.

Conclusion

This chapter covered just a small sample of the different CSS techniques that you might use to create a maintainable codebase, including the following:

  • Separating container from content
  • Defining roles and responsibilities of layouts versus components
  • Using single, flat selectors on all of your markup
  • Using other principles, such as the single responsibility principle, single source of truth, and content modifiers

The suggestions outlined here and in the previous chapter could benefit projects of any shape or size, but it is ultimately up to you and your team to decide how you are going to write your CSS. The only requirement I have is that you have these conversations, that you set expectations, and that you hold one another accountable during code review. If you do this, you will have cemented the code pillar of your frontend architecture and set your team up for success.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.152.139