What is 'Component Seep', and how to solve it by Jack Oliver

There are a lot of beautiful ways of organising design systems and code out there. Atomic Design, BEM, OOCSS, SMACSS. They're all super awesome in their own special way, but there's a phenomenon I have encountered recently that has raised a problem none of these can yet address. I've called it 'component seep' for lack of a better phrase (feel free to coin a new term).

Here's an example, just to get you into the concept of what we're talking about.

See the Pen Component Seep Example #1 by Jack Oliver (@jackoliver) on CodePen.

So as you can see in this example. The presence of a sidebar changes the content around it. This seems pretty obvious, but goes against the principles of components being completely self-contained, when they can affect the objects around them. We have a real-life example of this working today in a project that I maintain, and in the beginning it was fine, since the content inside the main area was scalable.

However;

When it came to making the entire view responsive (we were launching a mobile offering and so had to tackle this), the nightmares started.

Because the page has essentially 3 states in this case (2 sidebars, 1 sidebar and no sidebars), it meant 3 sets of media queries for every item. Something we affectionately called 'nam and nom' (in our case the left being a navigation, and the right being a list of monitors). So we had 'navigation-and-monitors', and 'navigation-or-monitors'. We ended up with this horrific piece of code;

//   Navigation 1 - 0 Monitors
// & Navigation 0 - 1 Monitors

.navigation-active,
.monitors-active {
  @content;
}

// Navigation 1 - 1 Monitors

.navigation-active.monitors-active {
  @content;
}

Combining this with the mq($width) mixin we had, and we had a workaround, but it was messy. All of a sudden, media queries couldn't be localised to a component, and had to instead be put well away from where they needed to be in order to be maintainable and understandable.

Then I stumbled upon @at-root, which totally blew open the problem.

What is @at-root?

The @at-root directive causes one or more rules to be emitted at the root of the document, rather than being nested beneath their parent selectors. It can either be used with a single inline selector:

Basically, in this case it lets you jump out of indentation to keep things localised.

How did it solve the problem?

//   Navigation 1 - 0 Monitors
// & Navigation 0 - 1 Monitors
.component {
  @at-root {
    .navigation-active,
    .monitors-active {
      @content;
    }
  }
}

// Navigation 1 - 1 Monitors
.component {
  @at-root {
    .navigation-active.monitors-active {
      @content;
    }
  }
}

See the Pen Component Seep Example #2 by Jack Oliver (@jackoliver) on CodePen.

This meant that we could then treat these visual problems and still localise code to the components that they affected. I think in terms of a principle, there is more discovery to be made here, and there are a few people doing some interesting work around 'element queries', but it's certainly a concept that needs to be considered when building interfaces.

Further reading

  • http://sass-lang.com/documentation/file.SASS_REFERENCE.html#at-root
  • http://elementqueries.com/

Thanks to Brad Frost for giving me the urge to write this, and Sebastian & Ewan for proofing it and making sure I'm not full of shit.