This document outlines best practices on writing semantic, scalable, and modular CSS. Note that these guidelines assume the use of SASS, a CSS pre-processor, and more specifically, the SCSS syntax. The basis for this guide is to outline distinct rules to format and write SCSS, though the principles outlined here can be applied to CSS or SASS syntax as well.
This guide borrows concepts from Code Guide by @mdo, Idiomatic CSS, and BEM.
- Use soft tabs with a two space indent
- A selector should have a single space between the name and the
{
- A property should have a single space between the
:
and the value - There should be only one property per line
- Unrelated or non-sequenced selectors should have a single new line between them
- Colors should be specified in hex or rgba values
- Hex values should be in lowercase and use shorthand if possible (e.g.,
#fff
) - rgba values should utilize SASS conversion to maintain the hex value (e.g.,
rgba(#fff, 0.5)
) - Comma-separated values should have a single space after the
,
if they appear on a single line - Multiple comma-separated property values should appear indented on multiple lines with one declaration per line
- Decimal values should start with a leading number before the
.
(e.g.,0.5
) - Psuedo selectors and elements should be denoted using a single
:
(e.g.,:hover
,:before
) - Vendor prefixes should be taken care of by an autoprefixer
- Use single quotes (e.g,
'foo'
) - Do not specify units for zero-values (e.g,
margin: 0
) - Single property declarations should appear on a single line
- Multi property declarations should appear on multiple lines with one rule per line
- Mixins without properties should omit the
()
(e.g.,@include my-mixin
) - Avoid the use of SASS color functions (e.g.,
lighten(#000)
) - Avoid the use of
!important
- Use the SCSS comment syntax (
//
) for all comments - Use comment headings appropriately
//
// This is a comment heading
//
//
// This is a comment block heading
//
// Iaculis velit a laoreet consectetur mollis dignissim diam fringilla,
// commodo vulputate turpis dis praesent lacus lorem, justo accumsan
// platea scelerisque pharetra viverra id.
// This is a good example
.foo,
.bar {
position: absolute;
display: block;
font-family: 'Times New Roman', serif; // This is another good example
font-size: 20px;
color: #000;
text-shadow:
0 1px 2px #ddd,
0 2px 2px #aaa;
content: 'Hello world';
background: rgba(#fff, 0.15);
box-shadow: 0 0 10px 0 #000;
}
.foobar {
color: #fff;
&:hover { text-decoration: underline; }
&:active { font-weight: bold; }
}
.biz { font-size: 16px; }
.baz { font-size: 24px; }
- Use
px
for things that should scale in a fixed manner - Use
rem
for things that should scale with the rootfont-size
- Use
em
for things that should scale with the elementfont-size
- Use
%
for things that should scale with the parent element - Use
vw
andvh
for things that should scale with the viewport line-height
should be a unitless multiplier offont-size
Rules and properties should only be as specific as they need to be. Ideally, your selectors should maintain a flat structure.
// Bad π
.grandparent .parent .my-selector {
...
}
// Good π
.my-selector {
...
}
- Always use class selectors
- Do not use ID selectors
- Do not use type selectors
- Do not mix class selectors with ID or type selectors
- Use attribute selectors wisely
Adhering to these guidelines increases the amount of style resuability and reduces the amount of overriding that needs to be done in the CSS codebase.
// Bad π
div {
...
}
div.my-selector {
...
}
#my-selector {
...
}
// Good π
.my-selector {
...
}
Rules should be named semantically so that someone looking at just the markup should be able to determine what that rule is going to do.
- Use BEM style for components
- Use kebab-case for multiple words (e.g.,
.my-block
) - Use two underscores (
__
) to separate blocks from elements (e.g.,.my-block__my-element
) - Use two dashes (
--
) to separate modifiers from blocks or elements (e.g.,.my-block--my-modifier
or.my-block__my-element--my-modifier
)
// Component Classes
.media {
...
&__object {
...
&--reversed { ... }
}
&__body {
...
}
}
// Utility Classes
.text-sm { ... }
.text-md { ... }
.text-lg { ... }
Class names are broken down first into a block (.media
). Then, into elements (.media__object
and .media__body
). And finally into modifiers (.media__object--reversed
).
While nesting is a great feature of SASS, it can easily make things much more complex and hard to digest. Nesting should be limited to 3 levels or less.
While extending selectors (@extend
) is a great feature of SASS, it can easily make the output CSS much more complex and hard to digest so it is best to avoid using them. More often than not, it would make more sense to pull something out into a mixin rather than use an extend.
Limit the use of shorthand declarations if you do not need to explicitly set additional declarations.
// Bad π
.my-selector {
margin: 10px 0 0 0;
border-radius: 4px 0 0 0;
}
// Good π
.my-selector {
margin-top: 10px;
border-top-left-radius: 4px;
}
SASS allows media queries to be nested inside of a selector. This can make our code very easy to digest when working in responsive styles.
.my-selector {
...
@media (min-width: 1024px) {
...
}
}
SASS variables should be used wherever a property value may need to be repeated across the codebase (e.g., color values, breakpoints).
- Use kebab-case (e.g.,
$color-white
) - Use semantic naming conventions
// Bad π
$alpha: #000;
$beta: #fff;
// Good π
$color-black: #000;
$color-white: #fff;
- Order side values by
top
,right
,bottom
,left
- Declare mixins first, then regular properties
- Psuedo selectors should come before other selectors
.my-block {
@include my-mixin;
// Positioning
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 10;
// Box Model
box-sizing: border-box;
display: block;
width: 100px;
height: 100px;
padding-top: 10px;
padding-bottom: 10px;
margin: 10px;
overflow: hidden;
// Typography
font-family: 'Times New Roman', serif;
font-size: 1rem;
font-style: italic;
font-weight: bold;
line-height: 1.5;
color: #000;
text-align: center;
text-decoration: underline;
text-shadow: 0 1px 0 #fff;
text-transform: uppercase;
// Accessibility & Interaction
cursor: pointer;
// Background & Borders
background: #aaa;
border: 10px solid #000;
box-shadow: 0 0 10px #000;
// Transitions & Animations
transition: all 100ms ease;
&:before { ... }
&:hover { ... }
&--my-modifier { ... }
&__my-element { ... }
}
It is best to leverage a linter like stylelint to make it easy to adhere to the following guidelines. You can install this repository through your package manager and point to it in your own stylelint config file. Alternatively, you can copy the stylelint config file (.stylelintrc
) included in this repository into your project.
yarn add stylelint scss-style-guide --dev
npm install stylelint scss-style-guide --save-dev
{
"extends": [
"scss-style-guide"
]
}