“Close-up of lines of code on a computer screen” by Ilya Pavlov on Unsplash

Writing Maintainable SCSS

Some tips and tricks for writing styles that won’t make you tear out your hair later

Some of these tips are things I’ve found on the web over years and put into practice, and found that they were as helpful as advertised (happy to include links whenever I come across a source). Others are things I noticed during my own development. All are subject to opinion, and there may be other ways to solve some of the problems put forth here: let me know in the comments!

Avoid deep nesting by never going more than 3 levels deep (Inception)

Deep nesting causes a performance hit because it outputs long selector strings.

It messes with specificity, due to lengthy selectors which forces the creation of subsequent selectors with greater specificity to override styles further up in the cascade.

It also reduces portability and maintainability of styles.

When selectors become this long, you’re likely writing CSS that is:

  • Strongly coupled to the HTML (fragile)
  • Overly specific (powerful)
  • Not reusable
  • If you are writing well-formed HTML and CSS, you should never need to do this.

Avoiding deep nesting in React projects

  • It’s common to modularize CSS in React via stylesheet imports at the component level. This presents an inherent challenge in preventing unintended style bleed and being explicit about what styles are shared across components.
  • To prevent unintended style bleed, the root element of a component should have a unique, camelCased name (i.e. `myCustomModalComponent`). Never duplicate root element class names.
  • In the SCSS, the first line should be this class name so that all styles therein are scoped to this component.
  • Elements below the root element should follow BEM practices. As a general rule, blocks within blocks likely belong in their own React component. Nevertheless, blocks within blocks should begin a new “block”, and not extend the classname from an existing block.
  • Following this practice consistently makes it easy to move styles to a shared model as needs become more apparent as the project progresses.
  • This practice makes styles easy to read and find in the stylesheet, because there’s no deep nesting.

Recognize and codify repeating patterns (DRY)

  • Before starting, identify the different modules. Some won’t become apparent until CSS development begins. When patterns become apparent, go back and codify them
  • Use variables for recurring values
  • Use placeholders for repeating visual styles
  • Use mixins where a pattern takes variables or for repeated multiline

Name variables based on their function in the website, not their appearance.

  • For example, naming a theme color $primary-color instead of $red-color.

Use @extend for inherently and thematically related elements. Use a mixin for elements that share the same style coincidentally.

  • This allows for function over presentation. For example, if there were a contact box and reference box on a site, and they were both blue boxes but we wanted to allow some flexibility later for additional changes, we might be inclined to class the elements with contact-box and ref-box, respectively, and, say, blue-box. Not only will this muddy the CSS a bit but can become restrictive and incur technical debt as those classes are selected and styled by later development. This is a good candidate for @extend, because these are both blue boxes, and by using this with a variable we can make our CSS reference clean and strip out the blue-box from the structure.
%blue-box {
background: #bac3d6;
bordeR: 1px solid #3f2adf;
		.contact-box {
@extend %blue-box;
...
}
		.references-box {
@extend %blue0box;
...
}
		Generates CSS:
.contact-box, .references-box {
background: #bac3d6;
border: 1px solid #3f2adf;
}

Do not mirror DOM structure

  • Mirroring DOM structure makes your SCSS almost entirely un-reusable without repeating
  • It will also make it overly difficult to make changes to the HTML structure without breaking styles

Don’t overuse ID selectors

  • This poses a similar problem to #5, because changes to DOM will invalidate your styles and make them un-reusable.

Name spacing

  • Name spacing is great! But it should be done at a component level — never at a page level.
  • Also, namespacing should be made at a descriptive, functional level, not at a page location level. For example, .profile-header could become .header-hero-unit.
  • Wrong:
.nav,.home-nav,.profile-nav,
  • Right:
.nav,.nav-bar,.nav-list

Style Scoping

  • Page level overrides should be minimal and under a single page level class nest.
.home-page {
.nav {
margin-top: 10px;
}
}

Naming

  • Use .js- prefixed class names for elements being relied upon for javascript selectors
  • .u- prefixed class name for single purpose utility classes like .u-underline, .u-capitalize, etc.
  • Meaningful hyphens and camelCase — to underscore the separation between component, descendant components, and modifiers
    The component’s name must be written in camel case.
    .myComponent { /* … */ }
    <article class="myComponent"></article>
  • componentName — modifierName
    A component modifier is a class that modifies the presentation of the base component in some form. Modifier names must be written in camel case and be separated from the component name by two hyphens. The class should be included in the HTML in addition to the base component class.
  • componentName-descendantName
    A component descendant is a class that is attached to a descendant node of a component. It’s responsible for applying presentation directly to the descendant on behalf of a particular component. Descendant names must be written in camel case.
  • .is- prefixed classes for stateful classes (often toggled by js) like .is-disabled
    Use is-stateName for state-based modifications of components. The state name must be Camel case. Never style these classes directly; they should always be used as an adjoining class.
    JS can add/remove these classes. 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).
  • Meaningful variable names
    Use names coupled to the variables purpose or output rather than describing the variable value or property itself.
    I.e. $hover-color vs $colorName if the variable in question is intended for all hover state text color changes; $element-name-transition-timing vs $transition for the transition property's time value in regards to a particular element; $success-color vs $green, etc.