├── .csscomb.json ├── .editorconfig └── README.md /.csscomb.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "node_modules/**", 4 | "resources/assets/sass/front/utility/**" 5 | ], 6 | "always-semicolon": true, 7 | "block-indent": " ", 8 | "color-case": "lower", 9 | "color-shorthand": true, 10 | "element-case": "lower", 11 | "eof-newline": true, 12 | "leading-zero": false, 13 | "quotes": "single", 14 | "remove-empty-rulesets": true, 15 | "space-after-colon": " ", 16 | "space-after-combinator": " ", 17 | "space-after-opening-brace": "\n", 18 | "space-after-selector-delimiter": "\n", 19 | "space-before-closing-brace": "\n", 20 | "space-before-colon": "", 21 | "space-before-combinator": " ", 22 | "space-before-opening-brace": " ", 23 | "space-before-selector-delimiter": "", 24 | "space-between-declarations": "\n", 25 | "strip-spaces": true, 26 | "unitless-zero": true, 27 | "vendor-prefix-align": true, 28 | "sort-order-fallback": "abc", 29 | "sort-order": [ 30 | [ 31 | "$variable" 32 | ], 33 | [ 34 | "$extend" 35 | ], 36 | [ 37 | "$include" 38 | ], 39 | [ 40 | "content", 41 | "box-sizing", 42 | "display", 43 | "flex", 44 | "flex-align", 45 | "flex-direction", 46 | "flex-grow", 47 | "flex-order", 48 | "flex-pack", 49 | "flex-shrink", 50 | "flex-wrap", 51 | "align-content", 52 | "align-items", 53 | "justify-content", 54 | "position", 55 | "top", 56 | "right", 57 | "bottom", 58 | "left", 59 | "width", 60 | "min-width", 61 | "max-width", 62 | "height", 63 | "min-height", 64 | "max-height", 65 | "clear", 66 | "clip", 67 | "float", 68 | "margin", 69 | "margin-top", 70 | "margin-right", 71 | "margin-bottom", 72 | "margin-left", 73 | "padding", 74 | "padding-top", 75 | "padding-right", 76 | "padding-bottom", 77 | "padding-left", 78 | "overflow", 79 | "overflow-x", 80 | "overflow-y", 81 | "vertical-align", 82 | "visibility", 83 | "zoom", 84 | "z-index" 85 | ], 86 | [ 87 | "font", 88 | "font-family", 89 | "font-size", 90 | "font-size-adjust", 91 | "font-stretch", 92 | "font-style", 93 | "font-variant", 94 | "font-weight", 95 | "line-height", 96 | "font-smooth", 97 | "webkit-font-smoothing", 98 | "moz-osx-font-smoothing", 99 | "hyphens", 100 | "letter-spacing", 101 | "tab-size", 102 | "text-align", 103 | "text-align-last", 104 | "text-decoration", 105 | "text-indent", 106 | "text-justify", 107 | "text-outline", 108 | "text-rendering", 109 | "text-transform", 110 | "text-wrap", 111 | "text-overflow", 112 | "text-overflow-ellipsis", 113 | "text-overflow-mode", 114 | "white-space", 115 | "word-break", 116 | "word-spacing", 117 | "word-wrap", 118 | "counter-increment", 119 | "counter-reset", 120 | "list-style", 121 | "list-style-image", 122 | "list-style-position", 123 | "list-style-type" 124 | ], 125 | [ 126 | "color", 127 | "background", 128 | "background-attachment", 129 | "background-clip", 130 | "background-color", 131 | "background-image", 132 | "background-origin", 133 | "background-position", 134 | "background-position-x", 135 | "background-position-y", 136 | "background-repeat", 137 | "background-size", 138 | "border", 139 | "border-collapse", 140 | "border-color", 141 | "border-style", 142 | "border-width", 143 | "border-top", 144 | "border-top-color", 145 | "border-top-style", 146 | "border-top-width", 147 | "border-right", 148 | "border-right-color", 149 | "border-right-style", 150 | "border-right-width", 151 | "border-bottom", 152 | "border-bottom-color", 153 | "border-bottom-style", 154 | "border-bottom-width", 155 | "border-left", 156 | "border-left-color", 157 | "border-left-style", 158 | "border-left-width", 159 | "border-radius", 160 | "border-top-left-radius", 161 | "border-top-right-radius", 162 | "border-bottom-right-radius", 163 | "border-bottom-left-radius", 164 | "border-image", 165 | "border-image-outset", 166 | "border-image-source", 167 | "border-image-repeat", 168 | "border-image-slice", 169 | "border-image-width", 170 | "border-spacing", 171 | "box-shadow", 172 | "opacity", 173 | "outline", 174 | "outline-width", 175 | "outline-style", 176 | "outline-color", 177 | "outline-offset", 178 | "text-shadow" 179 | ], 180 | [ 181 | "animation", 182 | "animation-delay", 183 | "animation-direction", 184 | "animation-duration", 185 | "animation-iteration-count", 186 | "animation-name", 187 | "animation-play-state", 188 | "animation-timing-function", 189 | "transform", 190 | "transform-origin", 191 | "transform-origin-x", 192 | "transform-origin-y", 193 | "transition", 194 | "transition-delay", 195 | "transition-duration", 196 | "transition-property", 197 | "transition-timing-function", 198 | "cursor", 199 | "pointer-events", 200 | "quotes", 201 | "resize", 202 | "-webkit-user-select", 203 | "-moz-user-select", 204 | "-ms-user-select", 205 | "user-select" 206 | ], 207 | [ 208 | "..." 209 | ], 210 | [ 211 | "$include form-placeholder", 212 | "$include mq", 213 | "$include mq-smaller", 214 | "$include mq-wider", 215 | "$include mq-taller", 216 | "$include mq-higher" 217 | ] 218 | ], 219 | "verbose": true 220 | } 221 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [{package.json}] 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [](https://supportukrainenow.org) 3 | 4 | # CSS Style Guide 5 | 6 | A work in progress CSS style guide for our projects at [Spatie](https://spatie.be). 7 | We currently use SCSS, but principles are useable for other pre/postprocessors. 8 | 9 | Spatie is a webdesign agency in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 10 | 11 | - [HTML](#html) 12 | - [CSS classes](#css-classes) 13 | - [SCSS syntax](#scss-syntax) 14 | - [File structure](#file-structure) 15 | - [Tools](#tools) 16 | - [Inspiration](#inspiration) 17 | 18 | ## Support us 19 | 20 | [](https://spatie.be/github-ad-click/css-styleguide) 21 | 22 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 23 | 24 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 25 | 26 | ## HTML 27 | 28 | - All styling is done by classes 29 | - Make elements easily reusable, moveable in a project, or between projects 30 | - Avoid #id's for styling 31 | - Avoid multiple components on 1 DOM-element 32 | 33 | ```html 34 | 35 |
36 | ... 37 |
38 | 39 | 40 |
41 |
42 | ... 43 |
44 |
45 | ``` 46 | 47 | Block tags are interchangeable since styling is done by class: 48 | ```html 49 | 50 |
51 |
52 |
53 | ``` 54 | 55 | Html tags that are out of control (eg. the output of an editor) are scoped by the parent: 56 | ```html 57 |
58 | 59 |
60 | ``` 61 | 62 | ## CSS classes 63 | 64 | We follow a BEVM syntax with custom accents. 65 | 66 | ```scss 67 | .js-hook // Script hook, not used for styling 68 | 69 | .block // Parent block 70 | .block__element // Child block 71 | .block__element__element // Grandchild 72 | 73 | // Shorthand if possible 74 | .items // Parent block 75 | .item // Child block 76 | 77 | .block--variation // Standalone variation of a block 78 | .block__element--variation // Standalone variation of an element 79 | .v-block // Block only used in specific view 80 | 81 | .-modifier // Single property modifier 82 | 83 | .h-type-value // Generic helper grouped by type (eg. `h-align`, `h-margin`) 84 | .is-state // State change by server or client 85 | .has-something // Parent class nests styling for children (eg. text editor output) 86 | 87 | ``` 88 | 89 | Class order in the DOM: 90 | 91 | ```html 92 |
93 | ``` 94 | 95 | Visual class grouping can be done with `... | ...`: 96 | 97 | ```html 98 |
99 | ``` 100 | 101 | ### .js-hook 102 | 103 | ```html 104 |
108 | ``` 109 | 110 | - Use `js-hook` to initiate handlers like `document.getElementsByClassName('js-hook')` 111 | - Use `data-attributes` only for data storage or configuration storage 112 | - Has no effect on styling whatsoever 113 | 114 | ### .blocks and .block__elements 115 | 116 | `class="news"` 117 | 118 | - Defined in `components/*.scss`, `patterns/*.scss` or `views/*.scss` 119 | - A single reusable component, patterns or view specific block 120 | - Children are separated with `__` 121 | - All lowercase, can contain `-` in name 122 | 123 | ```html 124 | class="news" 125 | class="news__item" 126 | class="news__item__publish-date" 127 | ``` 128 | 129 | - Use descriptive language. Consider `class="team__member"` instead of `class="team__item"`: 130 | 131 | ```html 132 | class="team" 133 | class="team__member" 134 | ``` 135 | 136 | - You can use plurals & singulars for readability. Consider `class="person"` instead of `class="persons_person"`: 137 | 138 | ```html 139 | class="persons" 140 | class="person" 141 | ``` 142 | 143 | ### .block--variation 144 | 145 | `class="button--delete"` 146 | 147 | ```scss 148 | .button--delete { 149 | @extend .button; 150 | 151 | background-color: red; 152 | color: white; 153 | text-transform: uppercase; 154 | } 155 | ``` 156 | 157 | - Defined in `components/*.scss` or `patterns/*.scss` 158 | - A variation adds a few properties to a block, and acts as a shorthand for multiple modifiers 159 | - It's used stand-alone without the need to use the base class `button` 160 | - It's a logical case to use `@extend` here, so the variation can inherit the original modifiers 161 | 162 | ### .v-block 163 | 164 | ```html 165 | class="v-auth" 166 | class="v-auth__form" 167 | ``` 168 | 169 | - Defined in `views/*.scss` 170 | - A special component that is not reusable, but tied to a specific view 171 | - Mostly exceptions and one-time styling 172 | - Use sparsely, try to think in more generic components 173 | 174 | ### .-modifier 175 | 176 | `class="button -rounded"` 177 | 178 | ```scss 179 | .button { 180 | &.-rounded { 181 | ... 182 | } 183 | ``` 184 | 185 | - Defined in `components/*.scss` or `patterns/*.scss` 186 | - A modifier changes one basic properties of a block, or adds a property 187 | - Modifiers are **always tied** to a component or block, don't work on their own 188 | - Make it generic and reusable if possible: `class="team -large"` is better than `class="team -management"` 189 | - Multiple modifiers are possible. Each modifier is responsible for a property: `class="alert -success -rounded -large"` 190 | - The order in html or css should therefore not matter 191 | 192 | ### .h-type-value 193 | 194 | `class="h-align-right"` 195 | 196 | ```html 197 | class="h-align-right" 198 | class="h-visibility-hidden" 199 | class="h-text-ellipsis" 200 | ``` 201 | 202 | - Eg. defined in `helpers/*.scss` 203 | - Reusable properties throughout the entire project 204 | - Prefixed by type (= the property that will be effected) 205 | - Each helper class is responsible for a well-defined set of properties 206 | 207 | ### .is-state 208 | 209 | `class="is-loaded"` 210 | 211 | ```scss 212 | &.is-loaded { 213 | ... 214 | } 215 | ``` 216 | 217 | - Special kind of modifier 218 | - Classes added by server or client 219 | - For state indication, interaction, animation start/stop 220 | 221 | ### .has-something 222 | 223 | `class="article has-html"` 224 | 225 | ```scss 226 | &.has-html { 227 | h1 { 228 | ... 229 | } 230 | } 231 | ``` 232 | 233 | - Explicit scoping class indicates special status of this DOM node's tree 234 | - An exception to the rule "all is class" when content of the node is rendered by a external component 235 | - Styling is done by nesting in the namespace 236 | 237 | ## Scss syntax 238 | 239 | ```scss 240 | 241 | /* Comment */ 242 | 243 | .block { // Indent 4 spaces, space before bracket 244 | 245 | @include ...; // @includes first 246 | 247 | a-property: value; // Props sorted automatically by csscomb 248 | b-property: value; 249 | c-property: .45em; // No leading zero's 250 | 251 | &:hover { // Pseudo class 252 | ... 253 | } 254 | 255 | &:before, // Pseudo-elements 256 | &:after { // Each on a line 257 | ... 258 | } 259 | 260 | &.-modifier { 261 | ... // Limit props or create variation 262 | } 263 | 264 | &.-modifier2 { 265 | ... 266 | } 267 | 268 | 269 | /* Try to avoid */ 270 | 271 | @extend ...; // Use only for variations. See eg. https://www.sitepoint.com/avoid-sass-extend/ 272 | 273 | &_subclass { // Unreadable and not searchable 274 | 275 | } 276 | 277 | h1 { // Avoid unless you cannot add a class to the H1 element 278 | ... 279 | } 280 | 281 | } 282 | // Line between blocks; 283 | .block--variation { // A block with few extra modifications often used together 284 | @extend .block; // Only good use for @extend 285 | ... 286 | } 287 | 288 | .block__element { // Separate class for readability, searchability 289 | ... 290 | } 291 | 292 | 293 | ``` 294 | 295 | ## File structure 296 | 297 | We typically use 8 folders and a main `app.scss` file: 298 | 299 | ``` 300 | |-- base : basic html elements 301 | |-- components : single components 302 | |-- helpers : helper classes 303 | |-- patterns : more complex components with parent/child relations 304 | |-- settings : variables 305 | |-- utility 306 | | |-- functions : SCSS functions 307 | | `-- mixins : SCSS mixins 308 | |-- vendor : custom files from 3rd party components like fancybox, select2 etc. 309 | |-- views : non-reusable components specific for a view 310 | `-- app.scss : main file 311 | 312 | ``` 313 | 314 | ### App.scss 315 | 316 | - Source order shouldn't matter, except for order of folders: import npm libraries, settings and utilities first 317 | - Import is done by glob pattern so files can be moved easily from eg. components to patterns 318 | 319 | ```scss 320 | @import 'settings/**/*'; 321 | @import '~normalize-css/normalize'; 322 | @import 'utility/**/*'; 323 | @import 'base/**/*'; 324 | @import 'components/**/*'; 325 | @import 'patterns/**/*'; 326 | @import 'helpers/**/*'; 327 | @import 'vendor/**/*'; 328 | @import 'views/**/*'; 329 | ``` 330 | 331 | ### Base folder 332 | 333 | Contains sensible defaults for basic html elements. Example files and classes: 334 | 335 | ``` 336 | |-- *.scss 337 | |-- html.scss 338 | |-- a.scss 339 | |-- p.scss 340 | |-- h.scss 341 | `--... 342 | 343 | ``` 344 | 345 | Example file `*.scss`: 346 | 347 | ```scss 348 | * { 349 | box-sizing: border-box; 350 | position: relative; 351 | 352 | &:after, 353 | &:before { 354 | box-sizing: border-box; 355 | } 356 | } 357 | ``` 358 | 359 | ### Components folder 360 | 361 | Stand-alone reusable components with modifiers and variations. 362 | 363 | ``` 364 | |-- alert.scss 365 | |-- avatar.scss 366 | `-- ... 367 | 368 | ``` 369 | 370 | Excerpt from `alert.scss`: 371 | 372 | ```scss 373 | .alert { 374 | 375 | ... 376 | 377 | &.-small { 378 | ... 379 | } 380 | } 381 | 382 | .alert--success { 383 | @extend .alert; 384 | ... 385 | } 386 | 387 | ``` 388 | 389 | ### Helpers folder 390 | 391 | Stand-alone helper classes for small layout issues. 392 | 393 | ``` 394 | |-- align.scss 395 | |-- margin.scss 396 | |-- padding.scss 397 | `-- ... 398 | 399 | ``` 400 | 401 | Excerpt from `margin.scss`: 402 | 403 | ```scss 404 | .h-margin { 405 | ... 406 | } 407 | 408 | .h-margin-none { 409 | ... 410 | } 411 | 412 | .h-margin-small { 413 | ... 414 | } 415 | 416 | .h-margin-medium { 417 | ... 418 | } 419 | ``` 420 | 421 | ### Patterns folder 422 | 423 | More complex reusable patterns with parent/child relations, modifiers and variations. 424 | 425 | ``` 426 | |-- footer.scss 427 | |-- grid.scss 428 | `-- ... 429 | 430 | ``` 431 | 432 | Excerpt from `grid.scss`: 433 | 434 | ```scss 435 | .grid { 436 | ... 437 | } 438 | 439 | .grid__col { 440 | 441 | &.-width-1\/2 { 442 | ... 443 | } 444 | 445 | &.-width-1\/3 { 446 | ... 447 | } 448 | 449 | &.-width-2\/3 { 450 | ... 451 | } 452 | } 453 | ``` 454 | 455 | ### Settings folder 456 | 457 | Settings for colors, breakpoints, typography. etc. 458 | 459 | ``` 460 | |-- breakpoint.scss 461 | |-- color.scss 462 | |-- grid.scss 463 | `-- ... 464 | 465 | ``` 466 | 467 | Excerpt from `color.scss`: 468 | 469 | ```scss 470 | $blue: ( 471 | lightest: #e6f5ff, 472 | lighter: #8bcdff, 473 | light: #2cb0de, 474 | default: #0080c8, 475 | dark: #047ac5, 476 | darker: #024271, 477 | darkest: #00284f, 478 | ); 479 | ``` 480 | 481 | ### Utility folder 482 | 483 | SCSS common mixins and functions. 484 | 485 | ``` 486 | |-- functions 487 | | |--color.scss 488 | | |--sass-map.scss 489 | | `--... 490 | `-- mixins 491 | |--background.scss 492 | |--block.scss 493 | `--... 494 | 495 | ``` 496 | 497 | Excerpt from `functions/color.scss`: 498 | 499 | ```scss 500 | @function blue($key: default, $opacity: 1) { 501 | ... 502 | } 503 | 504 | @function green($key: default, $opacity: 1) { 505 | ... 506 | } 507 | 508 | @function gray($key: default, $opacity: 1) { 509 | ... 510 | } 511 | ``` 512 | 513 | Excerpt from `mixins/block.scss`: 514 | 515 | ```scss 516 | @mixin block-reset { 517 | ... 518 | } 519 | 520 | @mixin block-cover($position: absolute) { 521 | ... 522 | } 523 | 524 | @mixin block-tag($color, $background-color) { 525 | ... 526 | } 527 | 528 | @mixin block-clearfix { 529 | ... 530 | } 531 | ``` 532 | 533 | ### Vendor folder 534 | 535 | Imported and customized SCSS from 3rd party components (this is the syntactical Wild West). 536 | 537 | ``` 538 | |-- fancybox.scss 539 | |-- select2.scss 540 | `-- ... 541 | 542 | ``` 543 | 544 | ### View folder 545 | 546 | Non-reusable CSS rules tied to specific views. Consider this the exception, and try to keep this folder empty. 547 | 548 | ``` 549 | |-- auth.scss 550 | |-- home.scss 551 | `-- ... 552 | 553 | ``` 554 | 555 | Excerpt from `auth.scss`: 556 | 557 | ```scss 558 | .v-auth{ 559 | ... 560 | } 561 | 562 | .v-auth__gravatar{ 563 | ... 564 | } 565 | ``` 566 | 567 | ## Tools 568 | 569 | ### Spatie-scss 570 | 571 | [@spatie/scss](https://github.com/spatie/scss) is a small npm package that is used to kickstart CSS authoring with default settings, mixins, functions etc. 572 | It lacks the `vendor` and `view` folders, since these are specific to every project. 573 | 574 | ### Code Style 575 | - Install cscomb globally via `npm i csscomb -g` 576 | - Put a `.csscomb.json` in root dir of your project 577 | - Run `csscomb resources` 578 | 579 | ## Inspiration 580 | 581 | - [CSS Wizardry](https://csswizardry.com) 582 | - [Chainable BEM modifiers](https://webuild.envato.com/blog/chainable-bem-modifiers/) 583 | --------------------------------------------------------------------------------