These guidelines will govern how we write frontend code for The Spruce (and hopefully beyond!)
- Author semantic markup - HTML gives structure to content, and browsers, search engines, and assistive devices make use of that structure to benefit users. Headings should be
<h1>
through<h6>
tags, groups of objects should be marked up as<ul>
or<ol>
tags, and so on. - Legibility - Markup should be easily readable by authors, especially considering that markup is often mixed with Freemarker and other non-HTML code. Use proper spacing, naming, and commenting conventions to keep code legible for yourself and your teammates.
- Bake in accessibility - Incorporate accessibility best practices into markup as you author markup rather than tacking it on as an afterthought.
All closing tags for HTML elements that span multiple lines must contain a comment on the same line that says identifies what the tag belongs to, such as <!--end primary-nav__item-->
.
<div class="card">
...
</div><!--end card-->
Finding the sweet spot between density and whitespace can be tricky. With that in mind, spacing in HTML isn't strictly enforced, but add appropriate space around like items to cluster related tags together in order to keep the code legible.
This:
<ul class="primary-nav__list">
<li class="primary-nav__item">
<a href="" class="primary-nav__link">Link</a>
</li><!--end primary-nav__item-->
</ul><!--end primary-nav__list-->
But maybe not this:
<ul class="primary-nav__list">
<li class="primary-nav__item">
<a href="" class="primary-nav__link">Link</a>
</li><!--end primary-nav__item-->
</ul><!--end primary-nav__list-->
Or this:
<ul class="primary-nav__list">
<li class="primary-nav__item">
<a href="" class="primary-nav__link">Link</a>
</li><!--end primary-nav__item-->
</ul><!--end primary-nav__list-->
Again, spacing isn't strictly enforced, but don't be afraid of a little white space.
- Consistency is key - Writing as a team means sticking to conventions we collectively established. Writing CSS consistently makes authoring, testing, and refactoring easier, which keeps everybody happy.
- Modularity – There should be clear separation between components to ensure each component is as reusable and portable as possible. That involves choosing agnostic names that describe a component's structure rather than the content that may live inside it (i.e.
checklist
vsrecipe-checklist
). Components exist in a hierarchy so make even simple components (molecules) stand on their own two feet rather than being dependent on a broader context. Legibility is key. – Developers should be able to understand CSS code at a glance and understand the purpose of any given selector. Use consistent syntax, appropriate spacing, and comprehensive commenting to ensure anyone can easily understand what's going on. - Legibility over succinctness – Keeping CSS legible and understandable means sacrificing a shorter syntax.
- Keep things flat – Long selector strings should be avoided wherever possible in order to keep CSS as DOM-independent and modular as possible.
Class prefixes are used to each class to make it more apparent what job that class is doing. Here’s what class prefixes we landed on:
- Unprefixed classes are reserved for UI components only.
.card
or.header
or.social-share
are examples of UI components. l-
for layout-related styles, such as.l-grid__item
u-
for utilities, such as.u-margin-bottom-double
is-
for specific states, such as.is-active
or.is-disabled
. These state-based classes would apply tojs-
for targeting JavaScript-specific functionality, such as.js-modal-trigger
. No styles are bound to these classes; they’re reserved for JavaScript targeting only. (Note: this approach may be revisited if the system isn't set up for
We're using BEM as the CSS methodology. BEM stands for “Block Element Modifier”, which means:
- Block is the primary component block, such as
.card
or.btn
- Element is a child of the primary block, such as
.card__title
. - Modifier is a variation of a component style, such as
.alert--error
.
Utilizing this methodology removes ambiguity about the job a class is doing and allows us to create more legible code. A few notes about BEM syntax:
- Never include multiple child elements in a single class name (i.e.
primary-nav__item__link
). We don't say "I need to scratch my face nose," so keep it to a single name. - Never use capital letters in class names. Words are separated by a
-
dash, such as.primary-nav
or.primary-nav__subnav-link
. - Elements of a component must appear in their own line rather than nested inside the block element (see nesting guidelines below). So this:
.card {
...
}
.card__title { }
Not this:
.card {
&__title { }
}
- Modifiers can appear nested inside the parent block if only a few lines long. See "Sass nesting" below for more details.
- Modifier styles modify a block's attributes (i.e.
person--tall
) rather than a block's context (meaningperson--is-on-a-bus
would be incorrect).
Our code base mostly follows the commenting guidelines laid out in CSS Guidelines.
The top of any major Sass partial should begin with a comment block containing the name of the component or partial in all uppercase preceded by a pound sign (#COMPONENT
, for example). This serves as an "h1" for the partial, and the pound sign allows us to easily find the component within the site structure.
/*------------------------------------*\
#COMPONENT
\*------------------------------------*/
/**
* Component element
*/
Each declaration
We're using headnotes to explain non-obvious style declarations. There's no need to explain what background: red
does, but create a headnote for
/**
* Component element
* 1) Places element in the top right corner of parent
*/
.component__element {
display: flex;
position: absolute; /* 1 */
top: 0;
right: 0;
z-index: 2;
}
Nesting in Sass can be very convenient, but runs the risk of poor output with overly long selector strings. Follow the Inception Rule and never nested Sass more than three layers deep.
In order to keep the CSS flat principle in mind, we are limiting Sass nesting to only the following four use cases:
- Modifiers of a style block
- Media queries
- Parent selectors
- States
For modifiers, if the rule is only a few lines long, the modifier can be nested inside the parent like so:
/**
* Alert message banner
*/
.alert {
border: 1px solid gray;
color: gray;
/**
* Error Alert
*/
&--error {
border-color: red;
color: red;
}
}
Style modifiers longer than 3 or 4 lines reduce the legibility of the code. In these instances do not nest the style modifiers and instead move it below the primary style block.
Component-specific media queries should be nested inside the component block.
.primary-nav {
/* Base styles */
/**
* 1) On larger displays, convert to a horizontal list
*/
@media all and (min-width: 40em) {
display: flex; /* 1 */
}
}
The CSS makes use of Sass’s parent selector mechanism. This allows all rules for a given component to be maintained in one location.
/**
* Primary navigation
*/
.primary-nav {
/* Base styles */
/**
* Primary navigation appearing in the header
* 1) Since the header is set to `display: flex`, setting the left margin to `auto`
* positions the navigation to the right.
*/
.header & {
margin-left: auto; /* 1 */
}
}
Consider a .person
block. There are properties that are innate to a person: height, weight, hair color, etc.
.person--tall
and .person--short
may be modifiers of the main .person
block, as they modify the intrinsic properties of the .person
block.
Objects also exist in different contexts, and that can influence style and behavior. A .person
can be riding in a subway car or taking a shower, and behavior will no doubt change based on that context. The intrinsic properties of the .person
do not change, but their changed context requires modifications.
.person {
/* Base styles */
.on-subway & {
/* Context-specific styles */
}
}
While exact ordering of CSS declaration isn't strictly enforced, try to follow the following conventions:
.component {
[box-model properties]
[positioning properties]
[font properties]
[other cosmetic properties]
[animation properties]
}
Note: the reason we're not alphabetizing declarations is because some related declarations (i.e. display: flex
, flex-direction: column
, align-items: center
, and justify-content: center
) would appear in wildly different places.
Putting all this together, let's take a look at a sample Sass partial that can serve as a boilerplate for creating
/*------------------------------------*\
#COMPONENT NAME
\*------------------------------------*/
/**
* 1) A brief description of the component.
* 2) Display like properties together
*/
.component-name {
display: flex; /* 2 */
flex-direction: column;
align-items: center;
justify-content: center;
&:hover, &:focus {
background: blue;
}
/**
* Modifier
*/
&--error {
border-color: red;
}
/**
* Component appearing inside Another Component
* 1) This is a Sass parent selector, used to
* 2) This positions the component in the top right corner
*/
.another-component & {
position: absolute; /* 2 */
}
@media all and (min-width: $bp-large) {
margin-bottom: 2rem;
}
}
/**
* Component child
*/
.component-name__child {
font-size: $font-size-large;
}
Sass variables are used to control. Here are some prefixes we'll follow for Sass variables:
$color-brand-
as a prefix for brand-related colors$color-gray-
for neutral colors. The value of the variable is the percentage of black, so$color-gray-50
means "50% black", aka#808080
.$color-dim-
for semi-transparent colors. This$color-utility-
for utility colors, such as success, error, warning, and info messages.
$font-family-
is used to define a font stack.$font-size-
for font sizes. We use t-shirt sizing for font size variables, such as$font-size-small
or$font-size-large
.
$l-
for layout-specific rules, such as$l-max-width
for capping the maximum width of a container.
$border-
for border-related variables, such as border width or radius values.
$anim-
for animation-specific variables, such as animation duration or easing values.
$bp-
for major breakpoint values that are shared across
[TODO]
[TODO]