The core philosophy here:
- variables are amazing!
- composing classes is really useful
- nesting is actually an anti-pattern that leads to selector over-specificity.
Naming conventions are adapted from the work being done in the SUIT CSS framework. Which is to say, it relies on structured class names and meaningful hyphens (i.e., not using hyphens merely to separate words). This is to help work around the current limits of applying CSS to the DOM (i.e., the lack of style encapsulation) and to better communicate the relationships between classes.
This style guide is based on the structure provided by @fat in Medium's Style Guide.
Table of Contents generated with DocToc
- Organization
- Components
id
attribute- Selectors and Nesting
- Variables
- Units
- Vendor Prefixes [vendor-prefixes]
- Formatting
- Performance
- Presentation-Specific vs Layout-Specific Styles
- Styles
- Media Queries
- Frameworks and Vendor Styles
- Languages
- Credits
Each component should have it's own style sheet. Components MUST NOT be styled by other components. This constraint ensures isolation. It's okay to compose from style sheets that are shared across multiple components and setup shared styles.
- Each component MUST
import
it's CSS in it's JS - Styles applied globally to tag names, see Tag Names, should be kept in a file named for what they're affecting. e.g.
typography
orlayout
- Where possible split presentation-specific styles from layout-specific styles. See below.
With CSS Modules, you're forced to develop in components. Classes are automatically namespaced for you.
Component driven development offers several benefits when reading and writing HTML and CSS:
- It helps to distinguish between the classes for the root of the component, descendant elements, and modifications.
- It keeps the specificity of selectors low.
- It helps to decouple presentation semantics from document semantics.
You can think of components as custom elements that enclose specific semantics, styling, and behavior.
CSS Modules auto namespace your classes.
Consider the following example, where we assigned ddl
to a drop down list component. Take note of the class names.
.ddl-container {
…
}
.ddl-item-list {
…
}
.ddl-item {
…
}
.item-list {
…
}
.dropdown-item-list {
…
}
.xyz-item-list {
…
}
See Selectors and Nesting for information in regard to how styles should be overridden
It's best to use aria attributes instead of separate classes. Here's a list of aria states that you can use via:
aria-busy
aria-grabbed
aria-invalid
aria-checked
aria-disabled
aria-expanded
aria-hidden
aria-invalid
aria-pressed
aria-selected
JS can add/remove these attrs. This means that the same state names can be used in multiple contexts, but every component must define its own styles for the state (as they are scoped to the component).
.tweet {
display: none;
background: rgb(0,0,0);
font-size: 1rem;
/* etc… */
}
.tweet[aria-expanded] {
display: block;
}
<article class="tweet" aria-expanded>
…
</article>
.tweet {
display: none;
}
.tweet.is-expanded {
display: block;
background: rgb(0,0,0);
font-size: 1rem;
/* etc… */
}
<article class="tweet is-expanded">
…
</article>
While the id
attribute might be fine in HTML and JavaScript, it should be avoided entirely inside stylesheets. Few reasons.
- ID selectors are not reusable
- Priority nightmares
- “Bad Code”, Dogmatism, etc.
.ur-name {
…
}
#ur-name {
…
}
Just assign a class name to the element.
Tag names in selectors follow a few rules.
- Application level styles that are only overridden in a few places are okay to use tag name selectors
- Not semantic. Avoid where possible, use class names instead
- Fine to use when there's a ton of elements under the same namespace that need a small tweak
- Don't overqualify (
a.foo
)
button {
padding: 5px;
margin-right: 3px;
}
.ddl-button {
background-color: #f00;
}
.ddl-container button {
background-color: #f00;
}
Styles should never by nested. It breaks isolation, leads to non-deterministic resolution, and leads to specificity wars.
.sg-title-icon::before {
…
}
.dg-container .sg-container .sg-title {
font-size: 1.1em;
}
.dg-container .sg-title span::before {
…
}
Components should never affect the styles of sub components.
Syntax: <property>-<value>[--componentName]
Variable names in our CSS are also strictly structured. This syntax provides strong associations between property, use, and component.
The following variable defintion is a color property, with the value grayLight, for use with the highlightMenu component.
@color-grayLight--highlightMenu: rgb(51, 51, 50);
When implementing feature styles, you should only be using color variables provided by variables/colors.css
.
When adding a color variable to colors.css
, using RGB and RGBA color units are preferred over hex, named, HSL, or HSLA values.
Right:
rgb(50, 50, 50);
rgba(50, 50, 50, 0.2);
Wrong:
#FFF;
#FFFFFF;
white;
hsl(120, 100%, 50%);
hsla(120, 100%, 50%, 1);
Please use the z-index scale defined in variables/z-index.css
.
@zIndex-1 - @zIndex-9
are provided. Nothing should be higher then @zIndex-9
.
With the additional support of web fonts font-weight
plays a more important role than it once did. Different font weights will render typefaces specifically created for that weight, unlike the old days where bold
could be just an algorithm to fatten a typeface. Use the numerical value of font-weight
to enable the best representation of a typeface. The following table is a guide:
Raw font weights should be avoided. Instead, use the appropriate font mixin: .font-sansI7, .font-sansN7, etc.
The suffix defines the weight and style:
N = normal
I = italic
4 = normal font-weight
7 = bold font-weight
Refer to type.css
for type size, letter-spacing, and line height. Raw sizes, spaces, and line heights should be avoided outside of type.css
.
ex:
@fontSize-micro
@fontSize-smallest
@fontSize-smaller
@fontSize-small
@fontSize-base
@fontSize-large
@fontSize-larger
@fontSize-largest
@fontSize-jumbo
See Mozilla Developer Network — font-weight for further reading.
type.css
also provides a line height scale. This should be used for blocks of text.
ex:
@lineHeight-tightest
@lineHeight-tighter
@lineHeight-tight
@lineHeight-baseSans
@lineHeight-base
@lineHeight-loose
@lineHeight-looser
Alternatively, when using line height to vertically center a single line of text, be sure to set the line height to the height of the container - 1.
.btn {
height: 50px;
line-height: 49px;
}
Letter spacing should also be controlled with the following var scale.
@letterSpacing-tightest
@letterSpacing-tighter
@letterSpacing-tight
@letterSpacing-normal
@letterSpacing-loose
@letterSpacing-looser
The web should be fluid by default. Locking your layout to specific sizes will make this goal harder. So, use units that allow fluidity.
Unless dealing directly with fonts or media quires, %
is usually the best unit to use. vw
and vh
are also very handy.
Always prefer using em
over px
. This allows your layout to adapt to font-size changes smoothly.
This is especially true when setting width
, font-size
, padding
, and margin
.
Use height
with caution. Pages are layed out horizontally, limiting your height will nearly always lead to problems.
Avoid vendor prefixes where possible. Only support current version of browsers and two versions back. Use autoprefixer to automatically generate the correct prefixes for you.
The following are some high level page formatting style rules.
CSS rules should be comma separated but live on new lines:
Right:
.content
, .content-edit {
…
}
Wrong:
.content, .content-edit {
…
}
CSS blocks should be separated by a single line. This is to allow users to jump between styles in the same way they jump between paragraphs.
Right:
.content {
…
}
.content-edit {
…
}
Wrong:
.content {
…
}
.content-edit {
…
}
Quotes are technically optional in CSS. Use double quotes as it is visually clearer that the string is not a selector or a style property.
Right:
background-image: url("/img/you.jpg");
font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial;
Wrong:
background-image: url(/img/you.jpg);
font-family: Helvetica Neue Light, Helvetica Neue, Helvetica, Arial;
Although in the name (cascading style sheets) cascading can introduce unnecessary performance overhead for applying styles. Take the following example:
ul.user-list li span a:hover { color: red; }
Styles are resolved during the renderer's layout pass. The selectors are resolved right to left, exiting when it has been detected the selector does not match. Therefore, in this example every a tag has to be inspected to see if it resides inside a span and a list. As you can imagine this requires a lot of DOM walking and and for large documents can cause a significant increase in the layout time. For further reading checkout: https://developers.google.com/speed/docs/best-practices/rendering#UseEfficientCSSSelectors
If we know we want to give all a
elements inside the .user-list
red on hover we can simplify this style to:
.user-list > a:hover {
color: red;
}
If we want to only style specific a
elements inside .user-list
we can give them a specific class:
.user-list > .link-primary:hover {
color: red;
}
Presentation-Specific styles are those that only alter the visual design of the element, but don't change its dimensions or position in a meaningful way. The examples below are presentation-specific.
- Rules such as
color
,font-weight
, orfont-variant
- Rules that animate other properties
font-size
is not considered a meaningful dimension changemax-width
andmax-height
may fit either category, but it's generally reasonable to consider them presentation-specific
Layout-Specific Styles are those that change the dimensions or positioning of DOM elements. These are mostly layout-specific.
- Rules such as
margin
orpadding
width
, andheight
- The element's
position
z-index
, definitely
Where possible, it's suggested to explicitly split styles into these two categories. The explicit differentiation could be made in a few different ways.
- (bad) No differentiation
- (decent) Layout-specific first, presentation-specific later
- (good) A line-break between both categories
- (better) Split in subsequent style declarations using the same selector
.foo {
position: fixed;
top: 8px;
right: 8px;
padding: 2px;
font-weight: bold;
background-color: #333;
color: #f00;
}
.foo {
position: fixed;
top: 8px;
right: 8px;
padding: 2px;
font-weight: bold;
background-color: #333;
color: #f00;
}
.foo {
font-weight: bold;
background-color: #333;
color: #f00;
position: fixed;
top: 8px;
right: 8px;
padding: 2px;
}
.foo {
right: 8px;
color: #f00;
padding: 2px;
top: 8px;
background-color: #333;
font-weight: bold;
position: fixed;
}
These rules apply to your CSS property values
- If the value of a property is
0
, do not specify units - The
!important
rule should be aggressively avoided.- Keep style rules in a sensible order
- Compose styles to dissipate the need for an
!important
rule - Fine to use in limited cases
- Overlays
- Declarations of the
display: none !important;
type
- Keep
z-index
levels in variables in a single file. Avoids confusion about what level should be given to an element, and arbitrarily-high999
-style values - Dislike magic numbers, use variables. Prefer global vars over component-specific vars.
- Avoid mixing units, unless in
calc
- Prefer
rgb
overrgba
over hex. - Unit-less
line-height
is preferred because it does not inherit a percentage value of its parent element, but instead is based on a multiplier of thefont-size
.
.btn {
color: #222;
}
.btn-red {
color: #f00;
}
.btn-red {
color: #f00 !important;
}
.btn {
color: #222;
}
If you are reading this, I salute you. You're almost as boring as I am. I'm more boring because I actually wrote the damn thing. It's not a contest, though.
A few rules apply to media queries.
- Settle for a few (2-3) breakpoints and use those only
- Don't wrap entire stylesheets in media queries
- Approach your styles in a Mobile First manner. Generally you add more things as you get more real state. Mobile First logically follows
.co-field {
width: 120px;
}
@media only screen and (min-width: 768px) {
.co-field {
width: 400px;
color: #f00;
}
}
.co-field {
width: 400px;
color: #f00;
}
@media only screen and (max-width: 768px) {
.co-field {
width: 120px;
color: initial;
}
}
You should shy away from all of these. A few rules apply.
- Stay away from frameworks
- Use
normalize.css
if you want - Vendor styles, such as those required by external components are okay, and they should come before you define any of your own styles. You'll need to prefix them with
:global
.
Some rules apply to stylesheet, regardless of language.
- Use a pre-processor language where possible. CSS Modules is probably the best.
- Use soft-tabs with a two space indent
- One line per selector
- One (or more) line(s) per property declaration
- Long, comma-separated property values (such as collections of gradients or shadows) can be arranged across multiple lines in an effort to improve readability and produce more useful diffs.
- Comments that refer to selector blocks should be on a separate line immediately before the block to which they refer
- Use a plugin such as EditorConfig to get rid of trailing spaces
.foo {
color: #f00;
}
.foo
, .bar
, .baz {
color: #f00;
}
.foo {
color: #foo;
}
.foo, .bar, .baz {
color: #f00;
}
.foo {
color: red;
}
Some ideas from bevacqua and @fat in Medium's Style Guide.