DEV Community

Johannes Millan
Johannes Millan

Posted on

Bum(S)CSS – Conventions for writing lazy and pragmatic CSS

bumcss (wip)

Best Utility Modularization CSS - for the lazy and pragmatic

Introduction

bumcss is a (S)CSS methodology of writing (mostly) semantic, scalable and maintainable CSS that aims at the most expressive semantic html, with least amount of code to write. The approach is specifically useful when developing Single Page Applications using JavaScript frameworks such as Vue.js, React or Angular or web components, where getting a quick grasp on what's happening is most important for development, but it can be also used for classic web pages.

bumcss It's inspired by BEM, SMACSS and ITCSS. The problem with all three approaches is that they require you to assign a lot of classes (aka classitis) which tend to have long names. Too much code in my humble opinion which potentially not only slows down loading times, but more importantly increases development time and makes the code less readable, because there is simply so much more of it.

Why conventions are so important

A lot has been written on that. They are! And (S)CSS has been traditionally the place where there are neglected the most.

Very basic example

<button class="btn _primary">
  Hello
</button>
// btn.scss
.btn {
    display: inline-block;
    border: 1px solid gray;
    padding: 5px;

    @media(min-width: 780px) {
      padding: 10px;
    }

    &:hover {
      border-color: black;
    }

    // variant styles
    &._primary {
      background: blue;
      color: #fff;

      &:hover {
        background: lightblue;
        color: black;
      }
    }
}

UI Components, Wrapper Components and Default Styles

Let's start with the very basics. A typical application will - for the most past - have three types of elements.

Global Default Styles for basic elements such as tables, links, headings, paragraphs, strong, etc. They should be imported first so they can be easily overwritten if needed. Having well designed foundation of base styles and limiting yourself to fixed set of e.g. heading styles, will save you a lot of work in the long run.

Wrapper Components/Logical Components such as pages, or logical sections, let's say a or a shopping cart or a map component. Always block elements in the spirit of BEM. In a single page application those components will likely be the place to distribute data throughout your UI Components.

UI Components such as a button, tabs or an accordion and so on. They only should have presentational logic and can be block elements, as well as inline elements.

In the spirit of block, element, modifier

Almost all of your ui components should be responsive. You should be able to place them in different sizes of responsive and non-responsive containers, without breaking their styles.

Integrate with ITCSS

The class naming conventions of bumcss are a good fit for the general approach of ITCSS. You can read more about it here.

Order of styles: Simple UI Component Example

The fundamental principle of bumcss is that all styles belonging to element should be grouped as closely together as possible.

Some Code says more than 100 lines of theory. So here is some practical example:

<button class="btn _primary-variant">
  <span>hello</span>
  <icon>fish</icon>
</button>
.btn {
    /* Different states & variants of the element itself */
    // NOTE: variant names should either always be adjectives (while element names,
    // e.g. btn, should never be) or they should have a prefix like "_"
    &._primary-variant {}
    // camel cased and usually prefixed with "is" or "has" to distinguish them visually
    &.isVisible {}
    // pseudo states
    &:hover {}

    // parent modifiers, used for themes or for parent states & variants
    .global-theme-class & {}
    .parent._variant & {}
    .parent.isExpanded & {}

    // pseudo selectors
    &:first-child {}

    // sibling selectors (probably rarely used)
    ~ .btn {}


    /* Sub element selectors
    While these should normally be avoided, there are two exceptions:
    1. Child elements and pseudo elements that usually don't have any styling on them.
    2. Positioning of global ui elements of global ui components and default elements.
    This is fine, because they normally should not have any positioning in the
    first place or can be easily overwritten in the case of default styles.*/

    // pseudo elements
    &:after {}

    // normally unstyled sub elements
    span {
      font-size: 20px;
    }
    &._variant span {}
    &.isVisible span {}

    // sub ui elements which are only positioned or shown/hidden,
    // e.g. margin, position, left, top, right, bottom, display, visibility, flex
    icon {
        position: absolute;
        right: 10px;
    }
    &._primary-variant .icon {}
    &.isVisible .icon {}
}

CSS Specificity: Complex Component Example

One of the main arguments for BEM is, that by using only a single class everywhere you won't run into problems where you need to go deeper and deeper to overwrite a style property (For more info on CSS specificity have a look here and here).

Let's explore how specificity will look like in a bumcss component.

<tabs>
  <tab class="_hero">
    <tab-heading>heading <icon>fish</icon></tab-heading>
    <tab-content></tab-content>
  </tab>
  <tab class="_accented">
    <tab-heading class="_empha">heading <icon>icon</icon></tab-heading>
    <tab-content class="_boxed"></tab-content>
  </tab>
</tabs>
// components/tabs/tabs.scss
// or for global scss: scss/components/tabs.scss

// el S001
tabs {
  // ...
}

// el S001
tab {
  // states S011
  &.isActive {
  }

  // variant S011
  &._accented {
    // variant + state S021
    &.isActive{}
  }

  // variant S011
  &._hero {
    // variant + state S021
    &.isActive{

      // DON'T DO THIS, if you don't have to, don't nest elements
      tab-heading {}
    }
  }
}

// el S001
tab-heading {
  // state (pseudo selector) S011
  &:hover{
    color: blue;
  }
  // parent state S012
  tab.isActive & {
    color: red;

    // parent state + state S021
    &:hover {
      color: darkred;
    }
  }

  // parent variant S012
  tab._hero & {
    font-size: 20px;

    // every sub element and variant gets its own media query
    @media (max-width: $screen-xs) {}
  }

  // child el S 002
  icon {
    margin: 5px;

    // parent variant S013
    // a bit ugly, but probably not needed very often
    @at-root tab-heading._empha & {
      transform: scale(2) rotate(13deg);
    }

    // parent parent state S013
    tab.isActive & {
      transform: rotate(360deg);
    }
  }
}

// el S001
tab-content {
  // parent state S012
  tab.isActive & {}

  // ...
}

As you can see that specificity and conflicting styles are not a problem most of the time if you stick to the described pattern. The only exceptions are the "parent variant" & the "parent parent variants" and "variants" vs "states". It's up to you, what you think, should have prevalence.

Variants vs States

Personally I think it's nicer to write states first, because they are closer related the the default main element while variants could be considered more of a new instance of the same element. On the other hand I think that states normally should have prevalence over variants. Most of the time this shouldn't be a problem, as states are more likely to focus on transforms and visibility, or in other words, what a component does on the page, while variants are more likely to focus on properties that change the general appearance such as font-sizes, colors, borders and box-shadows.

Having them so closely written together, should help you quickly identifying possible conflicts and if you think that a state should have prevalence over a variant in any case, it's probably ok to use !important for something like el.isHidden {display: none !important;}, as you are likely to want to apply display: none a 100% of the time (but only do this if you're sure it is really a 100%).

Parent Parent States/Variants vs. Parent States/States Variants

Probably not a problems most of the time, as it's unlikely too have many combinations of those. But as with with "Variants vs. States" writing them closely together, should help you to quickly identify problematic areas and order them accordingly if needed.

Wrapper/Logical Component example

<div class="product">
    <header>
      <h1>Product Header</h1>
    </header>

    <section class="product-info">
      <h2 class="_fancy">Product Info</h2>
      <tabs>
        <tab class="_hero">
          <tab-heading>heading <icon>fish</icon></tab-heading>
          <tab-content>
            <h2>Description</h2>
            <div class="product-description">
              <p>...</p>
              <p>...</p>
            </div>
            <button>Buy</button>
          </tab-content>
        </tab>
        <tab class="_accented">
          <tab-heading class="_empha">heading <icon>icon</icon></tab-heading>
          <tab-content class="_boxed">
            <h2>Features</h2>
            <ul class="features">
              <li>...</li>
            </ul>
          </tab-content>
        </tab>
      </tabs>
    </section>
</div>
// global default styles in their own file(s)
// should be included first so they can be overwritten
h2 {
  // S 011
  ._fancy {
    // ...
  }

  // DON'T DO THIS
  // Global default styles should be applied regardless of context
  header &._fancy{
  }
}
.product-info {

  // remember adjusting positioning is ok, everything else should be a variant
  // S 011 => overwrites default styles, even for variants
  h2 {
    margin-top: 0;
    margin-bottom: 20px;
  }

  tabs {
    margin-top: 30px;
  }
  // DON'T DO THIS
  // this probably better done inside the file for the tabs as a variant
  // e.g. tab-heading.blue {}
  tab-heading {
    color: blue;
  }
}

File and Folder Structure

This depends strongly on if and which JavaScript framework you use. But general rules of thumb are:

  • create a global scss folder, for things like default styling of:
    • headings
    • text (e.g. p, em, i, b, strong, etc.)
    • tables
    • inputs
    • buttons
    • links
    • (optional) a folder where you put the css files of libraries or overwrites of those styles
  • you normally shouldn't have to overwrite those global styles too often. If you do that's a strong sign that things are messily designed in the first place. So go and talk to your designer! ;)
  • grouping files by component is preferable to grouping files by type
  • global default styles should be imported first, so they can be overwritten

General Tips and Tricks

  • Having a consistent system for vertical margins helps. Use scss variables or mixins.
  • Use variables or mixins for different font styles, but prefer using the default tags e.g. h1, h2, p, strong, table, etcs. where possible
  • Having a good system for the base styles will save you a lot of code
  • Get in the habit of extracting ui components when you recognize a pattern very often, but also don't overdo it. If you find yourself with a button component with 50 different variants, chances are that something went wrong.
  • Talk to your designer if you recognize inconsistencies you don't understand. Chances are they are not there on purpose.

Grids

Grid utility classes are handy in my opinion while you give a quick indication on what's going on. Not semantic maybe but practical, so it's totally fine to use them.

A word on utility classes

Personally not the biggest fan and I would recommend a healthy bit of scepticism, but if you feel, it's very useful for your particular case, let's say for vertical margins or different font styles and sizes then use them. But if you do so, be consistent. Mixing multiple approaches can lead to chaos.

Global Box Sizing

Yes, please!

Top comments (5)

Collapse
 
amiangie profile image
am i angie?

I feel like we have been through all this before, maybe even a couple of times :) The fact that you're taking into consideration order of styles is great, have a look at ITCSS, which takes it a step further.

What caught my eye are modifier classes like _blue or _big. In my experience, it is better to avoid naming things based on their appearance, and instead stick to their semantics, so eg button becomes _primary or _secondary. While _blue might seem like a great name at the time, what happens if design changes and _blue button becomes red? You'll need to either refactor all of the code to change class names to _red, or live with inconsistency between naming and appearance.

Collapse
 
johannesjo profile image
Johannes Millan

Reading a little bit more about ITCSS I figured that they are using almost the same naming conventions as BEM. Imho this makes the html very convoluted and reading and writing it harder than it should be. On the other hand I like the little pyramid. Combine both approaches with each other should be no problem.

Collapse
 
amiangie profile image
am i angie?

Personally, I think BEM is great and not hard to write or read at all - but that's indeed the thing, you don't have to use it in pure form with ITCSS if you don't want to; you can combine inverted triangle approach with pretty much any naming convention you want.

Glad I could help :)

Collapse
 
johannesjo profile image
Johannes Millan

Thank you very much for the suggestion. You're right about the class names and I haven't heard about ITCSS before. It's very interesting stuff though I've yet to find an article digging deeper into the class structure. I probably will work this into the approach one way or another.

Collapse
 
johannesjo profile image
Johannes Millan

Please let me know what you think!
I also should mention that the source of this document is available here:
github.com/johannesjo/bumcss