├── .gitignore ├── README.md ├── box-model.png ├── give-up.gif └── quick-open-file.png /.gitignore: -------------------------------------------------------------------------------- 1 | /hours.txt 2 | /.idea 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 8 simple rules for a robust, scalable CSS architecture 2 | 3 | ### Translations 4 | 5 | - [Português (Brasil)](https://medium.com/tableless/8-regras-simples-para-uma-arquitetura-css-robusta-e-escal%C3%A1vel-545c6dade170) 6 | - [Chinese](http://www.jianshu.com/p/acb4b9d8ff4f) 7 | 8 | This is the manifest of things I've learned about managing CSS in large, complex web projects during my many years of professional web development. I've been asked about these things enough times that having a document to point to sounded like a good idea. 9 | 10 | I've tried to keep the explanations short, but this is essentially the tl;dr: 11 | 12 | 1. [**Always prefer classes**](#1-always-prefer-classes) 13 | 1. [**Co-locate component code**](#2-co-locate-component-code) 14 | 1. [**Use consistent class namespacing**](#3-use-consistent-class-namespacing) 15 | 1. [**Maintain a strict mapping between namespaces and filenames**](#4-maintain-a-strict-mapping-between-namespaces-and-filenames) 16 | 1. [**Prevent leaking styles outside the component**](#5-prevent-leaking-styles-outside-the-component) 17 | 1. [**Prevent leaking styles inside the component**](#6-prevent-leaking-styles-inside-the-component) 18 | 1. [**Respect component boundaries**](#7-respect-component-boundaries) 19 | 1. [**Integrate external styles loosely**](#8-integrate-external-styles-loosely) 20 | 21 | ## Introduction 22 | 23 | If you're working with frontend applications, eventually you'll need to style things. And even though the state-of-the-art of frontend applications keeps blazing ahead, CSS is still the only way to style anything on the web (and lately, in some cases, [native applications too](https://facebook.github.io/react-native/)). There's two broad categories of styling solutions out there, namely: 24 | 25 | * CSS preprocessors, which have been around for ages (such as [SASS](http://sass-lang.com/), [LESS](http://lesscss.org/), and others) 26 | * CSS-in-JS libraries, which are a relatively new approach to styling (such as [free-style](https://github.com/blakeembrey/free-style), and [many others](https://github.com/MicheleBertoli/css-in-js)) 27 | 28 | The choice between the two approaches is a topic for a separate article, and as usual, both have their pros and cons. That said, I'll be focusing on the former approach, and if you've chosen to go with the latter, this article will probably be a bit less interesting. 29 | 30 | ## High-level goals 31 | 32 | So we're after a robust, scalable CSS architecture. But what properties does that call for, specifically? 33 | 34 | * **Component oriented** - The best way to deal with UI complexity is to split the UI into smaller components. If you're using a sensible framework, the JavaScript side of this will come naturally. [React](https://facebook.github.io/react/), for instance, encourages a high-level of componentization and compartmentalization. We want a CSS architecture to match. 35 | * **Sandboxed** - Splitting the UI into components won't help our cognitive load if touching the styles of one component can have unwanted and unpredictable effects on another. Fundamental CSS features such as the [cascade](https://developer.mozilla.org/en/docs/Web/Guide/CSS/Getting_started/Cascading_and_inheritance), and a single, global namespace for identifiers actively work against you in this regard. If you're familiar with the [Web Components spec](https://developer.mozilla.org/en-US/docs/Web/Web_Components), think of this as getting the [style isolation benefits of the Shadow DOM](http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/) without having to care about browser support (or whether or not the spec ever gets serious traction). 36 | * **Convenient** - We want all the nice things, and we don't want to work for them. That is, we don't want to make our developer experience any worse by adopting this architecture. If possible, we want to make it better. 37 | * **Err on the side of safety** - Somewhat related to the previous point, we want everything to be *local by default*, and global only as an exception. We engineers are lazy people, and the path of least resistance always needs to point to the correct solution. 38 | 39 | ## Concrete rules 40 | 41 | ### 1. Always prefer classes 42 | 43 | This is just to get the obvious out of the way. 44 | 45 | Do not target ID's (e.g. `#header`), because whenever you think there can be only one instance of something, [on an infinite timescale](https://twitter.com/stedwick/status/525777867146539009), you'll be proven wrong. One past example of this was when we wanted to weed out any data-binding bugs on a large application we were working on. We started two instances of our UI code, side-by-side in the same DOM, both bound to a *shared* instance of our data model. This was to make sure that all changes in the data model were correctly reflected in both UI's. Any components that you might have assumed to always be unique, such as a header bar, no longer are. This is a great benchmark for surfacing other subtle bugs related to assumptions about uniqueness too, by the way. I digress, but the moral of the story is: there's no situation where targeting an ID would be a *better* idea than targeting a class, so let's just not, ever. 46 | 47 | Neither should you target elements (e.g. `p`) directly. It's often OK to target elements that *belong to a component* (see below), but on their own, eventually you'll end up having to [undo those styles](http://csswizardry.com/2012/11/code-smells-in-css/) for a component that doesn't want them. Recalling our high-level goals, this also goes against just about all of them (component-orientedness, avoiding the cascade like the plague, and being local by default). Setting things like fonts, line-heights and colors (a.k.a [inherited properties](https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance)) on `body` *can* be the exception to this rule if you so choose, but if you're serious about component isolation, it's completely feasible to forgo even these (see below about [working with external styles](#8-integrate-external-styles-loosely)). 48 | 49 | So with very few exceptions, your styles should always target a class. 50 | 51 | ### 2. Co-locate component code 52 | 53 | When working on a component, it helps tremendously if everything related to that component — its JavaScript, styles, tests, documentation, etc — live very close to each other: 54 | 55 | ``` 56 | ui/ 57 | ├── layout/ 58 | | ├── Header.js // component code 59 | | ├── Header.scss // component styles 60 | | ├── Header.spec.js // component-specific unit tests 61 | | └── Header.fixtures.json // any mock data the component tests might need 62 | ├── utils/ 63 | | ├── Button.md // usage documentation for the component 64 | | ├── Button.js // ...and so on, you get the idea 65 | | └── Button.scss 66 | ``` 67 | 68 | When you're working in the code, simply open your project browser, and all other aspects of the component are at your fingertips. There's a natural coupling between the styles and the JavaScript that produces your DOM, and it's a fair bet you'll be touching one soon after touching the other. The same applies to a component and its tests, for example. Think of this as the [locality of reference principle](https://en.wikipedia.org/wiki/Locality_of_reference) for UI components. I, too, used to meticulously maintain separate mirrors of my source tree under `styles/`, `tests/`, `docs/` etc, until I realized that literally the only reason I kept doing that was because that's how I'd always done it. 69 | 70 | ### 3. Use consistent class namespacing 71 | 72 | CSS has a single, flat namespace for class names and other identifiers (such as ID's, animation names, etc). Just like in the PHP days of old, the community has dealt with this by simply using longer, structured names, thus emulating namespaces ([BEM](http://getbem.com/) is an example). We'll want to choose a namespacing convention, and stick with it. 73 | 74 | For instance, let's say we use `myapp-Header-link` as a class name. Each of its 3 parts have a specific function: 75 | 76 | * `myapp` to first isolate our app from other apps possibly running on the same DOM 77 | * `Header` to isolate the component from other components in the app 78 | * `link` to reserve a local name (within the component's namespace) for our local styling purposes 79 | 80 | As a special case, the root element of the `Header` component can be simply marked with the `myapp-Header` class. For a very simple component, that might be all you need. 81 | 82 | Whatever namespacing convention we choose, we'll want to be consistent about it. In addition to each of the 3 parts having a specific *function*, they'll also have a specific *meaning*. Just by looking at a class, you'll know where it belongs. The namespacing will be the map by which we navigate the styles of our project. 83 | 84 | From now on I'll assume the namespacing scheme of `app-Component-class`, which I've personally found to work really well, but you can of course also come up with your own. 85 | 86 | ### 4. Maintain a strict mapping between namespaces and filenames 87 | 88 | This is just the logical combination of the preceding two rules (co-locating component code, and class namespacing): all styles affecting a specific component should go to a file named after the component. No exceptions. 89 | 90 | If you're working in the browser, and you spot a component that's misbehaving, you can right-click-Inspect it, and you'll see for instance: 91 | 92 | ```html 93 |