Simple Methods For Writing Scalable CSS Right Now

category-css

With CSS there's always a struggle when attempting to write styles that are maintainable, low in specificity, and efficient.

From much reading into BEM, SMACSS, OOCSS, and other methodologies which aim to modularise the way CSS can be written, I've begun to develop a style which I have found to be incredibly useful in tackling these issues.

In this post I will briefly cover methods for reducing specificity, giving the freedom to style what you want how you want, and for making your styles more navigable and easy to remember.

1. Understand The Natural Behaviour Of Elements

Understanding the natural display properties of elements is invaluable in getting your markup to do the heavy lifting before you even apply any styles.

Block level elements, such as divs, will occupy the full width of their container, pushing siblings to new lines.

This is simple, but powerful information. With this in mind, you'll know that if you want any single element or block to be the only occupant of a line, you can simply wrap it in a div, or other block level element.

Inline level elements, such as spans, only occupy the space that their contents occupy. Inline elements will sit next to each other as far as their container will allow.
Inline-block level elements shrink-wrap around their contents like inline level elements, while allowing vertical manipulation of margins and height like block level elements.

Pow! You're using CSS without writing CSS!

2. Normalise Your CSS - Resets Are So Last Century

You need a solid base for all your projects, but a reset is like dropping a nuke on a school to build a school. A reset will give you extra work, a shaky foundation, and leaves some elements bare when styles are not re-applied.

A normalise, however, will simply make all browsers behave similarly when displaying any elements. Kiss the majority of those quirky browser styles goodbye.

Better yet, take a look at how HTML5 Biolerplate has integrated normalize.css. Make sure to watch the introduction by Paul Irish on how to employ best standards web development with the HTML5 Boilerplate.

3. Understand How To Tame Specificity

Keep your selectors short

Short selectors greatly reduce specificity issues, and make your stylesheets more scalable. Shorter selectors are easier to reuse than longer convoluted selectors are.

The optimal depth of a selector for reducing specificity is one class. One class!

Harry Roberts covers excellent points in his article on CSS selector length.

Never use id's in your styles.

EVER.

Id's are a CSS specificity nightmare. One sure way to end up adding !important all over your stylesheets is to use id's where they are not needed.

Use id's for hooks for efficient DOM traversal for JavaScript, frag id's, and other non-styling needs. Classes handle styling perfectly well; put that sledge hammer away.

As soon as you are trying to increase specificity to get selectors to apply styles, you're doing something wrong! Revisit and rethink the selectors that need increased specificity for style overrides - it is likely that there is room to lower specificity.

4. Be Wary Of Globally Selecting Arbitrary Elements

Globally selecting arbitrary elements may be useful in reducing the number of selectors you need to write, but can also be dangerous in that an assumption is made that you are happy that every matching element should receive that styling.

Globally applied styles can present problems in scalability and maintenance as a project grows.

To illustrate:

/* 1. Apply a natural box layout model to all elements - safe and useful */
* { box-sizing: border-box }

/* 2. Give all divs a bottom margin of 1em - highly problematic */
div { margin-bottom: 1em;}

/* 3. Style all elements that have a class beginning with 'btn-' - useful, but can be problematic */
[class^="btn-"] { ... }

The universal box-sizing magic happening at #1 is amazing! Do I want this behaviour everywhere? Hell yes! Can I use a simple modifier for edge cases where I want different behaviour? You betcha!

With #2 we give every div some bottom margin.

This immediately assumes that the natural behaviour of a div is insufficient. What about when you don't want margin? How many times are you going to have to override that property? A modifier may benefit us here to rather extend the native properties of a div.

Remember, there are benefits in using the default behaviour of elements before they are modified.

To prevent unnecessary style overrides, leave single element styling to base styles as much as possible.

#3 looks harmless enough too:

Style all elements that have a class beginning with btn- as follows...

The problem with this is that styles are applied to every single element that has a class beginning with btn-. Are you absolutely sure you want to style every possible occurrence of btn- with those styles?

The issue becomes obvious when you add another class such as .btn-foo, and you don't want it to inherit styles from the .btn- global selector. To resolve this you have to add unnecessary overrides to the new class to ensure that the styles you really want are not affected by the global selector.

You're better off using an explicitly defined class that leaves little room for unwanted side effects:

/* default button styles */
.btn { 
  ...
  font-size: 24px;
  ...
}

/* styles to modify only the size of the button */
.btn-large { font-size: 32px; }

5. Leverage Source Order

This is where things get awesome.

The idea is to leverage the natural cascade of your stylesheets to further reduce specificity issues in your projects.

The following is a skinny version of the index I currently use as a base when starting a styleguide for a new project:

/* ==|== &INDEX ===========================================
        1. BASE STYLES
                ...
        2. BLOCKS
                ...
        3. GENERAL STYLING
                ...
        4. ANIMATIONS
        5. GRID AND MEDIA QUERIES
        6. PRINT STYLES ========================================================= */

The idea is to organise your styles in such a way that you are utilising the cascade to your advantage.

Start With Base Styles

Base styles include your normalise styles, and font-face declarations. These styles are the foundation on which everything else is built, so you want them to always be overridden by more specific styles.

Blocks Follow Base Styles

Your blocks include styles for buttons, styles for navigation, and any other styles that define reusable elements in your site.

Importantly, modifiers, such as the .btn-large example above, are declared after the styles declaring a block. Modifiers should not require higher specificity in order to do their jobs - they should use the cascade.

Always declare a block's modifiers after the block.

General Styling Follows Block Styles

General styles are the modifiers, helpers, and utility classes which don't belong to any specific block, but are useful everywhere. These classes will often define only a few properties, and serve a very specific purpose.

The bulk of these styles will, in fact, be classes that define a single property only, and describe that behaviour in their class name:

/* float modifiers */
.fr { float: right;}
.fl { float: left;}

/* text alignment modifiers */
.ta-l { text-align: left;}
.ta-c { text-align: center;}
.ta-r { text-align: right;}

/* margin modifiers */
.m-none { margin: none;}
.m-alpha { margin: 16px;}
.mb-alpha { margin-bottom: 16px;}

These micro utilities are exceptionally powerful, as they allow you to modify blocks in specific scenarios without having to dream up new names for them. Chris Hart wrote about this style of modifiers in his article on describing in the browser with OOCSS-inspired modifiers, and Necolas Gallagher has a useful library full of these utility classes.

Micro utilities are worthy of their own post, as their power is easily misunderstood, and can be easily misused.

The important thing to understand is that these classes are exceptionally powerful in making your stylesheet extensible while keeping it clean. They should be placed after the more specific block styles so that they can be used to further modify blocks, and any other elements which simply need one or two modifiers in a unique scenario.

Media Query Styles Follow All Other Screen Styles

Once we're done declaring styles that will be used everywhere, we can then declare our viewport specific styles.

Start media query declarations from mobile, and allow them to cascade up to desktop without max-width values.

A mobile first approach ensures that desktop styles easily override mobile styles via the cascade.

6. Namespace Classes To Describe Class Relations

Namespacing classes according to a theme helps you tell immediately which classes belong to which blocks or utilities. This is especially useful when scanning markup.

A class named ".large" says little about what that class pertains to. On seeing this you should ask yourself, "To which element or block does this class belong? What outcome can I expect if I apply it to a different element?" Descriptive namespacing removes this confusion:

/* block level modifiers - not so helpful class names */
.block  { display: block;}
.inline { display: inline;}
.flex   { display: flex;}

/* block level modifiers - useful namespacing */
.d-block  { display: block;}
.d-inline { display: inline;}
.d-flex   { display: flex;}

The simple modification above effectively relates the classes to each other, while describing that that relationship is through the display property.

Furthermore, namespacing allows you to carry ideas across classes, and keeps the classes reusable, while ensuring that there is no mystery as to what behaviours are expected. I've found this particularly useful in remembering which colours in a stylesheet are being applied where:

// declare Sass colour variables
$clr-alpha: red;  // colour modifiers named alpha will use red
$clr-beta:  blue; // colour modifiers named beta will use blue

/* font colour modifiers */
.fc-alpha { color: $clr-alpha;}
.fc-beta  { color: $clr-beta;}

/* background colour modifiers */
.bgc-alpha { background-color: $clr-alpha;}
.bgc-beta  { background-color: $clr-beta;}

This method for colour namespacing works equally well for naming micro utilities for margin sizes, font sizes, and even namespacing micro utilities specific to particular viewports. These micro utilities really do deserve a post of their own!

7. Write Location Agnostic Styles

Your styles should not care where in the markup they are placed. A block should behave predictably regardless of where it is used in your markup.

This is where understanding the ideas behind BEM, SMACSS, and OOCSS is paramount. In order to be able to reuse your styles effectively, you need to be confident that wherever they are placed they are not augmented by the more specific styles of a container, or do not augment the styles of their children, unless that is the behaviour you are expecting.

Minimise side effects of styles by applying block specific modifiers to blocks, or by adding micro utilities to the markup.

Bootstrap frameworks such as Twitter Bootstrap and Zurb Foundation illustrate this concept excellently. Every block is built in such a way that irrespective of where the block is placed, the block behaves predictably.

A styleguide helps in demonstrating how a block will behave under different conditions so that no surprises are encountered mid-project.

8. Use Sass

There are many arguments both for and against using CSS pre-processors, and I was reluctant to jump on the bandwagon for quite a while.

My biggest issue was that I wanted to ensure that the quality of my compiled CSS was not degraded by the misuse of @extends, @mixins, and nesting.

I wanted to ensure that I was writing quality CSS before taking the dive.

The only way a preprocessor can degrade the quality of your CSS is through how you choose to implement its features.

Now that I'm using Sass, I wish I had made the move sooner! The quality of my compiled CSS has not been affected, since I'm very careful of when and how I use its features.

The speed at which I can write, update, and maintain CSS is now leagues ahead of what I was capable of before using a preprocessor.

The power that variables introduce is invaluable, and the cleanliness and freedom of not having to sift through vendor prefixes is a gift:

.some-gradient {
  background-image: -webkit-linear-gradient(top, bottom, red, blue);
  background-image:    -moz-linear-gradient(top, bottom, red, blue);
  background-image:      -o-linear-gradient(top, bottom, red, blue);
  background-image:     -ms-linear-gradient(top, bottom, red, blue);
  background-image:         linear-gradient(top, bottom, red, blue);
}

// VS

.some-gradient {
  @include linear-gradient(top, bottom, red, blue);
}

The second class is far easier to manage and read than the first.

I have put together a list of useful utilities based largely on those made available through the Bourbon mixin library.

Conclusion

These methods have dramatically increased my ability to write scalable markup for both large and small projects. My stylesheets are now easier to navigate, extend, and maintain, and I rarely ever have specificity issues, other than when I've made poor decisions that stray from the methods outlined above.

Let me know if there are any methods that you've come across which help streamline your work process!

7 Responses

rss feed
  1. Hi, just a mistake : in “.some-gradient {“, I suppose that it is “-linear-gRadient” and not “-linear-gadient” ;)

    • hahaha… wow, thanks for picking that up :P

      Updated!

  2. Nice work, Larry – I’ve been looking forward to reading this, and it totally surpassed expectations! I’m stealing your color naming scheme for SASS variables. Way better than mine.

    • Glad you found it useful! This article’s given me fuel for a bunch more – I have a method for creating dynamic grids using the cascade in media queries which builds on some of these methods. I’ll give you a shout when it’s up!

  3. Nice article !

    Just a little mistake with:
    .ta-n { text-align: normal;}

    “normal” doesn’t exist for the text-align property.

    Thanks for sharing your working methods !

    • Thanks for picking that up, Jonathan, amended!

This comment thread is closed. Got something important to say? !