“Every line of code should appear to be written by a single person, no matter the number of contributors.” -@mdo
- Keep stylesheets maintainable
- Keep code transparent, sane, and readable
- Keep stylesheets scalable
- Minimize side effects
Block: Unique, meaningful names for a logical unit of style. Avoid excessive shorthand.
- Good:
.alert-box
or.recents-intro
or.button
- Bad:
.feature
or.content
or.button
Element: styles that only apply to children of a block. Elements can also be blocks themselves. Class name is a concatenation of the block name, two underscores and the element name. Examples:
.alert-box__close
.expanding-section__section
Modifier: override or extend the base styles of a block or element with modifier styles. Class name is a concatenation of the block (or element) name, two hyphens and the modifier name. Examples:
.alert-box--success
.expanding-section--expanded
Absolutely every class in a project fits into one of these categories, which is why BEM is so great—it’s incredibly simple and straightforward.
The point of BEM is to give a lot more transparency and clarity in your markup. BEM tells developers how classes relate to each other, which is particularly useful in complex or deep pieces of DOM. For example, if I were to ask you to delete all of the user-related classes in the following chunk of HTML, which ones would you get rid of?
<div class="media user premium">
<img src="" alt="" class="img photo avatar" />
<p class="body bio">...</p>
</div>
Well we’d definitely start with user, but anything beyond that would have to be a guess, educated or otherwise. However, if we rewrote it with BEM:
<div class="media user user--premium">
<img src="" alt="" class="media__img user__photo avatar" />
<p class="media__body user__bio">...</p>
</div>
Here we can instantly see that user, user--premium
, user__photo
, and user__bio
are all related to each other. We can also see that media
, media__img
, and media__body
are related, and that avatar
is just a lone Block on its own with no Elements or Modifiers.
This level of detail from our classes alone is great! It allows us to make much safer and more informed decisions about what things do and how we can use, reuse, change, or remove them.
A modifier should always be included with the base block.
- Good:
<div class="my-block my-block--modifier">
- Bad:
<div class="my-block--modifier">
Don't create elements inside elements. If you find yourself needing this, consider converting your element into a block.
- Bad:
.alert-box__close__button
If you're ever confused, ask for help in the Front-end HipChat room (or ask a friend—we're all in this together).
- Use BEM-based naming for your class selectors
- When using modifier classes, always require the base/unmodified class is present
- Don't use Sass’s nesting to manage BEM selectors. It makes those selectors non-searchable. Prefer something like this:
.block { [...] } .block--modifier { text-align: center; } .block__element { color: red; } .block__element--modifier { color: blue; }
The one thing missing from BEM is that it only tells us what classes to in relative terms, as in, how classes are related to each other. They don’t really give us any idea of how things behave, act, or should be implemented in a global and non-relative sense.
Thus, we prefix every class in a codebase with a certain string in order to explain to developers what kind of job it does. This Hungarian notation-like naming allows us to ascertain exactly what kind of job a class might have, how and where we might be able to reuse it (if at all), whether or not we can modify it, and much more.
There are a few reserved namespaces for classes to provide common and globally-available abstractions.
.o-
: Signify that something is a CSS Object, and that it may be used in any number of unrelated contexts to the one you can currently see it in. Making modifications to these types of class could potentially have knock-on effects in a lot of other unrelated places. Tread carefully..c-
: Signify that something is a Component. This is a concrete, implementation-specific piece of UI. All of the changes you make to its styles should be detectable in the context you’re currently looking at. Modifying these styles should be safe and have no side effects. Components are designed pieces of UI—like buttons, inputs, modals, and banners..u-
: Signify that this class is a Utility class. It has a very specific role (often providing only one declaration) and should not be bound onto or changed. It can be reused and is not tied to any specific piece of UI. Things like floating elements, trimming margins, etc. You will probably recognise this namespace from libraries and methodologies like SUIT..is-
,.has-
: Signify that the piece of UI in question is currently styled a certain way because of a state or condition. This stateful namespace is gorgeous, and comes from SMACSS. It tells us that the DOM currently has a temporary, optional, or short-lived style applied to it due to a certain state being invoked.._
: Signify that this class is the worst of the worst—a hack! Sometimes, although incredibly rarely, we need to add a class in our markup in order to force something to work. If we do this, we need to let others know that this class is less than ideal, and hopefully temporary (i.e. do not bind onto this)..js-
: Signify that this piece of the DOM has some behaviour acting upon it, and that JavaScript binds onto it to provide that behaviour. If you’re not a developer working with JavaScript, leave these well alone.
(This list is to get us started. In the future, we may find the need for another type of namespace.)
From our previous example:
<div class="media user user--premium">
<img src="" alt="" class="media__img user__photo avatar" />
<p class="media__body user__bio">...</p>
</div>
We now have:
<div class="o-media c-user c-user--premium">
<img src="" alt="" class="o-media__img c-user__photo c-avatar" />
<p class="o-media__body c-user__bio">...</p>
</div>
- Avoid using HTML tags in CSS selectors.
- E.g. Prefer
.o-modal {}
overdiv.o-modal {}
. - Always prefer using a class over HTML tags (with some exceptions like CSS resets and base styles).
- E.g. Prefer
For example:
div.sidebar .login-box a.btn span {
}
In this compound selector, the subject is span
, and the conditions are IF (inside .btn) AND IF (on a) AND IF (inside .login-box) AND IF (inside .sidebar) AND IF (on div)
.
That is to say, every component part of a selector is an if
statement—something that needs to be satisfied (or not) before the selector will match.
This subtle shift in the way we look at how we write our selectors can have a huge impact on their quality. Would we really ever write (pseudo code):
@if exists(span) {
@if is-inside(.btn) {
@if is-on(a) {
@if is-inside(.login-box) {
@if is-inside(.sidebar) {
@if is-on(div) {
// Do this
}
}
}
}
}
}
Probably not. That seems so indirect and convoluted. We’d probably just do this:
@if exists(.btn-text) {
// Do this
}
Every time we nest or qualify a selector, we are adding another if statement to it. This in turn increases its Cyclomatic Complexity.
- Don’t use IDs in selectors.
#header
is overly specific compared to, for example.header
and is much harder to override.- Read more about the headaches associated with IDs in CSS here.
- Don’t
!important
.- Ever.
- If you must, leave a comment, and prioritize resolving specificity issues before resorting to
!important
. !important
greatly increases the power of a CSS rule, making it extremely tough to override in the future. It’s only possible to override with another!important
rule later in the cascade.
Any CSS that unsets styles (apart from in a reset) should start ringing alarm bells right away. The very nature of CSS is that things will, well, cascade and inherit from things defined previously. Rulesets should only ever inherit and add to previous ones, never undo.
Any CSS declarations like these:
border-bottom: none;
padding: 0;
float: none;
margin-left: 0;
…are typically bad news. If you are having to remove borders, you probably applied them too early. This is really hard to explain so I’ll go with a simple example:
h2 {
font-size: 2em;
margin-bottom: 0.5em;
padding-bottom: 0.5em;
border-bottom: 1px solid #ccc;
}
Here we’re giving all h2s our usual font-size and margin for spacing, but also a bit of padding and a keyline on the bottom edge to visually separate it from the next element on the page. But, perhaps we have a circumstance in which we don’t want that keyline, perhaps we have a situation where we want a h2 to not have that border and padding. We’d likely end up with something like this:
h2 {
font-size: 2em;
margin-bottom: 0.5em;
padding-bottom: 0.5em;
border-bottom: 1px solid #ccc;
}
.no-border {
padding-bottom: 0;
border-bottom: none;
}
Here we have ten lines of CSS and one ugly class name. What would have been better is this:
h2 {
font-size: 2em;
margin-bottom: 0.5em;
}
.headline {
padding-bottom: 0.5em;
border-bottom: 1px solid #ccc;
}
Here we have eight lines of CSS, no undoing anything, and a nice, sensible class name.
As you go down a stylesheet you should only ever be adding styles, not taking away. If you find you are having to undo styling as you go down your document the chances are you jumped the gun and started adding too much too soon.
Imagine CSS like this over tens of thousands of lines… that’s a lot of bloat and a lot of unnecessary undoing. Peg things onto simpler things that came before it, do not start too complex and risk having to undo your work later on; you’ll end up writing more CSS to achieve less styling.
Sometimes you'll have to override styles (especially in old code). This means, if the pain of rewriting the old code is greater than just overriding it, use your best judgment. The goal is to make the styles additive and lean.
- Two spaces for indenting code
- Put spaces after
:
in property declarations- E.g.
color: red;
instead ofcolor:red;
- E.g.
- Put spaces before
{
in rule declarations- E.g.
.o-modal {
instead of.o-modal{
- E.g.
- Write your CSS one line per rule
- Add a line break after
}
closing rule declarations - When grouping selectors, keep individual selectors on a single line
- Place closing braces
}
on a new line - No trailing white-space at the ends of lines
- Trim excess whitespace
- All selectors are lower case, hyphen separated aka “spinal case” eg.
.my-class-name
- Always prefer Sass’s double-slash
//
commenting, even for block comments - Avoid specifying units for zero values, e.g.
margin: 0;
instead ofmargin: 0px;
- Always add a semicolon to the end of a property/value rule
- Use leading zeros for decimal values
opacity: 0.4;
instead ofopacity: .4;
- Put spaces before and after child selector
div > span
instead ofdiv>span
- Local Variables
- Base Styles
- Experiment Styles
Example:
//------------------------------
// Modal
//------------------------------
$modal-namespace: "c-modal" !default;
$modal-padding: 32px;
$modal-background: #fff !default;
$modal-background-alt: color(gray, x-light) !default;
.c-modal { ... }
// Many lines later...
// EXPERIMENT: experiment-rule-name
.c-modal--experiment { ... }
// END EXPERIMENT: experiment-rule-name
- Define all variables at the top of the file
- Namespace local variables with the filename (SASS has no doc level scope)
- eg
business_contact.scss
→$business_contact_font_size: 14px;
- eg
- Local variables should be
$snake_lowercase
- Global constants should be
$SNAKE_ALL_CAPS
- Use the defined color constants via the color function
- Lowercase all hex values
#ffffff
- Limit alpha values to a maximum of two decimal places. Always use a leading zero.
Example:
// Bad
.c-link {
color: #007ee5;
border-color: #FFF;
background-color: rgba(#FFF, .0625);
}
// Good
.c-link {
color: color(blue);
border-color: #ffffff;
background-color: rgba(#ffffff, 0.1);
}
Wrap experiment styles with comments:
// EXPERIMENT: experiment-rule-name
.stuff { ... }
// END EXPERIMENT: experiment-rule-name
Properties and nested declarations should appear in the following order, with line breaks between groups:
- Any
@
rules like include - Layout and box-model properties
- margin, padding, box-sizing, overflow, position, display, width/height, etc.
- Typographic properties
- E.g. font-, line-height, letter-spacing, text-, etc.
- Stylistic properties
- color, background-*, animation, border, etc.
- UI properties
- appearance, cursor, user-select, pointer-events, etc.
- Pseudo-elements
- ::after, ::before, ::selection, etc.
- Pseudo-selectors
- :hover, :focus, :active, etc.
- Modifier classes
- Nested elements
Here’s a comprehensive example:
.c-button {
@include drop-shadow;
display: inline-block;
padding: 5px 10px;
text-align: center;
font-weight: 600;
background-color: color(grey, medium);
border-radius: 3px;
color: color(black);
cursor: pointer;
&::before {
content: '';
}
&:focus, &:hover {
background-color: color(grey, dark);
}
}
- As a general rule of thumb, avoid nesting selectors more than 3 levels deep
- Nesting selectors increases specificity, meaning that overriding any CSS set therein needs to be targeted with an even more specific selector. This quickly becomes a significant maintenance issue.
- Avoid using nesting for anything other than pseudo selectors and state selectors.
- E.g. nesting
:hover
,:focus
,::before
, etc. is OK, but nesting selectors inside selectors should be avoided.
- E.g. nesting
Nesting can be really easily avoided by smart class naming (with the help of BEM) and avoiding bare tag selectors.
If a nested block of Sass is longer than 50 lines, there is a good chance it doesn't fit on one code editor screen, and starts becoming difficult to understand. The whole point of nesting is convenience and to assist in mental grouping. Don't use it if it hurts that.
@extend
may seem like a benefit to selector performance, but often results in unrelated elements being intimately tied together. @mixins
help to encapsulate code but still keep our writing of it very DRY. When gzipped, the performance difference of @mixin
vs @extend
is negligible. @mixins
make for easier maintenance (and we'll never be in danger of hitting IE's 4096 selector cap).
You should always try to spot common code—padding, font sizes, layout patterns—and abstract them to reusable, namespaced classes that can be chained to elements and have a single responsibility. Doing so helps prevent overrides and duplicated rules, and encourages a separation of concerns.
// Bad code
// HTML:
// <div class="modal compact">...</div>
.modal {
padding: 32px;
background-color: color(gray, x-light);
&.compact {
padding: 24px;
}
}
// Good code
// HTML:
// <div class="c-modal u-l-large">...</div>
// <div class="c-modal u-l-medium">...</div>
// components/_modal.scss
.c-modal {
background-color: color(gray, x-light);
}
// helpers/_layout.scss
.u-l-large {
padding: 32px;
}
.u-l-medium {
padding: 24px;
}
- Don’t use
margin-top
.- Vertical margins collapse. Always prefer
padding-top
ormargin-bottom
on preceding elements.
- Vertical margins collapse. Always prefer
- Avoid shorthand properties (unless you really need them)
- It can be tempting to use, for instance,
background: #fff
instead ofbackground-color: #fff
, but doing so overrides other values encapsulated by the shorthand property. (In this case,background-image
and its associative properties are set to “none.” - This applies to all properties with a shorthand: border, margin, padding, font, etc.
- It can be tempting to use, for instance,