├── LICENSE ├── README.md ├── common-mistakes.md └── solving-complexity.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 gilbox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CSS Bliss 2 | ========= 3 | 4 | A CSS style guide for small to enormous projects, without all that pomp and cruft. Many ideas borrowed from [BEM](http://bem.info), [SMACSS](https://smacss.com/), [OOCSS](http://oocss.org/), [SUITECSS](https://github.com/suitcss). This style guide uses SCSS. However, the only truly beneficial preprocessor-specific features we discuss are [**variables**, **mixins**, and **partials**](http://sass-lang.com/guide). Therefore, this guide will be useful for any preprocessor or no preprocessor at all as there is very little focus on features that aren't already part of vanilla CSS. 5 | 6 | --- 7 | There is now a [**Walkthrough**](http://gilbox.github.io/css-bliss/walkthrough/release/). If you have never used BEM, SMACSS, or similar, reading the Walkthrough is highly recommended. 8 | 9 | --- 10 | There is now a **[linter](https://github.com/gilbox/blint)**. 11 | Enforcing `css-bliss` rules without it is very difficult. 12 | 13 | ---- 14 | 15 | - [General](#general) 16 | - [Naming](#naming) 17 | - [Building Blocks](#building-blocks) 18 | - [Encapsulation](#encapsulation) 19 | - [Semantics](#semantics) 20 | - [Comments](#comments) 21 | - [%placeholder](#placeholder) 22 | - [@extend](#extend) 23 | - [@mixin](#mixin) 24 | - [$variable](#variable) 25 | - [DRY](#dry) 26 | - [Directory Structure and File Naming](#directory-structure-and-file-naming) 27 | - [Positioning a Module inside of a Module](#positioning-a-module-inside-of-a-module) 28 | - [z-index](#z-index) 29 | - [Namespacing](#namespacing) 30 | - [Linter](#linter) 31 | - [Solving Complexity](#solving-complexity) 32 | - [Common Mistakes](#common-mistakes) 33 | - [Additional Resources](#additional-resources) 34 | - [The Future](#the-future) 35 | 36 | ---- 37 | 38 | If you have questions, comments, or suggestions please [open an Issue](https://github.com/gilbox/css-bliss/issues). And of course, [PRs](https://github.com/gilbox/css-bliss/pulls) are welcome. 39 | 40 | ---- 41 | 42 | # General 43 | 44 | - Use class selectors instead of element or attr selectors in most cases. 45 | - SASS gives us too much power. In part the purpose of this guide is to restrict our use of that power. 46 | - Try to avoid nesting, except as described in the DRY section. 47 | - **Keep [Modules](#module) small**. When in doubt, create a new [Module](#module) instead of bloating an existing [Module](#module). 48 | - A class name will almost never have more than 3 dashes, ie: `.MyModule-myElement--myModifier` 49 | - Be sure to click the `[ pen ]` links as you encounter them below. 50 | 51 | # Naming 52 | 53 | TitleCase [Modules](#module), camelCase [Elements](#element). why-not-dashes ? Because cased names are more readable (very objective explanation, I know). Furthermore, if we see a class name that doesntStartWithTitleCase we know that it's not a module. 54 | 55 | **Note**: The following example does *not* match the conventions laid out in the [DRY](#dry) section because **this is the compiled CSS code, *not* the SASS code**. 56 | 57 | .MyModule { 58 | ... 59 | } 60 | 61 | .MyModule-myElement { 62 | ... 63 | } 64 | 65 | .MyModule-myElement--modifier { 66 | ... 67 | } 68 | 69 | .MyModule--modifier .MyModule-myElement { 70 | ... 71 | } 72 | 73 | .MyModule.isState { 74 | ... 75 | } 76 | 77 | .mySimpleRule { 78 | ... 79 | } 80 | 81 | 82 | # Building Blocks 83 | 84 | ## Module 85 | 86 | - `.TitleCase` 87 | - Self-contained 88 | - Cannot be `@extend`ed to create [Module Modifiers](#module-modifier) 89 | - Most Modules should not set their own width, margin, and positioning. By authoring a module to be **full-width or inline**, it can better adapt to the dimensions of an ancestral context. [(source)](https://github.com/suitcss/suit/blob/master/doc/components.md#adapting-to-ancestral-context) 90 | - No margin 91 | - No top, left, right, bottom 92 | - 100% width, or auto width 93 | 94 | ## Module Modifier 95 | 96 | [`[ pen ]`](http://codepen.io/gilbox/pen/cDkzn?editors=010) 97 | 98 | - `--camelCase` 99 | - Could possibly define margin, top, left, right, or bottom but should probably be avoided in most cases. 100 | - Subclasses a Module 101 | - Do not `@extend` a Module to create a Module Modifier 102 | - Do not `@extend` a Module Modifier to create another Module Modifier 103 | 104 | ## Element 105 | 106 | - `-camelCase` 107 | - Each element has an associated module ie: `.MyModule-myElement` 108 | - Nesting [Modules](#module) should **not** effect the appearance of any Element. 109 | - Apply at most **one** Element class per DOM element. 110 | 111 | ## Element Modifier 112 | 113 | - `--camelCase` 114 | - Each modifier has an associated [Element](#element) ie: `.MyModule-myElement--myModifier` 115 | - Nesting [Modules](#module) should **not** effect the appearance of any Element Modifier. 116 | - Subclasses an [Element](#element) 117 | 118 | ## State 119 | 120 | - `.isCamelCase` (formerly `.is-camelCase`) 121 | - Often, but not necessarily used in conjunction with JavaScript 122 | - **No** style except in context with another rule. For example: `.MyModule.isState`, `.MyModule-myElement.isState`, `.MyModule-myElement--myModifier.isState`, `.isState .MyModule-myElement`, etc. 123 | 124 | ## Utility Classes (aka Simple Rules) 125 | 126 | - `.camelCase` 127 | - Utility Classes `.may-containDashes` when the dashed word might imply very similar meaning as a function argument does in javascript. A good use-case for dashed utility classes are device-specific classes such as `.col2-mobile`, `.col2-tablet`, etc. 128 | - These rules should be completely flat. They include what are often called [utility classes](https://github.com/suitcss/suit/blob/master/doc/naming-conventions.md#u-utilityName) and [layout rules](http://smacss.com/book/type-layout). 129 | 130 | 131 | ## Encapsulation 132 | 133 | Modules promote strict encapsulation. We achieve encapsulation thusly: 134 | 135 | - No module may ever effect any other module by creating a selector that 136 | reaches inside of another module. 137 | - **All** classes inside of a module file are namespaced by the module's name 138 | (with the notable exception of state (`.is`) classes) 139 | - To achieve the strictest form of encapsulation, 140 | no DOM element should ever be assigned module-namespaced classes from more than one module. 141 | 142 | 143 | # Semantics 144 | 145 | **When choosing class names, try to ignore function, and concentrate on style.** Just because we have a section on our website named *music* doesn't mean that we should name our music-related card [Module](#module) `MusicCard`. Sure, we can name it `MusicCard` if similar cards do not appear anywhere else on the website. Otherwise, name it `Card` instead. (Since we probably can't know for sure whether the same card might later be added to a new section of the site, when naming classes make a judgement call based on the information available at the time and don't try to plan too far into the future.) 146 | 147 | And now that we have our `Card` module, if we need a [modifier](#module-modifier) for a green-tinted card in the section we're currently working on (which happens to be the music section) name it `Card--greenTint`. If your modifier truly is specific to the music section then `Card--music` is OK when a more stylistically descriptive name doesn't present itself. 148 | 149 | There is a lot of seemingly conflicting information about CSS semantics. Some people say to name your class by function like this: 150 | 151 |
FB
152 | 153 | instead of 154 | 155 |
FB
156 | 157 | However, we favor the second approach. Using the `.FacebookBtn` class name is convenient when you're using a library like jQuery because it keeps your markup and jQuery selector code semantically correct. However, with HTML5 and modern frameworks like angularjs the markup is already semantically correct via the use of custom element naming, attributes, and event handlers which declaratively describe the content and its function. 158 | 159 | What about our Jasmine unit tests which are heavy with jQuery selectors? If class names are *un-semantic* does that force us to write unit tests that break when simple stylistic changes are made to the interface? Not if we favor the use of attribute and element selectors in our unit tests which generally means the tests will only break when functionality changes. 160 | 161 | # Comments 162 | 163 | Above each [Module](#module), describe the purpose of the [Module](#module), as well as it's scope. Be restrictive and specific so that when someone else looks at it and needs to add some styling, they will know if they should add-on to the [Module](#module) or create a new one. 164 | 165 | // A floating dialog with dropshadow, rounded borders, and a header. 166 | // Includes the header text, but not any buttons and none of the other content inside of the dialog. 167 | .PopupDialog { 168 | ... 169 | } 170 | 171 | Note that in the example above, the comment answers the question, **what *is* and *isn't* a PopupDialog Module allowed to style?**. Later, if one of our teammates wants to add a close button to this dialog, they can read this comment and determine that the close button should probably be a Module in it's own right, and not an element of the `PopupDialog` module. 172 | 173 | # %placeholder 174 | 175 | - **Naming:** `%MyModule-placeholderDescriptor` 176 | - **Restrictions:** 177 | - Placeholders may only be declared inside of [Modules](#module). ([why?](http://csswizardry.com/2014/11/when-to-use-extend-when-to-use-a-mixin/)) 178 | - [Modules](#module) may only use placeholders declared inside of them. 179 | - A placeholder declared inside of a [Module](#module) may not be used anywhere else. 180 | 181 | The [next section](#extend) discusses why we should avoid using `@extend`. However if we do use `@extend` despite our trepedations, we will try to restrict its use to placeholders. And as we should with any `@extend`ed class, **keep `%placeholders` flat**, [here's why](http://oliverjash.me/2012/09/07/methods-for-modifying-objects-in-oocss.html). 182 | 183 | %placeholder { 184 | // avoid nesting .anyClassRules at any cost... 185 | .dontDoThis { ... } 186 | } 187 | 188 | `%placeholder`s should be small pieces of reusable styling, and [they should do one thing and do it well](http://en.wikipedia.org/wiki/Single_responsibility_principle). 189 | 190 | 191 | # @extend 192 | 193 | Don't `@extend` [Modules](#module) to create [Module Modifiers](#module-modifier) because 194 | 195 | 1. keeping modules totally flat may be near impossible 196 | 2. `@extend` can result in more code than simple subclassing 197 | 3. `@extend` is incompatible with media queries 198 | 4. `@extend` makes understanding the cascade of SCSS code very difficult 199 | 200 | What about using `@extend` in other rules? **In most cases, using `@extend` can lead to confusion and should probably be avoided.** 201 | 202 | # @mixin 203 | 204 | When a `@mixin` is declared **inside of a [Module](#module)**: 205 | 206 | - **Naming:** `@MyModule-mixinDescriptor` 207 | - It may not be used outside of the [Module](#module) in which it is declared. 208 | 209 | When a `@mixin` is declared **outside of a [Module](#module)**: 210 | 211 | - **Naming:** `@mixinDescriptor` 212 | - It should not generate complex nested structures. 213 | 214 | # $variable 215 | 216 | When a `$variable` is declared **inside of a [Module](#module)**: 217 | 218 | - **Naming:** `$MyModule-variableDescriptor` 219 | - It may not be used outside of the [Module](#module) in which it is declared. 220 | 221 | The purpose of this rule is to make it easy to identify local vs global variables. 222 | 223 | # DRY 224 | 225 | [`[ pen ]`](http://codepen.io/gilbox/pen/lKAIL?editors=010) 226 | 227 | 228 | .MyModule { 229 | ... 230 | &-myElement { 231 | ... 232 | } 233 | 234 | &-myOtherElement { 235 | ... 236 | } 237 | 238 | &-myOtherElement--myModifier { 239 | ... 240 | } 241 | 242 | &-myOtherElement--anotherModifier { 243 | ... 244 | } 245 | } 246 | 247 | and module modifiers: 248 | 249 | .MyModule--myModifier { 250 | ... 251 | .MyModule-myOtherElement { 252 | ... 253 | } 254 | 255 | .MyModule-myOtherElement--anotherModifier { 256 | ... 257 | } 258 | } 259 | 260 | 261 | A downside is that doing a full-text search for a class won't take us where we need to go, but if the naming convention is well-established we'll have that in mind when searching anyway. 262 | 263 | This DRY approach prevents `@extend`ing [*Elements*](#element) and [*Element Modifiers*](#element-modifier). This is good because `@extend`ing these nested classes creates confusing and difficult to maintain code. 264 | 265 | # Directory Structure and File Naming 266 | 267 | Create a new file for each [Module](#modules) and it's [Module Modifiers](#module-modifier) and save inside of the `modules` directory. Use TitleCase naming for module files and dashed-names for everything else. The following example suggests putting non-module rules alongside `application.scss` in the `css` dir. 268 | 269 | css 270 | ├─── modules 271 | | ├──── _PopupDialog.scss 272 | | ├──── _Btn.scss 273 | | └──── _ElmInfo.scss 274 | ├─── _base.scss 275 | ├─── _colors.scss 276 | ├─── _mixins.scss 277 | ├─── _zindex.scss 278 | └─── application.scss 279 | 280 | In the example above, the sass compiler is compiling `application.scss` and all of the other files are being `@import`ed from `application.scss` (is this the best way?). 281 | 282 | Use the SASS underscore naming convention whereby **all partials begin with underscore**. 283 | 284 | # Positioning a Module inside of a Module 285 | 286 | We will inevitably want to nest [Modules](#module) inside of modules. There are [various ways](http://stackoverflow.com/questions/24724929/smacss-and-bem-how-to-position-module-inside-of-a-module) that we could possibly position one [Module](#module) inside of another. In most cases we should **subclass the child [Module](#module) with an [*Element*](#element) class in the parent [Module](#module)**. 287 | 288 | ## Preferred approach using Element 289 | 290 | [`[ pen ]`](http://codepen.io/gilbox/pen/fwBhe?editors=010) 291 | 292 | Here we wrap the `.Btn` element with an `.PopupDialog-closeBtn` element: 293 | 294 | ### SCSS 295 | 296 | // modules/_Btn.scss 297 | .Btn { 298 | ... 299 | } 300 | 301 | // modules/_PopupDialog.scss 302 | .PopupDialog { 303 | ... 304 | &-closeBtn { 305 | position: absolute; 306 | top: 10px; 307 | right: 10px; 308 | } 309 | } 310 | 311 | ### Markup 312 | 313 |
314 | ... 315 |
316 |
318 |
319 | 320 | Note that the above approach is extremely flexible. If we want to swap out the `Btn` module for a different button module, it won't require any CSS changes. (And if we have a [pattern](http://ux.mailchimp.com/patterns) [library](http://alistapart.com/blog/post/getting-started-with-pattern-libraries), such a change will be as simple as copy-and-pasting some [markup](http://patterns.alistapart.com/).) 321 | 322 | Note also that if we wish to completely avoid Module file load-order specificity bugs and/or we need to load multiple CSS files asynchronously, `PopupDialog-closeBtn` shouldn't subclass `.Btn`, but instead [wrap it inside of another `
`](https://github.com/gilbox/css-bliss/blob/master/solving-complexity.md#6-non-deterministic-resolution) as we've done here. 323 | 324 | ## Alternate approach using Module Modifier 325 | 326 | [`[ pen ]`](http://codepen.io/gilbox/pen/LbKml?editors=010) 327 | 328 | Here we subclass `.Btn` with `.Btn--pullRight`: 329 | 330 | ### SCSS 331 | 332 | .Btn { 333 | ... 334 | } 335 | 336 | .Btn--pullRight { 337 | position: absolute; 338 | top: 10px; 339 | right: 10px; 340 | } 341 | 342 | ### Markup 343 | 344 |
345 | ... 346 |
348 | 349 | Note that the above approach is inflexible because in the future we won't be able to easily swap out the button without refactoring the css. Also, since it creates an unpredictable parent-child module positioning relationship the code is fragile which will make it more difficult to maintain. 350 | 351 | # z-index 352 | 353 | A ***globally-scoped*** z-index is any z-index who's [stacking context](http://philipwalton.com/articles/what-no-one-told-you-about-z-index/) is the `` tag or another tag which we deem to be the top-level stacking context of the page. 354 | 355 | All ***globally-scoped*** z-index rules should reference `$zindexVariables` in `_zindex.scss`. This [creates a central place](http://css-tricks.com/handling-z-index/) to manage z-indexes across the application. 356 | 357 | Note that there are many ways to create a stacking context, these [three](http://philipwalton.com/articles/what-no-one-told-you-about-z-index/) are the most common: 358 | 359 | > 1. When an element is the root element of a document (the `` element) 360 | > 2. When an element has a position value other than static and a z-index value other than auto 361 | > 3. When an element has an opacity value less than 1 362 | 363 | # Namespacing 364 | 365 | I don't like how it negatively effects readability, but if we need to namespace, prepend a lowercase two or three letter abbreviation. 366 | 367 | .ns-MyModule { 368 | ... 369 | } 370 | 371 | # Linter 372 | 373 | The css-bliss linter is **[blint](https://github.com/gilbox/blint)**, 374 | it helps enforce the naming conventions and proper module structure. 375 | It is nearly impossible to maintain a modular css structure for a complex 376 | web project without good tools to enforce the myriad of rules. 377 | 378 | # Solving Complexity 379 | 380 | [solving-complexity.md](https://github.com/gilbox/css-bliss/blob/master/solving-complexity.md) 381 | is another document in this repo inspired by the challenges identified 382 | by Facebook's [vjeux](https://speakerdeck.com/vjeux). 383 | solving-complexity presents some additional guidelines, which, when applied 384 | in addition to this guide 385 | help to solve problems faced by the most complex websites. 386 | 387 | 388 | # Common Mistakes 389 | 390 | [common-mistakes.md](https://github.com/gilbox/css-bliss/blob/master/common-mistakes.md) 391 | is a living document in this repo where I record common mistakes and their solutions. 392 | 393 | # Additional Resources 394 | 395 | - [include-media](https://github.com/eduardoboucas/include-media) - A nice way to handle @media queries. 396 | 397 | # The Future 398 | 399 | Unfortunately, in large part the need for something like css-bliss is a result of the poor scalability of vanilla CSS. There are various efforts in the works to drastically improve this situation. Most of this work is being conducted in the context of React, which is arguably the most cutting-edge JavaScript UI library. Some of these solutions are useable right now, while others are still experimental. Check out the following projects: 400 | 401 | - [webpack CSS modules](https://github.com/css-modules/webpack-demo) (started with the article [The End of Global CSS](https://medium.com/seek-ui-engineering/the-end-of-global-css-90d2a4a06284)) 402 | - [react-inline](https://github.com/martinandert/react-inline) 403 | - [react-in-style](https://github.com/ericwooley/react-in-style) 404 | - [jsxstyle](https://github.com/petehunt/jsxstyle) 405 | - [VirtualCSS](https://github.com/VirtualCSS/planning) 406 | - [stilr](https://github.com/kodyl/stilr) 407 | - [react-inline-style](https://github.com/dowjones/react-inline-style) 408 | - [aphrodite](https://github.com/Khan/aphrodite) 409 | 410 | -------------------------------------------------------------------------------- /common-mistakes.md: -------------------------------------------------------------------------------- 1 | # Common css-bliss Mistakes 2 | 3 | **Note:** Some of these only make sense if you read them in order, because they build on the previous. 4 | 5 | # Multiple Modules applied to same tag 6 | 7 | ### Bad 8 | 9 |
10 | 11 |
12 | 13 | ### Good 14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 | **OR** 22 | 23 |
24 | 25 |
26 | 27 | # Module and Element classes applied to same tag 28 | 29 | ### Bad 30 | 31 |
32 | 33 |
34 | 35 | ### Good 36 | 37 |
38 |
39 | 40 |
41 |
42 | 43 | # Width Applied at Module-level 44 | 45 | ### Bad 46 | 47 | .Button { 48 | width: 60px; 49 | } 50 | 51 | ### Good 52 | 53 | .PriceWidget-submit { // wraps the button 54 | width: 60px; 55 | } 56 | 57 | # Margin Applied at Module-level 58 | 59 | ### Bad 60 | 61 | .Button { 62 | margin-left: 10px; 63 | } 64 | 65 | ### Good 66 | 67 | .PriceWidget-submit { // wraps the button 68 | padding-left: 10px; 69 | } 70 | 71 | 72 | # Width Applied to Module Modifier 73 | 74 | While css-bliss doesn't explicitly prohibit doing this, it should generally be avoided. Whenever you find yourself utilizing `width` on a Module Modifier, consider applying `width` to the parent element instead. 75 | 76 | ### Not so Good 77 | 78 | .Button--width60px { 79 | width: 60px; 80 | } 81 | 82 | ### Good 83 | 84 | .PriceWidget-submit { // wraps the button 85 | width: 60px; 86 | } 87 | 88 | # Margin Applied to Module Modifier 89 | 90 | While css-bliss doesn't explicitly prohibit doing this, it should generally be avoided. Overuse of margin is bad for modularity. Whenever you find yourself utilizing `margin`, consider using `padding` on the parent element instead. 91 | 92 | ### Not so Good 93 | 94 | .Button--margin10px { 95 | margin: 10px 0; 96 | } 97 | 98 | ### Good 99 | 100 | .PriceWidget-submit { // wraps the button 101 | padding: 10px 0; 102 | } 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /solving-complexity.md: -------------------------------------------------------------------------------- 1 | # Solving Complexity 2 | 3 | This document is about solving 4 | [Facebook's CSS complexity problems](https://speakerdeck.com/vjeux/react-css-in-js) 5 | with [CSS Bliss](https://github.com/gilbox/css-bliss). 6 | 7 | Facebook's challenges are applicable to any very complex websites with many developers. 8 | Or any situation where CSS is bundled into multiple files and loaded asynchronously, 9 | and often loaded lazily. 10 | 11 | ## Terminology 12 | 13 | Terms below like [CSS Module](https://github.com/gilbox/css-bliss#modules), 14 | [CSS Module Element](https://github.com/gilbox/css-bliss#element), 15 | and [utility classes](https://github.com/gilbox/css-bliss#simple-rules) 16 | are references to definitions within CSS Bliss. 17 | 18 | 19 | ## 1 Globals 20 | 21 | Modules solve global variable issues with organization and naming. 22 | TitleCase naming with all modules in the same folder makes it impossible 23 | to have class-name conflicts. A TitleCased class name *must* only reside 24 | in a file with the same name inside of the `modules` directory. 25 | 26 | All other classes within a module are namespaced by the module's name 27 | and thus are all deliberately private. Any class namespaced 28 | with a module's name can only exist within the file of the same name. 29 | For example, the class `.IconButton-icon` must only live inside of 30 | `/modules/_IconButton.scss`. 31 | 32 | Facebook's solution to public variables was to make most variables 33 | private, and allow exporting public variables explicitly as needs. 34 | This solution is brittle and creates difficult to manage dependencies between CSS modules 35 | as well as requiring strict ordering of rules which is problematic in 36 | an application with multiple style sheets. 37 | 38 | 39 | ## 2 Dependencies 40 | 41 | We think of CSS Modules inside of the `modules/` folder as 42 | dependencies of our javascript components. 43 | CSS Bliss says nothing specific about the relationship between 44 | your javascript code and the CSS modules. That is, there is 45 | no explicit dependency management. You are expected to list 46 | required CSS modules inside of application.scss. 47 | 48 | A simplified version of facebook's `cx()` function (there is no 49 | need for their namespacing features) and tooling can manage dependencies 50 | automatically the same way it does now. 51 | 52 | 53 | ## 3 Dead Code Elimination 54 | 55 | grep for a module name and if it doesn't appear in the context 56 | of a CSS class then we can remove the module file as a dependency. 57 | We are guaranteed that module files will not contain any selectors that 58 | don't relate to them, nor placeholders nor mixins that are used 59 | by any other scss files. 60 | 61 | 62 | ## 4 Minification 63 | 64 | Facebook's `cx()` function and tooling could handle minification the same way it 65 | does now. 66 | 67 | 68 | ## 5 Sharing constants 69 | 70 | I've never found a good solution for this. 71 | 72 | 73 | ## 6 Non-Deterministic Resolution 74 | 75 | Facebook was applying classes from multiple CSS Modules 76 | to a single element: 77 | 78 |
82 | 83 | This presented load-order problems with asynchronously loaded 84 | CSS bundles. With CSS Bliss we almost have completely eliminated 85 | the possibility of this even happening. Just one more constraint, 86 | and problem solved: 87 | 88 | > ***Modules may not share common elements*** 89 | 90 | Without this new rule, CSS Bliss allowed a parent Module to sublass a child Module like this: 91 | 92 |
93 |
...
94 |
95 | 96 | However, with this new rule, if we have a module nested inside of 97 | another module, the parent may only indirectly affect it's child by wrapping it: 98 | 99 |
100 |
101 |
...
102 |
103 |
104 | 105 | Because of module rules, there is no way for `_ParentModule.scss` to directly effect 106 | `.ChildModule` or any `.ChildModule`-namespaced classes (aka CSS Module Elements). 107 | 108 | Now the order in which we include modules is irrelevant. We are 109 | free to load CSS asynchronously and lazily without negative repercussions. 110 | Note that the order of rules within a single module file is still relevant, 111 | so when bundling files a single module may not be split across multiple bundles. 112 | 113 | Of course, we have been ignoring utility classes. There are various approaches 114 | to deal with utility classes. Here is the simplest: 115 | 116 | > ***Option 1: Always load all utility classes before loading any modules.*** 117 | 118 | But then we lose the ability to load utilities asynchronously. Here's an alternative 119 | that solves the async problem: 120 | 121 | > ***Option 2: Make all utility classes `!important`*** 122 | 123 | This may sound like blasphemy, but it makes sense if we think of utility classes 124 | as overrides. Everything else is a module. Of course, **this means that we may never 125 | use `!important` inside of CSS Module**, but that's not a terribly difficult restriction to 126 | adhere to. 127 | 128 | 129 | ## 7 Breaking Isolation 130 | 131 | Facebook's challenge is that although they try to use modular encapsulation, 132 | 133 | > [developers] have the ability to modify the style of the internals 134 | > via selectors. The override looks like regular CSS, so it's often not being 135 | > caught by code review. It's also nearly impossible to write lint rules 136 | > against it. When this code gets checked in, it puts the maintainer of the 137 | > component in a very bad spot because when he changes the internals of the 138 | > component, she is going to break all those call sites. It makes you feel fearful 139 | > of changing code which is very bad. --vjeux 140 | 141 | The example that Facebook's vjeux gives of such a selector looks like: 142 | 143 | .product/button > div { 144 | /* override everything ! */ 145 | } 146 | 147 | CSS Bliss doesn't allow modules to contain any class name other than those 148 | namespaced by the module name. Combine this with a slight tweak of the following 149 | CSS Bliss rule, and this problem is (almost) completely solved: 150 | 151 | > Use class selectors instead of element or attr selectors in ~~most~~ **all** cases. 152 | 153 | Note that CSS Bliss does allow the use of state classes (`is-`) pretty much anywhere in the markup. 154 | How do we prevent state classes from inviting developers to circumvent the linter? 155 | Well, since state classes are not allowed to have styles of their own we can 156 | augment our linter to disallow any rule with a state rule at the end of it. 157 | 158 | So now the following is allowed: 159 | 160 | .isSomeState.MyModule { ... } 161 | 162 | But this isn't: 163 | 164 | .MyModule .isSomeState { ... } 165 | 166 | So now it becomes trivial to create tooling that prevents encapsulation-busting selectors. 167 | 168 | 169 | # Caveats 170 | 171 | - Tooling is still required 172 | - All problems aren't solved (notably 5 Sharing constants) 173 | - A lot of rules to follow 174 | - Legacy code can make it difficult to follow the rules. 175 | However, it might be possible to transition from legacy code fairly painlessly 176 | if TitleCased class selectors were never used previously 177 | 178 | 179 | # Conclusion 180 | 181 | Solving these challenges is really hard. Although I think we can come pretty 182 | close to a satisfactory solution, it's still far from ideal. 183 | Thus, I am intrigued by Facebook's **CSS in JS** approach and how easily it solves 184 | the problems above, and will definitely be looking at it some more. 185 | --------------------------------------------------------------------------------