Also see our top-level styleguide: it explains how and when to follow our styleguides, and how to change them.
This is an aspirational styleguide: Use this style for new projects. Move old projects in this direction, even if they have "broken windows", so we don't add to the burden of harder-to-maintain legacy styles.
This styleguide intentionally addresses both higher-level structure and lower-level details, based on the current needs of our team.
It is a UI toolkit, not (just) a framework. It can be hard to override sensibly.
Use normalize.css to make browsers render more consistently.
The root stylesheet is named application.scss
(possibly namespaced, e.g. admin/application.scss
).
If there's a print stylesheet, it's named print.scss
.
application.scss
has no inline CSS, only import statements.
We use @import "foo"
or @import "dir/*"
(globbing provided by sass-rails) rather than Sprockets require
statements, since this works better with Sass variables etc.
For simplicity, our filenames don't start with an underscore (we use foo.scss
, not _foo.scss
). So far we've never compiled Sass in a way that underscores would make a difference. We do use underscores as word separators: cookie_notice.scss
defines .cookie-notice
.
Note that all @import
ed files must end in .scss
, otherwise Sass will generate a plain-CSS @import url(foo.css)
which may work in dev but fail in production.
application.scss
links to this file at the top for reference and mentions any additional project-specific constraints or tools, e.g.:
// Styleguide: https://github.com/barsoom/devbook/tree/master/styleguide/css
application.scss
conventionally imports the following things:
// Import general-purpose (typically third-party) libraries first, so we can override them.
@import "lib/*"; // E.g. lib/normalize.scss, lib/tipsy.scss
// Config files are for $vars only.
// Depending on the project's needs, you may have a single file:
@import "config";
// Or separate files:
@import "config/*"; // E.g. config/{colors,fonts,misc}.scss
// Globally reusable SASS mixins, placeholder selectors and similar.
// Mixins etc that are only interesting within a single component should be defined there instead.
@import "mixins/*"; // E.g. clearfix.scss
// Base elements without classes: `a`, `a:hover`, `input[type=password]`, `li a`, `p > a`.
// Descendant/child/attribute selectors are allowed, but keep specificity as low as you can.
// Again, you may have a single file or separate files: base/{forms,tables,typography,misc}.scss
@import "base";
// Tiny, often single-declaration utility classes.
@import "utilities";
// Anything else, including major layout components.
@import "components/**/*"; // E.g. components/{sidebar,cart,cookie_notice}.scss
// Per-page CSS, when unavoidable.
@import "pages/**/*"; // E.g. pages/{about_page,item_page}.scss
We try to keep our components small (a screenful or less) and reusable (no more specific than necessary).
Influences:
Strive for as low CSS specificity as possible by avoiding nesting selectors (.foo .bar { … }
), chaining selectors (.foo.bar { … }
) or qualifying classes (div.foo { … }
).
This makes maintenance easier by avoiding specificity wars and bugs.
BEM-like naming helps achieve this.
Further reading:
If you mean "within message boxes, style the icon" then write .message-box .icon { }
, not .message-box i { }
.
Even if both selectors work, the former expresses your actual intent and the latter does not. So it communicates better. As a side-effect of that, it's less fragile: the style rule will only affect future icons, not all <i>
elements. And it won't break if we change the icon markup.
Further reading:
If you find yourself needing to undefine a previously defined style, stop and consider if this is a sign that the previously defined CSS should be refactored.
Do .foo-bar
, not .foo_bar
or .fooBar
.
Rather than 0px
, 0em
etc. Not a big deal, but it's the common convention, and less to type.
Our preference is px
(as opposed to pt
, em
, rem
or %
) since it's easy to reason about.
The use of px
was previously discouraged since older browsers didn't scale the text on zoom, but modern browsers get this right.
We do like em
for margins or paddings that should be relative to the current font size.
When you use a CSS class to find elements in automated tests, prefix it. E.g. test-destroy-link
.
These classes should not be used for styling or to find elements in JavaScript. And styling or JS-hook classes should not be used to find elements in tests.
This means that we can confidently change our tests without breaking styling or JavaScript, and vice versa.
We typically don't use BEM-like naming for test-only classes: we tend to do just .test-title
or .test-item-title
instead of .test-item__title
, but it's allowed if you feel like it makes the grouping clearer.
Further reading:
When you find elements in JavaScript, use dedicated JS-only selectors, e.g. .js-destroy-link
.
These classes should not be used for styling or to find elements in tests. And styling or test classes should not be used for JS.
This means that we can confidently change our JS without breaking styling or tests, and vice versa.
We typically don't use BEM-like naming for JS-only classes: we tend to do e.g. .js-item-title
instead of .js-item__title
, but it's allowed if you feel like it makes the grouping clearer. See an example.
Further reading:
We usually write markup in something like Slim.
Then we prefer table.table-bordered.items-table
to table.items-table.table-bordered
.
Since the element name comes first and is the most generic, it reads well to also have generic CSS classes sooner.
Feel free to use them for anchor links (and then write a comment like "This ID is used for anchor links" so it's explicit).
But never style by ID if you can help it.
Use them for third-party JS or CSS if we absolutely must (e.g. some third-party JS library has that API, or we style some embedded third-party widget).
They are "specificity anomalies", making it hard to override styles.
They also discourage modular CSS, as they're only intended to be used once per page.
Sometimes, even a "unique" element may be included twice on a page, e.g. if we show different parts at different viewport sizes.
And for JS, we prefer to use CSS classes with a js-
prefix.
We aim for BEM-like naming. This is a summary of the salient points.
This type of naming has less risk of collisions than nested selectors, more clearly shows which classes are part of the same whole, and makes it easier to override specificity.
Use __
(two underscores) for component parts (e.g. .item-listing__title
).
Use --
(two hyphens) for modifiers (e.g. .item-listing--inactive
).
Example:
.item-listing.item-listing--inactive
.item-listing__title The title
.item-listing__body The body
Define these classes without nesting to keep specificity low:
.item-listing { … }
.item-listing--inactive { … }
.item-listing__title { … }
With Sass 3.4 or later, you can use &
like this:
.item-listing {
&--inactive { … }
&__title { … }
}
Use &
or not as you please, on a case-by-case basis.
The block (the first part of the name) may be implied/implicit: we can use .input--small
for input
variants even if we don't want to add an .input
class to all of them. Or we may do .home-page__thingie
when a style needs to be page-specific and we don't need to actually add a .home-page
class.
You must never use a component part without either an explicit or an implied block: don't use .item-listing__title
for something that isn't an item listing just because it happens to look good. Either add a new component like .other-thing__title
(if they incidentally look the same) or extract a more general concept like .list-title
that can be used in both places.
BEM is an ideal. Pragmatically, we will sometimes "cheat" and write a selector like .item-info__list dt
rather than .item-info__list__title
, if we're confident this doesn't introduce significant risk of collisions or specificity wars. And if those things do become a problem later, we can stop cheating then – we have the ideal to fall back on.
Further reading:
- "BEM-like Naming" in Harry Roberts' CSS Guidelines
- MindBEMding – getting your head ’round BEM syntax
- More Transparent UI Code with Namespaces
- Side Effects in CSS
- Shoot to kill; CSS selector intent
- Utility classes
Only use __
when something is a subcomponent, not when it can be seen as a separate component that happens to be nested.
If you have a "hamburger" menu in the site header and you know you are highly unlikely to have other types of hamburgers elsewhere, do .hamburger
– no need for .site-header__hamburger
.
Aim for no more than one level of subcomponents (.a__b
is OK; avoid .a__b__c
).
Further reading:
In addition to BEM-like modifier naming, we also accept naming like .is-inactive
for modifiers when they are polymorphic, i.e. when JavaScript applies the same modifier to multiple types of components. Then BEM naming would be very inconvenient.
Never style these generic modifier classes standalone (don't do .is-inactive { … }
); always style them together with the modified thing (do .item-listing.is-inactive { … }
). Generic modifier classes should have no styling on their own since we never know what they may be combined with in the future.
Our philosophy on breakpoints (with @media queries) is to just resize the window (or view the page on a device) and set the breakpoint where it makes sense on a case-by-case basis.
We don't want to use predefined breakpoints like "phone", "tablet", "screen" because they're unnecessarily limiting, and less future-proof.
We only use predefined breakpoints when multiple things truly have the same reason to change. If the menu becomes a hamburger at a certain breakpoint, and that change should coincide with other layout changes, then a predefined breakpoint is fine. But name it something like "small-enough-for-hamburger", not "phablet".
We use Sass. See their docs.
So $foo-bar: 123px;
This is the style used in the Sass documentation.
Prefer color: $error-color
or color: $red
to color: #f00
.
This helps us keep color schemes small and consistent. It also names the color.
Similarly, variables can help us keep things like margins, padding, line height, font family and font weight consistent.
Whenever possible, if you need a non-global variable, define it inside a selector so it's only visible in that scope.
Sometimes that's not possible. Then we name it with a leading underscore, e.g. $_item-spacing
, to show intent: we see it as file-private, though it isn't technically.
Thanks to Henric Trotzig for suggesting this.
Do
.my-thing {
margin: 0;
&__subthing {
padding: 0;
}
}
Don't do
.my-thing {
&__subthing {
padding: 0;
}
margin: 0;
}
No practical reason, just taste.
When the list of selectors may grow, do
.foo,
.bar,
{
color: #f00;
}
The separate lines makes it easier to read.
The trailing commas make changes slightly easier.
If the list of selectors is unlikely to grow (e.g. a:hover, a:active {}
) you don't need multiple lines.
Do
.foo,
%foo,
{
color: #f00;
}
.bar {
@extend %foo;
}
Don't do
.foo {
color: #f00;
}
.bar {
@extend .foo;
}
This avoids unintended side-effects if .foo
appears in multiple contexts.
Further reading: