CSS Architecture Guidelines

Writing CSS in imperative style can quickly effect in:

  • lack of clarity — what bring tons of confusions e.g. not clear dependency (we are afraid about removing and adding things),
  • no maintainability — inefficiency while creating new and improve existed code,
  • duplications — makes things very bloated and has negative impact for performance,
  • lot’s of problems with scalability, consistency and on-boarding new developers.

In Result we often have messy UI & CSS. Not mention developer anger! That’s gonna lead us to messy UX. Let’s see what could be done in order to prevent all those issues.


Specificity

Specificity battles between selectors is always reflected in poor code quality. Having a low specificity will help maintain the integrity and performance of a large CSS project’s for a lot longer.

First of all avoid unnecessary deep selectors. You really don’t need selectors like this in order to style an arrow:

.page-quick-sidebar-wrapper .page-quick-sidebar .page-quick-sidebar-chat .page-quick-sidebar-chat-user .page-quick-sidebar-chat-user-messages .post.out .message .arrow {

}

We can use more meaningful selector like .message-arrow { … } and apply this class instead of .arrow? Or just write styles for .arrow if there is only one arrow to style?

In most cases It’s a SASS/SCSS nesting trap. Nesting shouldn’t be more than 3 levels deep. Stop tempting to make the SASS nesting match the html nesting. Don’t try to show CSS selectors visual hierarchy (it looks nice but generates maintenance cost). It’s in our interests not to style things based on where they are or how they behave, but on what they are (location independence).

Creating selectors

IDs overpower class names by orders of magnitude. For this reason you can’t override an ID selector’s style with a class name selector easily. Avoid IDs as hooks for styling but if you need them for other things (like JS hooks) use them. It seems that classes are best solution while creating selector. What about tag selectors? You can use tags only for reseting and applying common styles. Using !important is not bed practice until “you are not using it with anger”. It fits perfectly for your utility classes.


Naming

Naming convention is beneficial for immediately understanding which category a particular style belongs to and its role within the overall scope of the page.

There are rules that apply to any naming convention:

  • name something based on what it is, not how it looks or behaves.
  • in general it’s better to have longer class name than more complex specificity.

In most cases it’s nice to use prefixes for classes and id’s. Using prefixes and/or naming system helps to recognize between framework and custom classes/id’s. What’s more by doing that you can easily avoid naming collisions (e.g. you want to implement date picker which add to global classes like .next or .prev. What will happen if you have used those class names already?). So how to use prefixes?

  • .u-hide-text (“u” communicates that it’s utility class with a specific purpose. It may be used in many places and should not be extended. Typically you want to make sure that utility class will always win specificity battle. Therefore using !important it’s not bad practice for that case.)
  • .l-main-nav (“l” means layout or component class. Used to group modules into components and position them within the page)
  • .b-main-nav (“b” prefix can be applied to main blocks/modules wrappers)
  • .js-header-animate (“js” communicates that it’s class used only by JavaScript. It prevents from accidentally breaking behavior or breaking tests based on class name)

BEM

One of more popular naming convention is BEM. By using this naming convention you are automatically enforced to think about layout as a set of independent modules rather than „how do I style this page”. Few general BEM rules:

  • you want to use only classes to build your selectors. In most cases your selector is one unique & meaningful class.
  • class name starts with name of block/module, next one is name of element (preceded by two underscores), ends with modifier name (preceded by two dashes). e.g. .main-nav__item — current
  • modifiers can be applied either to blocks and elements.
  • can’t have element outside it’s block
  • you can nest elements within its block, but in that case do not write selectors like .block__element1__element2. Always use: .block__element1 .block__element2
  • block are independent and share only most common styles (resets and a few base styles). This reduce amount of code and make it reusable.
  • you don’t want to apply positioning styles directly to block/module. This is why we want to use layout classes.

Thanks to BEM we have:

  • modular CSS code. It makes CSS easy to convert to React world where you often want to have separate css file for each component.
  • very low specificity,
  • decoupled HTML structure from CSS,
  • improved readability (you can understand much more about CSS while looking at HTML)
  • no more naming collisions (isolated modules helps to avoid putting CSS into global scope).
  • clear rules how to name new elements inside a block
  • no more problems with cascade inherited styles (accidental leaking of styles to places where you don’t want them).
  • smaller HTML by reducing classes and making HTML reusable.

Mobile First

Why this concept is beneficial? By doing mobile version as first you are enforced to think about content hierarchy. You want to deliver the most needed functionality & content in best possible way (simple and effective solutions). It has a positive effect for desktop version. Secondly, it’s easier to add than remove.

Writing MediaQueries

Using predefined breakpoints (like bootstprap uses) is quite good for grid managing but not necessarily for changing modules look. It’s completely fine to add some custom breakpoints. We want our webapp to fit nicely in wide range of resolutions. Not only for those ones defined by grid framework. In general it’s neat strategy to use “min-width” for brakepoints. Try to avoid brakepoint with “max-width” only. By doing that you will end up with less code, less overrides and less !important declarations.

Components, Modules and Elements

As far as possible try to build isolatable modules and components (collection of modules). While creating similar modules remember about using DRY principle in a proper CSS way. See more here and here.

What are differences between modules, components and elements?

Component is a set of modules. In most cases it gonna be this same level of abstraction as layout class.

Module is a set of Elements. Should be reusable and don’t depend on other modules.

Element is fragment of code that cannot be used on its own and need parent entity.

e.g. Header is component. It contains modules — navigation and logo modules. Navigation has items elements. Logo has image and text elements.

Files & catalogs structure organisation

In order to create maintainable CSS architecture we need something more than one huge main.css file and vendor catalog. Description presented below base mainly on SMACSS concepts.

Fist of all we will need a preprocesor for compiling CSS files (after each save), creating dependencies and teach CSS new tricks. Due to debugging purposes we don’t want to use minification for development env.


Dependencies

We want to load our CSS in given order:

  1. vendor styles (frameworks, plugins styles)
  2. Next ones are our utils. We want to load all variables, mixins and other helpers (classes, placeholders).
  3. If we don’t use a frameworks which provides CSS reset then we definitely want to include normalize.css (or similar) plus our custom resets and base styles.
  4. Finally we can include layout and modules styles.

Project structure

In order to ensure layout consistency we should strive to keep (only) shared css in global.

— vendor globally used tools

— foundation CSS reset (like normalize) and base styles. “Base styles are the defaults. They are almost exclusively single element selectors but it could include attribute selectors, pseudo-class selectors, child selectors or sibling selectors. Essentially, a base style says that wherever this element is on the page, it should look like this.”

— dependencies globally used: variables, metrics, colors, typography and forms styles, SASS mixins and functions, debugging tools.

— modules are the reusable, modular parts of our design. Always use one file per module.

— layout Wrappers for modules. Layout divide the page into sections and hold one or more modules in place, allows to position them within the page. This is the only place where you can add margins, width, height etc. So layout cooperate with modules but modules are independent units and can be used in any context.

— state are ways to describe how our modules or layouts will look when in a particular state. Is it hidden or expanded? Is it active or inactive? They are about describing how a module or layout looks on screens that are smaller or bigger. They are also about describing how a module might look in different views like the home page or the inside page.