There are several overarching style rules which should be kept to for the sake of consistency across projects.
Let’s look at some basic examples:
// Avoid
.foo, .baz, .bar{
color: #000000; background-color: linear-gradient(to bottom, #ffffff, #cccccc);
font-size: 16px; font-family: Helvetica Neue, sans-serif }
.foo-bar{/* … */}
// Prefer
.foo,
.baz,
.bar {
color: #000;
background-color: linear-gradient( to bottom, #fff, #ccc );
font-size: 1rem;
font-family: "Helvetica Neue", sans-serif;
}
.foo-bar { /* … */ }
Note several features about the above preferred code style
- Each selector is on its own line
- There is a space before the opening
{
- The trailing
}
is on its own line - Each declaration is on its own line
- Each declaration has an ending semicolon, even the last in the set
- There is one blank line between selector sets
- Double quotes around values
- Spacing inside parenthesis
Unless stated, we follow the WordPress CSS coding standards.
If you don’t need to set all the values, avoid shorthand notation. This avoids writing over-broad styles that accidentally overwrite inherited styles.
// Avoid:
.some-component {
background: blue;
margin: 0 0 0 10px;
}
// Prefer:
.some-component {
background-color: blue;
margin-left: 10px;
}
Font weights should be expressed numerically, avoiding the the keywords normal
and bold
.
// Avoid:
h1 {
font-weight: bold;
}
// Prefer:
h1 {
font-weight: 700;
}
The numerical values 100
to 900
roughly correspond to the following common weight names:
Value | Common weight name |
---|---|
100 | Thin (Hairline) |
200 | Extra Light (Ultra Light) |
300 | Light |
400 (normal) | Normal |
500 | Medium |
600 | Semi Bold |
700 (bold) | Bold |
800 | Extra Bold |
900 | Black |
Font sizes should not be written in px
notation, but instead use the preferred rem
or em
values. Using relative units allows inheritance to work more gracefully and prevents having to override static values repeatedly.
// Avoid:
h1 {
font-size: 32px;
}
// Prefer:
h1 {
font-size: 2.5rem;
}
Timing values in CSS should use milliseconds instead of seconds. While 400ms
and .4s
represent the same amount of time, the millisecond value is more explicit and easily readable.
// Avoid:
button {
transition-duration: .15s;
}
// Prefer:
button {
transition-duration: 150ms;
}
The url()
function within CSS accepts parameters with both quotes and without quotes in the v3 spec. However, quoteless parameters require more escaping and special care; using quotes avoids having to remember special escaping rules and makes the values work more reliably.
// Avoid:
div {
background-image: url( ../my-background.jpg );
}
// Prefer:
div {
background-image: url( "../my-background.jpg" );
}
We use the BEM naming convention as a general guide for class naming. BEM stands for Block-Element-Modifier and follows the following style of delineation to denote which level of item that we are modifying.
An element is delineated by underscores __
, and a modifier by dashes --
. All elements of the BEM class should be lowercased.
.block__element--modifier { }
This would carry out in the following example.
.car { /* … */ }
.car__door { /* … */ }
.car--blue { /* … */ }
Our block is a car – this represents the whole of what we’re styling.
Then, we have an element of __door
– this represents a single piece of the larger block that we want to style.
Finally, we have a modifier of --blue
to describe a variation of the car. Perhaps our car was originally red, but we need to style this one as blue.
You can read more on BEM here.
CSS at Human Made should be written in a highly-componentized manner. Each component should have its own file and specific set of styles that relate to it and it alone. These styles can be extended, but generally should be very specific for each use case. This allows us to use a singular set of markup and styles all across a site with predictable results.
Sub-components are indicated with an underscore, variants with a hyphen. For example:
.some-component
.some-component__sub-component
.some-component--variation
In the styles/components directory, components should be placed in a file named for the component, some-component.scss
.
Utilities are prefixed with util-
, for example util-clearfix
. All utility classes should be placed in the styles/utilities.scss file and should be sorted alphabetically.
If a component has state (often determined by JavaScript) then you should set a class on the component to indicate the current state.
You should use the BEM modifier syntax, on a per-component basis, with grammatically correct modifier names to indicate the state.
For example .some-component--is-open
, .some-component--has-option-selected
.
You should avoid using global classes such as .is-visible
(refer to the BEM FAQ for more info).
You should always use the modifier class on the top level block/component, never on the child element.
// Correct
<div class="some-component some-component--is-active">
<div class="some-component__heading"></div>
<div class="some-component__content"></div>
</div>
// Incorrect. Avoid global modifiers.
<div class="some-component is-active">
<div class="some-component__heading"></div>
<div class="some-component__content"></div>
</div>
// Incorrect. Set the state on the on the top level component/block and not on a child element.
<div class="some-component">
<div class="some-component__heading"></div>
<div class="some-component__content some-component__content--is-visible"></div>
</div>
Class names in vendor files should not be changed, the files should be placed in the styles/vendor directory and included as any other file would be included. These files do not need to meet our linting standards.
JavaScript hooks should be kept separate from those used for styles. This allows future developers to move the JavaScript behaviour without the need to modify the CSS.
Our preference is for JavaScript hooks to make use of data attributes, eg [data-lightbox]
. See the JavaScript style guide for further details.
If a JavaScript component must use a class, it should remain separate from styles and be prefixed with js-
.
Specificity across styles is important to the long-term maintainability. Over-specifying at any point on the stylesheet makes it more difficult to make changes or adjustments in the future.
For this reason, the preferred identifier for rules is to use a highly-distinct class name. Classes are distinct without using a high specificity index rating and are re-usable where needed. Using the convention mentioned in the Naming section will help avoid the need to use IDs, !important
, or over-nest your rules.
General tag identifiers p {}
, a {}
, li {}
, h1 {}
, etc. should be avoided unless the style is a global one. For example, using a general tag when styling all <h1>
tags on the site is OK, styling a specific page’s H1 using a tag is not.
// Avoid:
#cards { /* … */ }
.card { /* … */ }
.card ul { /* … */ }
.card ul li p a { /* … */ }
.single-post h1 { /* … */ }
// Prefer:
.card-list { /* … */ }
.card-single { /* … */ }
.card-single__link { /* … */ }
.single-post__page-title { /* … */ }
CSS should be documented as robustly as PHP, HTML, and JS. Verbosity and over-documenting is better than leaving an item unexplained or vague and should complement good class and file naming conventions.
Documentation should include the intent of the code, including for complex selectors (nth-child, combinators); describing animation;
If you make changes to an existing stylesheet, review existing comments to ensure documentation is kept up-to-date.
Each CSS/SCSS file should have a basic docblock at the top of the file identifying a general purpose.
Or a more verbose example when a file’s purpose is less immediately clear:
/**
* WordPress-specific styles
*
* Styles for over-riding or adapting WordPress-specific classes and hacks.
* The HTML used with these styles is out of our control, and thusly may not
* adhere to our normal standards.
*/
Distinct sections of CSS should be individually identified for easier parsing.
For example:
/**
* Media query mixins.
*
* All styles should be written mobile-first, only a limited few should be max-width.
*/
/**
* Colors
*/
/**
* Breakpoints
*/
Functions and Mixins in SCSS should be documented similarly to PHP functions.
Sass Function:
/**
* Test if `$value` is a valid direction.
*
* @param {*} $value Value to test
* @return {Bool} Whether `$value` is valid or not.
*/
@function is-direction( $value ) {
/* … */
}
Sass Mixin:
/**
* Build a linear gradient with a direction and any number of color stops.
*
* @param {String} The direction that the gradient should be oriented `120deg` or `to bottom`
* @param {String} Color stop for each step of the gradient `#fff 0%`
*/
@mixin linear-gradient( $direction, $color-stops... ) {
/* … */
}
Where necessary, individual lines of CSS should be documented as necessary. This documentation is most generally leveraged when a rule breaks the standards, uses a particularly large amount of specificity, or would not make sense to your future self.
In Sass files, //
may be used for single-line comments in lieu of /* */
.
For example:
/* The high-specificity is necessary to override the Bootstrap style .xxxxxx */
.my-class__item a:nth-of-type(3n):after { /* … */ }
/* Prevent a sliver of the photo from showing up behind the banner */
transform: translate( 1px, 1px );
/* All WP images should get some margin and start out centered. */
[class*="wp-image"] {
display: block;
margin: 10px auto;
}
/*
* Multiline comments are formatted with asterisk
* at the start of each line.
*/
CSS should be kept as minimal and simple as allowed by the required design. As we generally follow mobile-first styling, basic style rules should directly apply to the mobile-view (that is, avoid using max-width queries to target small devices; have those rules be the default instead) and styles for progressively larger breakpoints can be layered on with successive min-width:
media queries.
However, there are times where a breakpoint may establish many styles that would need to be overwritten in a larger breakpoint. In cases where a set of complex styles apply to only one intermediate set of screens, aim to reduce complexity in the codebase with a more tightly-scoped query specifying both a min-width and max-width.
This “simplicity first” methodology ensures easier-to-maintain code, maximum performance on mobile devices, and a more fluid experience for all users.
// Base styles applicable to all media - color, etc.
.my-element {
color: black;
font-size: 1.25rem;
}
// More complex layout adjustments.
@media screen and (min-width: 768px) {
.my-element{
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
}
At the scale of projects Human Made typically takes on, rolling your own is more time-effective and less frustrating than using libraries for styling. However, there are certain situation where a library can be useful and used wholesale.
For example, using sanitize.css can give a project a solid baseline and consistency. It will be used across an entire site and the whole library is used wholesale. It provides consistency and can simply be imported and forgotten.
A large frameworks or library such as Bootstrap or Font Awesome is unlikely to be the right solution for the projects that we build and should not be used. You will likely only use a tiny portion of the entire library, leaving the rest as unused orphan code that provides no benefit and adds to the complexity of the codebase.
Variables in Sass and CSS should be used sparingly and with specific purpose. Each variable should represent a single distinct value without side effects. Variables are incredibly useful, but can also add a lot of deleterious side-effects if not carefully managed.
// Prefer:
$breakpoint-tablet: 768px;
$hm-red: #D24632;
$font-family-body: "proxima-nova", sans-serif !default;
$images-path: "../images" !default;
Each of the above represents a variable that is likely to stay the same and can be used without side-effects. You could change any of these values and expect a consistent result and have full knowledge that the effect would be consistent.
// Avoid:
$blue-btn-color: $blue;
$font-medium: 18px;
Let’s look at these examples:
$blue-btn-color
is simply referencing another variable and is hyper-specific to a specific button. This variable is unlikely to be used more than once or twice. This is not a hard no-no, but generally the better path here would be to simply reference$blue
.$font-medium
is usable globally, but if the variable is ever changed down the road then there would be numerous side effects that would be quite difficult to test for.
Unlike Sass variables, CSS custom properties (variables) are subject to the cascade and can be overwritten for elements matching certain selectors. A CSS custom property defined once and used globally may be better as a Sass variable following the rules above.
// Avoid:
:root {
--hm-red: #D24632; /* Better as a Sass variable */
}
// Prefer:
.car {
--transparent: rgba( 0, 0, 0, 0 );
--solid: rgba( 0, 0, 0, 1 );
background-image: linear-gradient( var( --solid ), var( --transparent ) );
}
.car--blue {
--transparent: rgba( 0, 0, 255, 0 );
--solid: rgba( 0, 0, 255, 1 );
}
Avoid nesting, and if nesting is required than absolutely nothing over 2 levels. Nesting more deeply generally indicates that the naming convention used wasn’t robust enough or is not being used appropriately.
The use of BEM for class names can help avoid nesting:
// Avoid:
.car__door {
.car__door--gullwing { /* … */}
}
// Prefer:
.car__door { /* … */ }
.car__door--gullwing { /* … */ }
The more specific modifier rule will take precedence over the element, but both rules only maintain one class’ worth of specificity.
Nesting modifiers within element classes duplicates the existing meaning of the BEM class and adds unnecessary selector specificity.
To save ourselves from writing extra CSS with browser prefixes, all CSS should be run through an autoprefixer. Autoprefixing our styles allows to simply write the basics future-proof code and know that previous versions of browsers will work as expected.
Autoprefixer should be specifically tuned to the browser requirements of the project using browserslist. This can be put in one of many places, a .browserslist file, package.json, and more. We generally recommend placing this in package.json
, but use what is best for your project’s build pipeline.
Autoprefixing via PostCSS is available through Webpack, Grunt, and most other front-end build systems and task runners.
When compiling Sass, you’re generally preparing for one of 2 contexts: development or production. How and what you compile to development will generally depend on the project and technology used. So long as the styles themselves are the same as on production, the style of output is irrelevant.
However, when compiling for production, we need to be much more stringent about our output. The styles need to be as small as possible traveling over the wire.
When compiling to production:
- All comments should be removed
- CSS should be minified as much as possible, and no source maps should be pushed