Skip to content

Instantly share code, notes, and snippets.

@arlodesign
Last active November 10, 2016 21:44
Show Gist options
  • Save arlodesign/74947d4e515ec3f434f6 to your computer and use it in GitHub Desktop.
Save arlodesign/74947d4e515ec3f434f6 to your computer and use it in GitHub Desktop.
Sprout Social CSS Style Guide

CSS/SCSS Standards

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.

Format

  • 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, not p>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() or translate3d().
  • No leading zeros (e.g., .5 instead of 0.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 of margin: 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: "; 
}

Preprocessor

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 use rgba($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.

P.E.A.S.

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.

P.E.A.S. In Action

A breakdown of a button pattern in P.E.A.S.:

The pattern

<button class="Button"></button>
.Button { 
	background-color: $green;
}

Elements in the pattern

<button class="Button"><span class="Button-text">Send Now</span></button>
.Button { 
	&-text {
		font-size: 14px;
	}
}

Alternates

<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

	/* State class added dynamically */
	<button class="Button isLoading"><span class="Button-text">Send Now</span></button>
.Button { 
	background-color: $green;

	&.isLoading {
		transform: none;
	}
}

Full pattern (example)

.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: ‘< ‘;
		}
	}
}

Writing in P.E.A.S.

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 the CSS rule declaration. In certain cases when there are no replacement elements e.g., 'svg' or dl 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 or is followed by a word describing the state e.g., isAnimating.

Wrapper Classes

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 {
	…
}

Declarations

Guidelines

  • 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.

Declaration Order

Related property declarations should be grouped together following this order:

  1. Variable declarations, extends, and mixins
  2. Positioning
  3. Box model
  4. Flexbox
  5. Table Layout
  6. List styles
  7. Typography
  8. Visual (backgrounds, borders, opacity)
  9. Pseudoelement content
  10. Behavior (pointer events, user select)
  11. Transforms and Animations

CSSComb

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.

Comments

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.

{
"block-indent": "\t",
"space-before-selector-delimiter": "",
"space-after-selector-delimiter": "\n",
"space-before-opening-brace": " ",
"space-before-closing-brace": "\n",
"space-before-colon": "",
"space-after-colon": " ",
"space-after-opening-brace": "\n",
"space-between-declarations": "\n",
"always-semicolon": true,
"space-after-combinator": " ",
"space-before-combinator": " ",
"color-case": "lower",
"color-shorthand": true,
"quotes": "double",
"unitless-zero": true,
"element-case": "lower",
"eof-newline": true,
"leading-zero": false,
"remove-empty-rulesets": false,
"strip-spaces": true,
"vendor-prefix-align": true,
"sort-order": [
[
"$variable"
],
[
"$import",
"$include",
"position",
"top",
"right",
"bottom",
"left",
"z-index",
"display",
"float",
"width",
"height",
"max-width",
"max-height",
"min-width",
"min-height",
"box-sizing",
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"margin",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"margin-collapse",
"margin-top-collapse",
"margin-right-collapse",
"margin-bottom-collapse",
"margin-left-collapse",
"overflow",
"overflow-x",
"overflow-y",
"clip",
"clip-path",
"clip-rule",
"clear",
"flex",
"flex-basis",
"flex-grow",
"flex-shrink",
"flex-direction",
"flex-wrap",
"flex-flow",
"order",
"justify-content",
"align-items",
"align-content",
"align-self",
"table-layout",
"empty-cells",
"caption-side",
"border-spacing",
"border-collapse",
"list-style",
"list-style-type",
"list-style-position",
"list-style-image",
"font",
"font-family",
"font-size",
"font-style",
"font-weight",
"font-variant",
"font-size-adjust",
"font-stretch",
"font-effect",
"font-emphasize",
"font-emphasize-position",
"font-emphasize-style",
"-webkit-font-smoothing",
"-moz-osx-font-smoothing",
"hyphens",
"line-height",
"letter-spacing",
"word-spacing",
"color",
"color-interpolation",
"color-interpolation-filters",
"color-profile",
"color-rendering",
"text-anchor",
"text-align",
"text-decoration",
"text-indent",
"text-overflow",
"text-rendering",
"text-size-adjust",
"text-shadow",
"text-transform",
"word-break",
"word-wrap",
"white-space",
"vertical-align",
"appearance",
"background",
"background-color",
"background-image",
"background-repeat",
"background-attachment",
"background-position",
"background-position-x",
"background-position-y",
"background-clip",
"background-origin",
"background-size",
"opacity",
"visibility",
"border",
"border-width",
"border-style",
"border-color",
"border-top",
"border-top-width",
"border-top-style",
"border-top-color",
"border-right",
"border-right-width",
"border-right-style",
"border-right-color",
"border-bottom",
"border-bottom-width",
"border-bottom-style",
"border-bottom-color",
"border-left",
"border-left-width",
"border-left-style",
"border-left-color",
"border-radius",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
"border-image",
"border-image-source",
"border-image-slice",
"border-image-width",
"border-image-outset",
"border-image-repeat",
"outline",
"outline-width",
"outline-style",
"outline-color",
"outline-offset",
"box-decoration-break",
"box-shadow",
"content",
"quotes",
"counter-reset",
"counter-increment",
"pointer-events",
"cursor",
"resize",
"user-select",
"transition",
"transition-property",
"transition-duration",
"transition-timing-function",
"transition-delay",
"transform",
"transform-origin",
"transform-style",
"backface-visibility",
"perspective",
"animation",
"animation-name",
"animation-duration",
"animation-play-state",
"animation-timing-function",
"animation-delay",
"animation-iteration-count",
"animation-direction",
"animation-fill-mode",
"alignment-baseline",
"baseline-shift",
"direction",
"dominant-baseline",
"enable-background",
"fill",
"fill-opacity",
"fill-rule",
"filter",
"flood-color",
"flood-opacity",
"glyph-orientation-horizontal",
"glyph-orientation-vertical",
"image-rendering",
"kerning",
"letter-spacing",
"lighting-color",
"marker-end",
"marker-mid",
"marker-start",
"mask",
"pointer-events",
"shape-rendering",
"stop-color",
"stop-opacity",
"stroke",
"stroke-dasharray",
"stroke-dashoffset",
"stroke-linecap",
"stroke-linejoin",
"stroke-miterlimit",
"stroke-opacity",
"stroke-width",
"unicode-bidi",
"writing-mode"
]
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment