Creating the “Perfect” CSS System
A high level guide for designers and developers who write CSS, but want to be more strategic about building moderate to large scale CSS systems
I’ve spent a lot of time working CSS systems. A large amount of my exposure occurred while working for a small web development agency as a UI Engineer and Designer. Being principally responsible for all things HTML, CSS and presentational JS on dozens of previously cobbled together codebases taught me how to be agile and flexible in a messy system. It also taught me how to be adaptable in opinionated environments, sometimes switching between five completely different codebases in a day and staying consistent with their separate patterns.
More than once over the years, I’ve typed some combination of the words ‘perfect CSS structure architecture’ into Google looking for guidance in building a maintainable and scalable environment. My work in these wide ranging CSS environments with equally diverse team dynamics has made me think a lot about what the “perfect” CSS system looks like for a project and a company.
Recently, I had the opportunity to build a new CSS system for Gusto’s Partner Directory. The directory helps users find accountants, bookkeepers and more who use the Gusto Partner platform. Building a digital marketplace is no small task, even on a smaller scale and involved a massive cross-collaborative effort. My area of focus in the development phase was primarily structuring our new CSS system. It brought to light some deep questions about what patterns we should use and how this project could function as a prototype for larger scale refactors on the horizon. This got me once again thinking about the ‘perfect’ CSS system, what that means, and if it is even possible.
Set aside dedicated brainstorm and research time before coding
Maybe you’ve been working in disorganized systems for years and already dreamed up everything you would do differently while silently judging the use of id selectors and one off page styling files. Maybe it is your first project using Sass and compiling CSS. Either way, take a step back and ask yourself why you are choosing a tool, naming convention or structure before diving into the work. There are a lot of opinions about CSS best practices out there and it can get overwhelming. Breaking it into smaller pieces will make the research process easier.
Some things you will want to review for your plan of action:
- Naming conventions
- BEM, SMACSS and any modifications to those you want to include
- What CSS style sheet language extension you want to use
- CSS Modules with React?
- Sass, Less, none?
- File architecture
- Atomic CSS, ITCSS, your own fancy structure
- CSS frameworks
- Bootstrap, Framework, a simple homemade grid
- React, Angular, PostCSS, CSS Grid, etc..
Get buy-in early and prepare for compromise
Getting other developers and designers to use the standardized rules is essential. When starting a project, get developers onboard with your CSS, JS and even HTML conventions from the start. Meet early and often to discuss every library, framework, mental model, and gem you are interested in using and take feedback seriously. Simply put, if they absolutely hate BEM and refuse to write it, don’t use BEM. You can explore working around this with linters, but forcing people to use a naming convention they hate isn’t going to make your job any easier. Hopefully, you will be able to convince them why the extra underscores are useful, but finding a middle ground where everyone will participate in some type of system is the priority.
For example, when explaining my architecture and naming conventions for Partner’s Directory to another developer we ran into one of these compromising moments. I asked her to use BEM, semantic naming, rely heavily on helpers, etc. She agreed and then proposed that we write CSS properties in alphabetical order. I personally prefer to list things with functional grouping, but alphabetical ordering is a legacy coding convention at Gusto. Putting personal preference aside, it was an easy thing to compromise in light of the larger systematic standards I was hoping to accomplish.
Stand your ground at the most impactful moments
All that compromise ideology said, figure out where you are putting your foot down. This is easier in instances where something only needs to be implemented occasionally. Rules that are followed often — like naming conventions — will likely lapse over time if developers aren’t bought into the concept. File structure naming or third party vendors are good candidates for high impact moments you can fight for as they are modified infrequently.
An example would be Bootstrap. Don’t get me wrong, front end frameworks and libraries are great, however they can lead to excessive code bloat when used too liberally. We use Bootstrap (primarily the grid) in our codebases, because it is widely known and easy to work with for most developers and designers. It is arguable if companies with front-end devs should be using Bootstrap at all as it has over 10,000 lines of code (150,000 characters!) and most of this styling code isn’t used in a branded site. For example, having Bootstrap button styling is pointless and repetitive if you are coding your own system of buttons. It is a great tool for developers who want to polish up their applications, but in our case, using the whole Bootstrap library isn’t required.
Early on in the Partner’s Directory I teased out the grid, media, and display files from Bootstrap 4 into our Sass directory. A few weeks later I saw the Bootstrap gem had been included from a well-intentioned developer who didn’t understand why I had only added only a few files of Bootstrap code to the repo. After an amicable, but thorough conversation, we removed the gem. The balance of compromise and standing by your decisions is critically important to the long term health of your system.
Choosing the right mental model
Often developers use the style sheets part of cascading style sheets like rock stars and even the cascade bit proficiently within a specific file. That said, finding a scattering of !important tags hints that the macro level cascade is starting to fall apart. Thinking through the architecture of your project’s cascade from the very start can help mitigate these issues.
Additionally, creating the right organizational structure for your CSS will save a lot of headache when your system becomes more robust and complicated. A few popular mental models in existence are Brad Frost’s Atomic structure, a basic file organization system from Sass or the option I went with on Partner’s Directory: ITCSS — Inverted triangle CSS. These structural systems essentially define the overall cascade to make sure the most specific elements are inheriting from the most broad rules, leading to DRY code and proper inheritance.
Conceptualizing layers of specificity, whether you rely on a mental model like ITCSS or not is important for scalability. Think of the broadest tools first or what you want to have the least authority. For example, your brand color variables will need to be accessible almost everywhere throughout the rest of the code. That should set off the signal in your mind to put them at the top of the cascade. Start with what else needs to be almost universally accessed and work your way down from there.
Customizing Your System
I like using an existing mental model or naming convention system because it helps future developers find discussions and documentation in the online front-end design community. ITCSS on it’s own was almost perfect for my needs, but I did make a few small tweaks to custom fit my project. If you want a more in depth look at each layer of ITCSS, read more about it from Harry Roberts here.
Layer definitions for this slightly modified ITCSS structure:
- Vendor — files for third party code. At the top to be the most easily overridden
- Settings — preprocessors, fonts, variables (doesn’t generate any style output)
- Tools — mixins, functions (doesn’t generate any style output)
- Generic — resets or normalizing files
- Elements — bare HTML elements (H1, a, p, etc…)
- Components — Majority of code goes here to style specific UI components
- Utilities/Helpers — utilities and helper classes that have the most authority (show/hide, color helpers, etc…)
ITCSS modifications in detail
- Adding a Vendor Layer One issue I ran across with ITCSS and all the example projects, was that none of them used any third party vendor style sheets. One could argue putting those outside this file structure or ideally skipping them all together, but we use a few vendor stylesheets (Bootstrap grid for example) and I wanted those nearby. Vendor party style sheets should always have the least authority over the cascade. Your custom code should easily overwrite anything from a vendor without needing !important tags. Therefore, I added a vendor directory as the topmost layer in my inverted triangle.
- Prefixing the Directories with Numbers This is a small one, but makes the whole system more visually easy to explain to a new developer coming into the project. By giving layers directories numbers, the cascade of importance is made visually clear at first glance. Remember how I said vendor was at the top of the inverted triangle? It’s easy to see that with a prefixed number.
The most important thing in either building your own custom system or modifying an existing system is to document your changes. Make sure to give other developers a heads up that there are some small changes to this mental model or explain how your shiny new model works.
Comments, comments, comments
I think people sometimes forget the importance of commenting in CSS because it is simple to write CSS and hard to break. Remember, CSS is simple, but it isn’t easy. Make your system easier to figure out in the README and inline commenting. Write down your logic, let contributors know how to use the system. Yes, there is a variable file for typography, but they don’t need to use those variables very often, because we already have helpers set up for that. So why not say that in the variable file? This is also especially helpful if you are using vendor code for something. Using Bootstrap for breakpoints? Comment where you would write new CSS breakpoints so developers don’t end up making another set of unnecessary media queries.
Comments are kind to future developers and your future self, use them.
Closing Thoughts on Building the ‘Perfect’ CSS Structure
While the tools, mental models, naming conventions, ordering of properties and more can create a great system in a vacuum, collaborating with your team members is the most important part. Things have happened to this structure since I last touched it that fall outside the patterned parameters, but overall the system is chugging along. Have a conversation about the system before developers jump into the project, make lightweight documentation and try to code review as often as possible until the system is running on its own. Lastly, don’t try and control things too tightly. CSS is ultra flexible for better or worse and letting it evolve in a semi-controlled way shouldn’t affect your blood pressure.