There's no shortage of posts about preprocessors, and more specifically about LESS. Now that I've been working with preprocessors for a few years now, I feel good about sharing the features that I used the most and find the most useful.

Variables and Mixins

Arguably the two biggest reasons to use a preprocessor are variables and mixins. The power of these features are at this point well documented, and people have become very creative with their usage.

It's pretty common to use variables in the context of colors, values that are used frequently, and even for class names. It's just as common to use mixins as a way to pass variables into them like functions. However, the large scale project I worked on regularly in my last agency job pushed me to think creatively about how to use variables and mixins, and how to make them usable (something I had not considered before).

I've talked about how to use variables for established media queries. While this was very useful, it was only the start of how I would experiment with variables and mixins. Rather than thinking about reusing individual variables, I started thinking about how to reuse entire design patterns leveraging variables and mixins.

The Project Backstory

This large-scale project I keep talking about throughout my posts was truly gigantic. It is internationalized for over 20 countries, has a back-end system that pulls data from all over the place, displays data to the front-end dynamically based on very complicated information architecture, user interactions, and requirements. What Vodori has created in terms of a product for this client is truly amazing!

Now that we have glimpse of context around the depth, and size of this application, we can understand the need for identifying design patterns, and being able to reuse them.

Design Patterns?

As with everything in this space, "Design Pattern" means many different things to different people. In my case, I see a "design pattern" as a set of attributes, be it design, or data, that are reused in many contexts. Since I'm a front-end developer, I'll use an example of a visual pattern.

  header {
  padding: 1em 2em;
}

section {
  margin-top: 3em;
  margin-bottom: 3em;
  padding: 1em 2em;
}

footer {
  margin-top: 3em;
  padding: 1em 2em;
}

In this case, we want the padding of the header, section, and footer to be the same, so our content is displayed consistently. The padding: 1em 2em line is an example of a design pattern because we are using it in multiple places.

Since we can recognize this as a design pattern, we can turn it into a mixin.

  .content-padding() {
  padding: 1em 2em;
}

Now, we can reuse this design pattern without having to declare and remember the padding values. It's much easier to remember "content-padding". As a bonus, this type of modularity adheres to DRY!

  header, section, footer {
  .content-padding();
}
/* Note: Even though we're not passing anything into the mixin, 
I still like to add the parens so it's easy to identify it as a mixin, pattern, or object */

The beauty of this approach is that if we discover a new design pattern that pertains to the content-padding mixin, all we have to do is add it to one place!

The problem: for semantics, we may need to rename the mixin if the design pattern strays from whitespace. Or perhaps there's a better way. This would be a good time to talk about "Design Objects".

The problem: Patterns with different functions

When I was going through this exercise of using design patterns, I found that quite often, I would make a set of design patterns that were not related in function. Rules around whitespace, typography, and positioning quite often would get mashed together into a mixin that would get used in many places, forcing us to override them in edge cases. (Cringe)

The way we handle this is to go back to basics with Object Oriented CSS principles.

Object Oriented CSS Principles

The idea with OOCSS is to decouple rules into modular objects, which for CSS usage, are injected into the HTML. Since we're using LESS, we can use this same principle without injecting HTML classes.

Again, there are many ways to do this, but I have a general method for abstracting patterns. I try to not cross-pollinate these patterns so they can stay abstracted by function.

  • Typography: color, text-decoration, font-family, font-size, line-height
  • Positioning: floats, TRBL (top, right, bottom, left) positions, absolute, relative, z-index
  • Whitespace: padding, margin

Naming Convention: Slightly Modified BEM

Using a naming convention is critical in ensuring your code is scannable, intuitive, and maintainable. This is especially important when creating patterns and objects. The developer looking at this code needs to be able to see relationships between patterns and objects at glance, without too much explanation. (Although, writing comments is extremely important too!)

I loosely adhere to the "Block, Element, Modifier" naming convention (BEM) for naming classes. This methodology was designed for large applications (althought it works very well for smaller projects too), and I can attest that this method works very well in that context. Here's an example of a slightly modified version of BEM:

  //"content-section" is the block with dash separating words, and "typography" is the modifier
.content-section--typography();

//"content-section" is the block with dash separating words, and "positioning" is the modifier
.content-section--positioning();

//"content-section" is the block with dash separating words, and "whitespace" is the modifier
.content-section--whitespace();

Patterns and Objects in Practice

Let's take the original example. Now we can see how the design patterns are making up the design object.

  // The design object
.content-padding() {
  // The design patterns
  padding: 1em 2em;
}

header, section, footer {
  .content-padding(); // The design object
}

Now, let's say we want to add more patterns to the design object. Let's add some typography patterns that we can reuse. Now that we're creating patterns with different functions, let's rename the patterns so they are more semantic.

  // Variables
@brand-font--primary:       'Open Sans', Helvetica, Arial, sans-serif;
@brand-color--body-copy:    #4a4a4a;

// Design Patterns
// Let's keep the .content-padding() but use it as a design pattern, not a design object.
// Also, let's rename it to something more semantic and appropriate.
.content-section--whitespace() {
  padding: 1em 2em;
}

.content-section--typography() {
  font-family: @brand-font--primary;
  color: @brand-color--body-copy;
  font-size: 1em;
  line-height: 1.75em;
}

// Design Object
.content-section--patterns() {
  .content-section--padding();
  .content-section--typography();
}

header, section, footer {
  .content-section--patterns();
}

Patterns as objects

You might thinking: this is too granular! You might be right. On some cases, it might be too risky to add all the patterns into one object. In this case, we should use our patterns as objects. Essentially, all the patterns are objects and can be used as such.

Here's an example of using patterns as objects.

  // Instead of creating a design object, you could just apply the design patterns as individual objects.
// You may need to do this in some cases, especially in the case of pattern variation.
header, section, footer {
  .content-patterns();
}

// The Sidebar has slightly different typography rules, but the same padding rules
// We can apply the padding design pattern, but apply different rules for the typography
// Let's hash out those typography rules as a new pattern.

@brand-color--sidebar-body-copy: #cccccc;

.sidebar-typography() {
  font-size: 0.85em;
  line-height: 1.25em;
  color: @brand-color--sidebar-body-copy;
}

aside {
  .content-padding();
  .sidebar-typography();
}

We could have wrote rules for sidebar typography, but it's smarter to convert the set of typography rules into a pattern so we can reuse it later if we need to.

Be careful! I strongly recommend not reusing patterns if your intent is to only use some of the rules. This breaks the idea of a pattern, and causes messy situations where you have to override.

  .content-padding() {
  padding: 1em 2em;
}

.content-typography() {
  font-family: @brand-font--primary;
  color: @brand-color--body-copy;
  font-size: 1em;
  line-height: 1.75em;
}
// ---------------------------
// Don't do this!
// ---------------------------
aside {
  .content-padding();
  .content-typography();
  // The content-typography has most of the rules I want, except for the color.
  color: red;
}

While it may be trivial in this example to override one of the design pattern rules, this way of thinking can lead to messy overriding problems that can be difficult to keep track of, and or troubleshoot. This is especially true when working in a large application.

A Learning Experience

Aside from using mixins and variables to make patterns and objects, the exercise of pattern recognition has a lot of value. Working with designers in the beginning stages of projects to uncover these patterns greatly improved my development workflow, and understanding of OOCSS.


1,664 0 5