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.
There are two parts of that definition I’d like to touch on:
- Single responsibility
- 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" />
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.
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;
}
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.
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.