Chapters
CSS Coding Standards
LESS & SASS
We use pre-compilers for styles here. You don’t have to, honestly, CSS is pretty advanced these days. That said it does provide a nicer development experience which translates to more readable and consistent code.
Note: If you want to learn all official features of these precompilers, check out their official documentation: LESS & SASS
By default, the project boilerplate is set to use SASS (SCSS to be more precise).
CSS Variables
While SCSS does give us access to variables, we tend not to use them. Mostly because CSS basically has built-in variables, in the way of custom properties. The more we can do something natively without relying on third party libraries, the better. Furthermore, custom properties are more flexible. Here is a good article that explains the differences if you’re curious to learn more.
Don’t use IDs for Styling
Write IDs onto HTML elements, headings, etc. They’re perfect for targeting event listeners in JavaScript and making anchor links, but they jack CSS specificity way up in an unnecessary way. So, avoid writing styles to an #ID selector unless you have to on inherited/legacy code.
Use a Flat Hierarchy for Defining Classes
Write out all of your classes on the same level using their full names. This seems like a weird little thing (Prettier will do it for you anyhow), but it makes it considerably easier to read, understand and search your code if everything is on the same indentation level (with the exception of nested selectors which you’ll read about next). Just describing a single class helps keep specificity low, makes rewriting and overwriting styles easy and not to mention how crazy things can get with indentation and line lengths extending forever.
Correct:
Incorrect:
Use “&” Sparingly
In SASS, the & character makes reference to the parent context. This is used in a nested selector. For instance:
Use as few of these as possible. It makes a stylesheet almost impossible to search through otherwise. Double “don’t do it” if you’re considering using the & character with ELEMENT or MODIFIER classes in BEM. Seriously, don’t do it.
Structure
It is important to have a clear understanding of the flow of the document. To achieve this, we strive to follow the following format:
- Use tabs, not spaces, to indent each property.
- Each selector should be on its own line, ending in either a comma or an opening curly brace. Property-value pairs should be on their own line, with one tab of indentation and an ending semicolon. The closing brace should be flush left, using the same level of indentation as the opening selector.
- There should be a space between the last selector and the opening curly brace.
Correct:
Incorrect:
Selectors
With specificity, comes great responsibility. Broad selectors allow us to be efficient, yet can have adverse consequences if not tested. Location-specific selectors can save us time, but will quickly lead to a cluttered stylesheet. Exercise your best judgement to create selectors that find the right balance between contributing to the overall style and layout of the DOM.
- Use lowercase and separate words with hyphens when naming selectors. Avoid camelcase and underscores.
- Use human readable selectors that describe what element(s) they style.
- Attribute selectors should use double quotes around values.
- Refrain from using over-qualified selectors,
div.containercan simply be stated as.container.
Correct:
Incorrect:
Nest Sparingly
One of LESS/SASS’s greatest features is the ability to nest selectors. For example:
That’s awesome, however, it does add specificity. I don’t want to sound like a broken record, but it’s just a rule we try to follow first before breaking it.
Attempt to only nest selectors that apply to THIS class, like pseudo elements (before and after), state selectors (hover, active, focus, visited), js-related state classes (e.g., .is-visible, .is-hidden) and media queries.
Here’s a maximal example of acceptable nested selectors:
We do this for developer intelligibility. No need to jump around and find a breakpoint that contains all of the > 1000px styles. It adds to the weight of the stylesheet, which is bad for performance, but Gzip on the server will cut back on the weight for that particular network request. This is an example of a place where the rule (Be better! Performance!) has informed a decision to break from our rules.
Use our @media mixin directly in styles
Don’t write out media queries by hand, use our little tool instead. In the Variables section of the stylesheet, there will be a “Map” of breakpoints:
$breakpoints: (
"360": 360px,
"375": 375px,
"500": 500px,
"680": 680px,
"800": 800px,
"960": 960px,
"1100": 1100px,
"1300": 1300px,
"1400": 1400px,
"1700": 1700px,
"mobile" : 1100px // This is a shortcut for the mobile breakpoint.
) !default;
Change these based on how the project develops (add/modify as necessary). The _breakpoints.scss file can be found in the assets/src/styles/mixins folder in the project boilerplate. When you need to use one, use our handy-dandy mixin like this:
.navigation {
...desktop styles
@include break("1100") {
...styles that apply @ 1100px and below
}
}
.navigation__icon {
...desktop styles
@include break("500") {
...styles that apply @ 500px and below
}
}
We do this for developer intelligibility. No need to jump around and find a breakpoint that contains all of the > 1000px styles. It adds to the weight of the stylesheet, which is bad for performance, but Gzip on the server will cut back on the weight for that particular network request.
Use the .is- Prefix for Styling State
We use classes like .is-visible or .is-hidden to describe a change of state. When a user clicks a button and the interface is meant to change via an interaction that’s when Javascript gets involved.
The most maintainable system is to use as little JS as possible to get a job done. Switching a single .is- class on a couple of elements should cover most of what you’re doing (preferably, just the parent).
Do not go crazy with inline styles, extra classes, etc. using JavaScript.
JavaScript is awesome, our reliance on it as developers is not. Consider how the heck a blind person is meant to navigate the site if JS is a requirement to use the navigation menu.
There’s another rule hidden here:
Do not use display: none unless it absolutely needs to be invisible to the browser/user. It is the equivalent of deleting the code from the document and is thereby inaccessible
There is a class in the boilerplate that will completely hide an element from the DOM, while keeping it accessible: hidden--visually.
Avoid using !important
Using an !important flag should not be a common practice. It’s the front-end equivalent of the “Nuclear option”. You will not use these on a new build. That said you may have to use them when inheriting a legacy code base or a template we did not author.