Author: Ben Turner
Published on: 13th of December, 2017
Or CCM, if you want a snappy acronym.
Throughout my career, I have tried and witnessed dozens of ways of organising CSS, from enterprise level frameworks to small boutique websites, and they often share the same organisation problems. Things are hard to find, code relating to the same thing lives in multiple locations, refactoring becomes a nightmare, and code doesn't get re-used as it's too hard to find or has become too dependent on code around it.
No matter how you organise your code, the pieces of CSS always apply to a thing (component), and that thing always lives within a context. By isolating these two key concepts, Contexts and Components, and organising our code around them, we can make it more re-usable, easier to navigate, and less complicated.
📁 Root Directory └📁 Contexts └📄 home-page.scss └📄 product-detail-page.scss ...more 📁 Components └📄 product-list.scss └📄 slider.scss └📄 button.scss ...more 📄 app.scss
The file structure for Contexts and Components is simple on purpose, only one level deep. The less digging you have to do to find something the better. If your tag.scss
lives inside /app/partials/product/tag.scss
, it's easy to miss, and you will end up with another tag.scss
living somewhere else. It's also not clear that tag.scss
can be used outside of the context of a product page, when often that kind of code should be re-usable across the site.
The app.scss includes all files inside Contexts
and Components
, and it can serve as a directory of all components and contexts. No includes are hidden inside sub files, making it clear what does and doesn't already exist.
#Components @import('Components/product-list') ... #Contexts @import('Contexts/home-page') ...
The Contexts are included after Components, as the intention of the context is to override a components styling in certain situations, or on certain pages.
A component is a given chunk of CSS that relates directly to a chunk of HTML, and represents a particular part of the page. Some examples would be a product list, an image slider, or even a button. The key is to make sure any styling done to the component is isolated, and affects only itself. This makes it clean, and easily reusable because the component doesn't have to care about what context it's in or what lives around it.
Because we also know for sure it lives in the Components
folder, when future developers come to the codebase, they will quickly find any existing code and hopefully be able to reuse it. If not, they will know for certain that it's not in the codebase already, and that they're not going to be duplicating work.
<div class="product-list"> <div class="product">...</div> <div class="product">...</div> ... </div>
.product-list { padding: 20px; margin-bottom: 20px; }
You might be wondering, what if my product-list
on the homepage is different to my product-list
on the product detail page? Should I make a product-detail/product-list.scss
. Nope! That gets messy fast. Context specific changes to a component go inside of a context file. Lets explore that a bit.
A context represents a situation that a component might find itself in. Typically that's simply a page on the website. So when a button needs to move slightly so that it's sitting right on the contact us page, you add that CSS into a context called contact-us.scss
so that it's clear that this code only relates to that page.
In the example above, where we need our product-list
component to be styled differently on our product detail page, we create a new context instead of a new component. This makes it obvious that these changes to the component are specific to this context only, and it makes sure our changes are properly scoped and do not affect the rest of the site.
.product-detail-page { .product-list { padding: 40px; margin-bottom: 10px; } }
Often our contexts will have barely any code in them, and that's perfect. You only create a context if you need to change a component when it's on that page, and that way we keep our component code reusable and uncluttered with page-specific changes, as it all lives in a context file instead.
With this organisation strategy, your components are cleanly isolated from eachother but easy to find and re-use, and your context specific scenarios are scoped and isolated without having to try and squeeze the context specific code into the component itself. This makes refactoring easier, less risky, and re-use of code more practical.