Simple Methods For Writing Scalable CSS Right Now
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.
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.
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
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.
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.
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.
#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.
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.
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.
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 @extend
s, @mixin
s, and nesting.
I wanted to ensure that I was writing quality CSS before taking the dive.
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!
Nico - 12 November 2012 #
Hi, just a mistake : in “.some-gradient {“, I suppose that it is “-linear-gRadient” and not “-linear-gadient” ;)
Larry Botha - 12 November 2012 #
hahaha… wow, thanks for picking that up :P
Updated!
Chris Hart - 13 November 2012 #
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.
Larry Botha - 13 November 2012 #
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!
Jonathan Bonnefoy - 18 November 2012 #
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 !
Larry Botha - 19 November 2012 #
Thanks for picking that up, Jonathan, amended!