This document represents our ideals for writing CSS for the Sprout Social web app*. This document will serve as our guide for writing new CSS and updating existing styles. Code reviews of new CSS will adhere to these guidelines as closely as is practical—it’s a jungle in there, so concessions will be made for fighting existing specificity issues.
Colors, buttons, icons and modals can be found at our previous style guide document. Though that document is now deprecated, the patterns are still valid.
- Tabs, not spaces.*
- One selector per line.
- One space before the opening brace of property declaration blocks.
- Closing brace on a new line.
- No spaces before and one space after
:
for each property. - One property per line.
- End all declarations with a semicolon, even the last one.
- Single selectors with one property declaration should be written on the next line.
- One space around combinators, e.g.,
p > a
, notp>a
. - One space after commas in property values (e.g.
, box-shadow
) and arguments in mixins. - No spaces after commas within values like
rgb()
,rgba()
,hsl()
,hsla()
,rect()
ortranslate3d()
. - No leading zeros (e.g.,
.5
instead of0.5
and-.5px
instead of-0.5px
). - Lowercase hex, e.g.,
#fff
. - Shorthand hex values where available, e.g.,
#fff
instead of#ffffff
. (see notes on color usage Preprocessor section and) - Double quotes.
- Quote attribute values in selectors, e.g.,
input[type="text"]
. - No units on 0 values, e.g.,
margin: 0;
instead ofmargin: 0px;
. - Double-colon on pseudoelements
- When nesting selectors with a preprocessor, one additional line break between declarations and the nested selector
- When using a mixin with no arguments, leave the parentheses off
- Two line breaks before a nested CSS definition e.g.,
.Selector {
background: $green;
padding: 10px;
&-nested {
color: $white;
}
}
An example of poorly formatted CSS:
.selector, .selector-secondary, .selector[type=text] {
@include selector-mixin;
padding:15px; margin:0px 0px 15px;
background-color:rgba(0, 0, 0, 0.5);
box-shadow:0px 1px 2px #CCC,inset 0 1px 0 #FFFFFF
&-nested+p {
color: #ff0000;
}
}
.selector-single:before {
content: ‘Optional: ‘;
}
An example of awesomely formatted CSS:
.selector,
.selector-secondary,
.selector[type="text"] {
@include selector-mixin;
padding: 15px;
margin: 0 0 15px;
background-color: rgba(0,0,0,.5);
box-shadow: 0 1px 2px #ccc, inset 0 1px 0 #fff;
&-nested + p {
color: #f00;
}
}
.selector-single::before {
content: "Optional: ";
}
Our preprocessor is Libsass, and we use the SCSS dialect of Sass.
- No preprocessor color functions except in mixins where values such as hover or focus states can be abstracted (one exception below)
- Use color variables, not explicit color values. If the design calls for translucency, use a preprocessor color function in order to reference an existing palette color. For example, if a design calls for our brand green at 30% opaque, don’t use
rgba(122,193,67,.3)
. Instead usergba($green, .3)
. $img_cdn
to reference CDN image, not URL- No ampersands nested more than two levels deep
Functions, mixins and placeholders will be documented soon. We promise.
Our approach to CSS organization is called Pattern, Element, Alternate, State, or P.E.A.S.. It is our our hybrid of OOCSS’ methodology, BEM’s syntax enforcement, and SMACSS’ organization principles.
- All other parts of selectors use lowercase letters only.
- In most cases, use classes only for styling. HTML elements are safe (such as section) when they make specific semantic sense to the pattern you’re styling, but use a > child selector whenever possible.
- Same goes for the * wildcard selector. You probably have a good reason for using it., but use a > child selector if you can. It’s an expensive selector.
- IDs are only permissible when wrestling with specificity issues in legacy code.
- Whenever possible, classes should describe the contextual intent, not the presentation.
.Warning-text
good,.red-text
bad - State classes should be camelCased e.g.,
.isAnimating*
. These classes denote styles toggled based on a change in a component’s state, usually toggled with Javascript. While these types of styles seem vestigial when building React components, it still communicates intent for the style and helps developers use patterns in legacy (non-React) areas of the app. Avoid including these classes in HTML. .js-*
classes are intended for hooking event listeners in the Javascript..qa-*
classes are sometimes used for automated tests. These classes should never appear in our CSS.- Modifier and state classes are bound to a pattern or child element.
.Button.isLoading
is great, but.isLoading
on its own defined globally is not. Do not write global utility classes—an element’s visibility, for instance, should be scoped to the pattern.
A breakdown of a button pattern in P.E.A.S.:
<button class="Button"></button>
.Button {
background-color: $green;
}
<button class="Button"><span class="Button-text">Send Now</span></button>
.Button {
&-text {
font-size: 14px;
}
}
<button class="Button _warning"><span class="Button-text">Delete Forever</span></button>
<button class="Button"><span class="Button-text">Save</span></button>
.Button {
&-text {
font-size: 14px;
}
&._warning {
background-color: $red;
}
}
/* State class added dynamically */
<button class="Button isLoading"><span class="Button-text">Send Now</span></button>
.Button {
background-color: $green;
&.isLoading {
transform: none;
}
}
.Button {
// Button styles
background: $green;
&:hover {
background: $green-hover;
}
// Child elements
&-text {
font-weight: bold;
}
// Using an HTML element? Try to use a child selector
> img {
position: absolute;
}
// Button states
&:active.isLoading,
&:active.isDisabled {
transform: none;
}
// Button alternates
&._warning {
background: $red;
&:hover { background: $red-dark; }
}
&._passive {
color: $white;
background: $gray40;
border-color: $white;
&:hover {
background: $gray50;
}
}
&._large {
min-height: 40px;
}
&._back {
> .button-text::before {
content: ‘< ‘;
}
}
}
Since P.E.A.S. is based off BEM and OOCSS principals we borrow the good parts of both when writing selectors in P.E.A.S..
Some general guidelines:
- Pascal case for Patterns
- Pattern name should be as generic as possible. Always consider future use cases.
- Use a
-
to specify element e.g..Pattern-headline
- Keep element names lowercase with no spaces, e.g.
// Always
.Pattern-subheadline {
…
}
// Never
.Pattern-sub-headline {
…
}
.Pattern-subHeadline {
…
}
- Use an additional
-
for sub-sub elements, e.g..Pattern-subheadline-helpbubble
- Avoid nesting more than 3 levels of sub elements
- In cases where there are more than 3 levels of sub elements, consider breaking down naming at a higher level, e.g.
// Avoid superfluous sub classes
.Modal-calendarintro-content-publishinadvance-graphic {
…
}
// Use concise and unique naming avoiding levels
// unnecessary to the element being styled
.Modal-calendarintro-publishinadvance-graphic {
…
}
- Element classes should be used as liberally as needed but in certain cases it makes sense to use the
HTML
tag name in theCSS
rule declaration. In certain cases when there are no replacement elements e.g., 'svg' ordl
it would be appropriate to style the element but in cases where overly specifc tags would be required e.g.,> div
and> p
classes should be used.
// Where top selector is a UL
.Modal-calendarintro-list {
li {
…
}
// Where top selector is a div and styled element
// is potentially passed in dynamically
.Modal-calendarintro-graphic {
figure,
img {
…
}
}
- Alternate classes should be used to denote a specific version of something, .e.g.,
._twitter
,._primary
- Alternate classes should always be lowercase and words grouped together (as with Elements) e.g.,
._linkedincompany
- State classes should be used to express the current state of an element at a particular point in time. e.g.,
.hasError
,.isLoading
- *State *classes are almost always added dynamically via
JavaScript
should be removed / altered from the element when the state has changed. If there is a need to initially add a state class to an element, it’s likely you should be using a Alternate class - State classes always start with a verb e.g.,
has
oris
followed by a word describing the state e.g.,isAnimating
.
Wrapper Classes are essential for keeping our pattern library maintainable and scalable. When writing a new pattern, avoid incorporating context-unique styles and markup and instead use an element with a wrapper class as a namespace and add the specific styles to the related style sheet. Things to avoid include: Specific box model definitions (height, width, padding, margins) as well as references to a specific feature / use case / section of the app.
Wrapper class usage example:
// Never (in EducationUnit stylesheet)
.EducationUnit-inboxnodata-graphic {
…
}
// Always
<div class="EducationUnit-inboxnodata">
<EducationUnit />
</div>
// In inbox stylesheet…
.EducationUnit-inboxnodata {
.EducationUnit-graphic {
// Custom styles for inbox here..
}
}
Within a component, classes should should be applied with a specific purpose to avoid the need to write hacks in the future. Avoid applying styles directly to generic elements that could change or could have multiple meanings.
// Use
// This works because the styles are only applied an element with this specific class
.Button-icon-svg {
…
}
// Avoid
// In this scenario, all instances of a span get the styles in this block
.Button > span > svg {
…
}
- No units for line-height. Tip:* Use math in the preprocessor. For example, if the design has 14px type with 20px leading, use
line-height: (20/14)
; - Use
$img_cdn
when referencing images, not the CDN’s URL. When placing the variable in a url, use the #{} interpolation syntax:#{$img_cdn}
- Images should be optimized before they are converted to data-uris. Reccomended optimization tools are ImageOptim and ImageAlpha for PNGs and SVGO for SVGs and use gzip (saved as
.svgz
). - Do not use vendor prefixes. Our build system uses Autoprefixer to add the vendor prefixes required for the browsers we support.
Related property declarations should be grouped together following this order:
- Variable declarations, extends, and mixins
- Positioning
- Box model
- Flexbox
- Table Layout
- List styles
- Typography
- Visual (backgrounds, borders, opacity)
- Pseudoelement content
- Behavior (pointer events, user select)
- Transforms and Animations
Much of what is documented above can be automated with CSScomb. The .csscomb.json
file in the root of our app defines our declaration sort order and fixes most of the format described above.
A few caveats:
- It chokes on some preprocessor syntax, and the error messages typically are not helpful. You may find that you use CSScomb in chunks and not over an entire file in one fell swoop
- It does not add a blank line between a parent’s declaration and a nested child element’s selector. You’ll have to go back and do it manuall, or, if you are using Sublime Text, do this.
If you must do any of the following things, please leave a comment explaining why for the next person who will open your SCSS file:
- Magic Numbers. With the exception of font sizes, if you must use a number that is not divisible by 5, add a comment explaining why.
!important
, IDs or overly qualified selectors. If you’re fighting specificity issues, explain them.- Anything other than px for units. If you have a reason, add a comment. One day we’ll move to rems, I promise.
*The marketing site uses spaces instead of tabs. Bambu doesn’t even write CSS anymore. Despite our best efforts to align our standards, it’s still anarchy.