May 21, 2014

Single Responsibility

By Drew Barontini

When it comes to writing modular CSS, I try to stick to the Single Responsibility Principle wherever possible. This will limit the scope of your modules and allow you to easily build and combine them to create flexible style patterns.

Definition

In object-oriented programming, the single responsibility principle states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.

Wikipedia

There are two parts of that definition I’d like to touch on:

  1. Single responsibility
  2. Encapsulation

Single Responsibility

Obviously, as the name indicates, each module should have a single responsibility; what does the module do? For example, a thumb module might handle a thumbnail, such as a user’s avatar. Our module could look something like this:

.thumb {
  border-radius: 50%;
  display: block;
}

Our thumb module creates circle avatars for us to use on an image, like so:

<img class="thumb" src="user.jpg" alt="User Name"  />

Place Cage Our example thumb module.

That’s it. The module has one job, and it’s a very simple one.

Encapsulation

…and that responsibility should be entirely encapsulated by the class.

Now what about this idea of keeping our modules “encapsulated”? Encapsulation varies in definition as it applies to programming, but when it comes to CSS, I think of it as an information-hiding mechanism. We don’t want our modules talking to one another; they shouldn’t know or care about other modules. This allows them to stay flexible and fit into whatever context they need to be in. Let’s look at an example.

Example Sidebar Image

Let’s say that we have a sidebar block, and our default button doesn’t position itself the way we’d like in our sidebar. We want the button to be a sticky button that is positioned absolutely at the bottom of the sidebar.

<aside class="sidebar">
  <!-- ... -->
  <a class="btn" href="#">Button</a>
</aside>

We could just target the .btn class inside of our .sidebar and make the proper adjustments, like so:

.sidebar .btn {
  bottom: 1.25em;
  left: 1.25em;
  position: absolute;
  right: 1.25em;
}

Example Sidebar Image

That works, but it breaks the encapsulation of both our sidebar and button modules. The sidebar is telling the button how to behave and, if we’re adhering to the idea of encapsulation as we should be, the sidebar shouldn’t even know that the button exists. Why? Well, what if we were to alter the way the .btn behaves in some way that affects how it’s styled in the .sidebar? We’d have to look in a separate module to determine its styling, and adjust accordingly. No good.

Okay, let’s make a change.

<a class="btn sidebar-btn" href="#">Button</a>
.sidebar-btn {
  bottom: 1.25em;
  left: 1.25em;
  position: absolute;
  right: 1.25em;
}

We’ve added a new submodule class to the .sidebar module: .sidebar-btn. This submodule now handles the specific styling required of our .btn that is within the .sidebar. Moreover, using the module-submodule naming convention, we’re explicitly stating that the styling of this button is a dependent of the .sidebar class. Now, our two modules remain separate and encapsulated, and they don’t need to know anything about one another.

Example Sidebar Image

Uh oh! We have a problem. Since our .btn is set to position: absolute, it’s been removed from the layout context, which means that our new sidebar content is pushing underneath the button. Additionally, the .sidebar needs to set the positioning context for the .sidebar-btn with position: relative to make sure the .sidebar-btn is positioned to the .sidebar correctly.

.sidebar {
  padding-bottom: 5em;
  position: relative;
}

Okay, we’ve added some bottom padding and a position of relative to our .sidebar, and everything is good now. Well, not exactly. What if we don’t have a .sidebar-btn submodule in our .sidebar? There would be unnecessary padding that we don’t want at the bottom. We can fix this with a context class.

Context

.has-sidebar-btn {
  padding-bottom: 5em;
  position: relative;
}

Alright, now in our markup:

<aside class="sidebar has-sidebar-btn">
  <!-- ... -->
  <a class="btn sidebar-btn" href="#">Button</a>
</aside>

So what in the world are we doing? We’re creating a context class that will apply styling to a parent module based on a set of styles needed by the individual module. Now, only the .sidebar blocks with .has-sidebar-btn will have a bottom padding, as well as the appropriate postioning context.

We use the has- prefix for context classes.

Refactoring

As we’re building out our site, we see this pattern resurface. We have another container that needs its button to be sticky at the bottom. Rather than duplicate styles (keep it DRY!), we know that we need to do some refactoring. We’ll move the .sidebar-btn styling into the .btn module using a modifier, which is an alternate set of styling on an element.

.btn--sticky {
  bottom: 1.25em;
  left: 1.25em;
  position: absolute;
  right: 1.25em;
}

We use the double hyphen (--) to denote a modifier on a module.

Now we have our .btn--sticky styles that we can apply to this new element, and easily reuse across the site.

<div class="card">
  <!-- ... -->
  <a class="btn btn--sticky" href="#">Button</a>
</div>

Uh oh! What about our context class, .has-sidebar-btn? We need to move that into our .btn module class as well:

.has-btn--sticky {
  padding-bottom: 5em;
  position: relative;
}
<div class="card has-btn--sticky">
  <!-- ... -->
  <a class="btn btn--sticky" href="#">Button</a>
</div>

There we go! Now we have a flexible .btn--sticky module and a .has-btn--sticky context class to handle the styling it needs on a parent container. This allows us to easily apply the styling to any container that needs it.

That’s All, Folks

Hopefully that gives you a glimpse into how you can apply the Single Responsibility Principle to your CSS modules. Limit the scope of your modules, keep the styles small, and make sure your modules are properly encapsulated to ensure that your styles are both maintainable and predictable. Use submodules, modifiers, and context classes to make sure that your modules are nice and flex-y.

© 2019 Drew Barontini — Building products under Drewbio, LLC