How to style different sites with one CSS collection

How one CSS collection can define the styles for multiple sites that look different.

TL:DR; summary:

Here’s the problem.

In the beginning of 2018 we planned to launch a new presentation layer service for story pages on Business Insider and INSIDER. The project was called, “birta” (from Viking Old Norse meaning, “to display or unveil”).

Business Insider and INSIDER story pages have similar layouts. However, the CSS is mostly different.

Lots of questions came up during our planning. One of them was, “What’s the best way to architect the sites’ CSS?”

“Do we fork our CSS collection for each site?”

“Or do we drive two sites with the same CSS? And is that even possible?”

Ack, there’s a problem with the problem!

Forking codebases creates a big problem.

Inevitably, forked codebases diverge, which increases complexity when implementing synchronized multi-site changes, like a new logo, font changes, or redesigns.

At first, maintaining two codebases can be manageable while they have parity. However, each time you add a site, each codebase adds more overhead and maintenance.

Time costs magically increase exponentially for each additional website. For example, the time cost of changing two sites is more than double the time of changing one site:

It gets worse.

Driving multiple sites with the same CSS collection creates an entirely different set of problems.

How do we handle two different site designs?

And how do we style unique elements and modules?

What if this approach is rigid and fragile? What if it reduces flexibility, especially for different page layouts?

At first glance the idea of driving multiple sites from a single CSS collection may seem efficient, but it could also introduce unnecessary complexity.

Worthy problems to consider.

Here’s a thought.

What if these are the right kind of problems?

Said another way: what if progressing through those problems with a goal of building a sustainable, efficient, and scalable CSS collection would help us become better engineers?

What if we become awesome as we face these problems head on?

What if these obstacles help us write incredibly well designed CSS?

A solid foundation.

We decided to drive two sites from the same CSS collection.

But how did we build one collection of CSS to define styles for multiple sites that look different?

And how did we architect our CSS to support multiple sites without forking it?

Our answers began with two important foundational decisions:

First, we decided to implement a CSS methodology called SMACSS, which stands for Scalable and Modular Architecture for CSS. The creator of SMACSS, Jonathan Snook, describes it this way:

SMACSS is a way to examine your design process and a way to fit those rigid frameworks into a flexible thought process. It is an attempt to document a consistent approach to site development when using CSS.

Second, we decided to variable-ize CSS property values when necessary.

Our original site was written using standard CSS over ten years ago. For the new codebase, we upgraded to SCSS which, among other things, supports the use of SCSS variables.

By combining the powerful methodology of SMACCS with the robust flexibility of SCSS variables, we architected a robust, scalable CSS framework capable of driving multiple sites simultaneously.

Here’s how it works.

In this example, we’ll style two different looking sites using the same CSS.

Visually, here’s what the difference looks like:

First, here’s the style for Site 1’s nav and footer:

// Site 1
.nav {
color: #fff;
}
.nav-logo {
background: url(images/site-1-logo.svg);
}
.footer {
background: transparent;
}

And Site 2’s styles:

// Site 2
.nav {
color: #fff;
}
.nav-logo {
background: url(images/site-2-logo.svg);
}
.footer {
background: #f00;
}

Next, we’ll create a new file called _styles.scss. It will drive the styles for both sites, even though they look different.

Since both sites use the same text color for .nav, we won’t variable-ize it.

The other classes have unique property values. For those we’ll state the property, but leave the value empty temporarily. You’ll see why shortly:

// New file: _styles.scss
.nav {
color: #fff; // same for both sites
}
.nav-logo {
background: ;
}
.footer {
background: ;
}

Variable-ize all the things.

Next, let’s create a SCSS variables for properties with unique values.

We’ll follow this pattern:

  1. Name the SCSS variable using the class and property names.
  2. Define the variable in two new SCSS files. One for each site.

Again, notice we don’t variable-ize the color property value of .nav because it’s not unique. Both sites use the same nav text color, so we don’t need to write a variable for it.

However, we do need variables for the other properties. So, let’s create a SCSS variable called $nav-logo-background for the background property value of .nav-logo:

// File: _styles.scss
.nav {
color: #fff;
}
.nav-logo {
background: $nav-logo-background; // new variable
}
.footer {
background: ;
}

And in two new files we’ll define the unique values of that variable for each site:

// New file: site-1/_variables.scss
$nav-logo-background: url(images/site-1-logo.svg);
// New file: site-2/_variables.scss
$nav-logo-background: url(images/site-2-logo.svg);

Finally, we follow the same pattern to create a SCSS variable for the footer background, called $footer-background:

// File: _styles.scss
.nav {
color: #fff;
}
.nav-logo {
background: $nav-logo-background;
}
.footer {
background: $footer-background; // new variable
}

And define it uniquely for both sites:

// File: site-1/_variables.scss
$nav-logo-background: url(images/site-1-logo.svg);
$footer-background: transparent;
// File: site-2/_variables.scss
$nav-logo-background: url(images/site-2-logo.svg);
$footer-background: #f00;

Boom. Two different sites. One CSS stylesheet.

At this point, several questions arise. We’ll address some of them shortly.

Feel free to skip ahead.

But first, let’s look how to configure the core CSS for each site.

Setup the core CSS.

For the final step, create a core SCSS file for each site to compile the CSS.

In a new file site-1/core.scss, import the Site 1 variables first. Then import the styles:

// New file: site-1/core.scss
// Import site 1 variables
@import 'site-1/variables';
// Import the styles
@import 'styles.scss';

Do the same for Site 2. Create site-2/core.scss, which imports the Site 2 variables, followed by the style rules:

// New file: site-2/core.scss
// Import the site 2 variables
@import 'site-2/variables';
// Import the styles
@import 'styles.scss';

The order of @import statements matters here. Reverse the order, and the compiler will complain about variables that haven’t been defined yet.

The results are in.

The output for Site 1 looks like this:

.nav {
color: #fff;
}
.nav-logo {
background: url(images/site-1-logo.svg);
}
.footer {
background: transparent;
}

And the output for Site 2 is:

.nav {
color: #fff;
}
.nav-logo {
background: url(images/site-2-logo.svg);
}
.footer {
background: #f00;
}

While this example is straightforward, it illustrates a powerful concept. One CSS collection can drive two differently designed sites at scale.

A more in-depth look could prove helpful at this point, but for the sake of brevity we’ll move on.

Now to address a few questions.

“Won’t this create lots of variables? Isn’t that a headache?”

Yes and no.
Yes, there are a ton of variables.
No, it’s not a headache if your CSS is well organized.

When employing the SMACSS methodology, the CSS for every module lives in its own file. This pattern creates a scalable approach to writing and editing CSS.

Likewise, for ease of maintenance and readability, we organize the SCSS variables in groups based on module names, similar to the structure of a configuration file.

For example, let’s say we’ve written CSS for these modules:

_nav.scss
_login.scss
_footer.scss

The SCSS variables for Site 1 and Site 2 both use the same format and organization:

// Note: both files (site-1/_variables.scss)
// and (site-2/_variables.scss) follow this format:
// Nav Module
(SCSS variables here)
//
// Login Module
(SCSS variables here)
//
// Footer Module
(SCSS variables here)

“Yeah, but does it scale?”

Yes, this approach scales smoothly.

In fact, it’s designed specifically for scaling well. So far, our experience has proven as much.

That said, there are times when a block of CSS doesn’t scale, which often indicates poorly designed CSS rather than a problem with the framework. Most of the time it involves legacy code, or third party work.

Briefly, here are some symptoms of poorly designed CSS that won’t scale well:

  • It’s rigid: complex sector specificity or specificity collisions.
  • It’s fragile: tight coupling to HTML.
  • It’s monolithic: non-reusable class styles.

By the way, you know your framework is serving you when it reveals problems which require you to think through to a solution using best practices.

The opposite is no fun: a framework with inherent obstacles to best practices. Or even worse, one that requires solutions contrary to best practices.

“What if a module exists on Site 1, but not Site 2?”

This question sounds more complicated than it actually is.

The solution is simple: don’t include CSS for site-specific modules on sister sites.

As an added bonus, configurations like this boost site speed performance.

Site 1 doesn’t need a unique-module:

// Import modules
@import
'nav',
'login',
'footer';

However, Site 2 does:

// Import modules
@import
'nav',
'login',
'unique-module',
'footer';

There are other great questions worth discussing. However, this concludes a brief, hopefully helpful, look into driving multiple sites from a single CSS collection.

Thank you!

Hopefully some of these ideas will serve your team, planning process, or upcoming CSS refactor.

Thank you for reading!

Got ideas or feedback?

I’d love to hear from you if you have questions or suggestions. Especially if there’s stuff you’d like to know more about, or if something was confusing.