├── .gitignore ├── README.md ├── dist └── components.css ├── gulp ├── notifyError.js ├── paths.js └── tasks │ ├── css.js │ └── default.js ├── gulpfile.js ├── package.json └── stylus ├── base ├── base.styl └── vars.styl ├── components ├── avatar.styl ├── comment.styl └── post.styl ├── main.styl └── utils └── img.styl /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | built -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SUIT CSS with Stylus & PostCSS 2 | 3 | This is an example of writing SUIT components with Stylus and then using [gulp](http://gulpjs.com/) and [PostCSS](https://github.com/postcss/postcss) to handle the tooling 4 | 5 | By combining SUIT with Stylus it allows things like functions and nesting to come back whilst also harnessing the tools that SUIT provides. On top of that there is an entire [ecosystem of PostCSS plugins](https://github.com/postcss/postcss#plugins) available for further processing. 6 | 7 | ### Why Stylus? 8 | 9 | Stylus plays nice with the `--` custom property prefix and also allows `@import` to be escaped with [`@css`](http://learnboost.github.io/stylus/docs/literal.html) 10 | 11 | These are key features for working with the SUIT preprocessor. Plus it's really fast and allows minimal syntax 12 | 13 | ### How it works 14 | 15 | Using [gulp](gulpjs.com) the SUIT components (written as Stylus files) are compiled into CSS, linted with the SUIT conformance tool and then built with [postcss-import](https://github.com/postcss/postcss-import) and [cssnext](https://github.com/cssnext/cssnext). A single file is the result. 16 | 17 | Additional plugins can be added easily. 18 | 19 | ### Treating SUIT as a global 20 | 21 | In this repo the dependencies of each component are listed as `@imports` so that `postcss-import` can work out the dependency tree. 22 | 23 | An alternative to this is to treat SUIT as a global library. This works well and can reduce the hassle of ensuring each component requires the right dependencies. 24 | 25 | Example from `base.styl`: 26 | 27 | ``` css 28 | @css { 29 | @custom-media --xs-viewport (min-width: 30em); 30 | @custom-media --sm-viewport (min-width: 48em); 31 | @custom-media --md-viewport (min-width: 62em); 32 | @custom-media --lg-viewport (min-width: 75em); 33 | 34 | /** 35 | * Use SUIT as a global 36 | */ 37 | @import "suitcss-base"; 38 | @import "suitcss-utils"; 39 | @import "suitcss-components"; 40 | } 41 | 42 | @require "functions" 43 | @require "vars" 44 | 45 | /** 46 | * More code here 47 | */ 48 | ``` 49 | -------------------------------------------------------------------------------- /dist/components.css: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the manifest for all your top level components. 3 | * Use @css so that the imports are left alone by Stylus and picked up 4 | * by Rework 5 | */ 6 | 7 | /** 8 | * Base styles and any SUIT vars 9 | */ 10 | /** 11 | * Placed in a CSS block so that they are picked up by Rework after 12 | * Stylus compilation 13 | */ 14 | 15 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */ 16 | 17 | /* ========================================================================== 18 | HTML5 display definitions 19 | ========================================================================== */ 20 | 21 | /** 22 | * Correct `block` display not defined in IE 8/9. 23 | */ 24 | 25 | article, 26 | aside, 27 | details, 28 | figcaption, 29 | figure, 30 | footer, 31 | header, 32 | hgroup, 33 | main, 34 | nav, 35 | section, 36 | summary { 37 | display: block; 38 | } 39 | 40 | /** 41 | * Correct `inline-block` display not defined in IE 8/9. 42 | */ 43 | 44 | audio, 45 | canvas, 46 | video { 47 | display: inline-block; 48 | } 49 | 50 | /** 51 | * Prevent modern browsers from displaying `audio` without controls. 52 | * Remove excess height in iOS 5 devices. 53 | */ 54 | 55 | audio:not([controls]) { 56 | display: none; 57 | height: 0; 58 | } 59 | 60 | /** 61 | * Address `[hidden]` styling not present in IE 8/9. 62 | * Hide the `template` element in IE, Safari, and Firefox < 22. 63 | */ 64 | 65 | [hidden], 66 | template { 67 | display: none; 68 | } 69 | 70 | /* ========================================================================== 71 | Base 72 | ========================================================================== */ 73 | 74 | /** 75 | * 1. Set default font family to sans-serif. 76 | * 2. Prevent iOS text size adjust after orientation change, without disabling 77 | * user zoom. 78 | */ 79 | 80 | html { 81 | font-family: sans-serif; /* 1 */ 82 | -ms-text-size-adjust: 100%; /* 2 */ 83 | -webkit-text-size-adjust: 100%; /* 2 */ 84 | } 85 | 86 | /** 87 | * Remove default margin. 88 | */ 89 | 90 | body { 91 | margin: 0; 92 | } 93 | 94 | /* ========================================================================== 95 | Links 96 | ========================================================================== */ 97 | 98 | /** 99 | * Remove the gray background color from active links in IE 10. 100 | */ 101 | 102 | a { 103 | background: transparent; 104 | } 105 | 106 | /** 107 | * Address `outline` inconsistency between Chrome and other browsers. 108 | */ 109 | 110 | a:focus { 111 | outline: thin dotted; 112 | } 113 | 114 | /** 115 | * Improve readability when focused and also mouse hovered in all browsers. 116 | */ 117 | 118 | a:active, 119 | a:hover { 120 | outline: 0; 121 | } 122 | 123 | /* ========================================================================== 124 | Typography 125 | ========================================================================== */ 126 | 127 | /** 128 | * Address variable `h1` font-size and margin within `section` and `article` 129 | * contexts in Firefox 4+, Safari 5, and Chrome. 130 | */ 131 | 132 | h1 { 133 | font-size: 2em; 134 | margin: 0.67em 0; 135 | } 136 | 137 | /** 138 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 139 | */ 140 | 141 | abbr[title] { 142 | border-bottom: 1px dotted; 143 | } 144 | 145 | /** 146 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 147 | */ 148 | 149 | b, 150 | strong { 151 | font-weight: bold; 152 | } 153 | 154 | /** 155 | * Address styling not present in Safari 5 and Chrome. 156 | */ 157 | 158 | dfn { 159 | font-style: italic; 160 | } 161 | 162 | /** 163 | * Address differences between Firefox and other browsers. 164 | */ 165 | 166 | hr { 167 | box-sizing: content-box; 168 | height: 0; 169 | } 170 | 171 | /** 172 | * Address styling not present in IE 8/9. 173 | */ 174 | 175 | mark { 176 | background: #ff0; 177 | color: #000; 178 | } 179 | 180 | /** 181 | * Correct font family set oddly in Safari 5 and Chrome. 182 | */ 183 | 184 | code, 185 | kbd, 186 | pre, 187 | samp { 188 | font-family: monospace, serif; 189 | font-size: 1em; 190 | } 191 | 192 | /** 193 | * Improve readability of pre-formatted text in all browsers. 194 | */ 195 | 196 | pre { 197 | white-space: pre-wrap; 198 | } 199 | 200 | /** 201 | * Set consistent quote types. 202 | */ 203 | 204 | q { 205 | quotes: "\201C" "\201D" "\2018" "\2019"; 206 | } 207 | 208 | /** 209 | * Address inconsistent and variable font size in all browsers. 210 | */ 211 | 212 | small { 213 | font-size: 80%; 214 | } 215 | 216 | /** 217 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 218 | */ 219 | 220 | sub, 221 | sup { 222 | font-size: 75%; 223 | line-height: 0; 224 | position: relative; 225 | vertical-align: baseline; 226 | } 227 | 228 | sup { 229 | top: -0.5em; 230 | } 231 | 232 | sub { 233 | bottom: -0.25em; 234 | } 235 | 236 | /* ========================================================================== 237 | Embedded content 238 | ========================================================================== */ 239 | 240 | /** 241 | * Remove border when inside `a` element in IE 8/9. 242 | */ 243 | 244 | img { 245 | border: 0; 246 | } 247 | 248 | /** 249 | * Correct overflow displayed oddly in IE 9. 250 | */ 251 | 252 | svg:not(:root) { 253 | overflow: hidden; 254 | } 255 | 256 | /* ========================================================================== 257 | Figures 258 | ========================================================================== */ 259 | 260 | /** 261 | * Address margin not present in IE 8/9 and Safari 5. 262 | */ 263 | 264 | figure { 265 | margin: 0; 266 | } 267 | 268 | /* ========================================================================== 269 | Forms 270 | ========================================================================== */ 271 | 272 | /** 273 | * Define consistent border, margin, and padding. 274 | */ 275 | 276 | fieldset { 277 | border: 1px solid #c0c0c0; 278 | margin: 0 2px; 279 | padding: 0.35em 0.625em 0.75em; 280 | } 281 | 282 | /** 283 | * 1. Correct `color` not being inherited in IE 8/9. 284 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 285 | */ 286 | 287 | legend { 288 | border: 0; /* 1 */ 289 | padding: 0; /* 2 */ 290 | } 291 | 292 | /** 293 | * 1. Correct font family not being inherited in all browsers. 294 | * 2. Correct font size not being inherited in all browsers. 295 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 296 | */ 297 | 298 | button, 299 | input, 300 | select, 301 | textarea { 302 | font-family: inherit; /* 1 */ 303 | font-size: 100%; /* 2 */ 304 | margin: 0; /* 3 */ 305 | } 306 | 307 | /** 308 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 309 | * the UA stylesheet. 310 | */ 311 | 312 | button, 313 | input { 314 | line-height: normal; 315 | } 316 | 317 | /** 318 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 319 | * All other form control elements do not inherit `text-transform` values. 320 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 321 | * Correct `select` style inheritance in Firefox 4+ and Opera. 322 | */ 323 | 324 | button, 325 | select { 326 | text-transform: none; 327 | } 328 | 329 | /** 330 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 331 | * and `video` controls. 332 | * 2. Correct inability to style clickable `input` types in iOS. 333 | * 3. Improve usability and consistency of cursor style between image-type 334 | * `input` and others. 335 | */ 336 | 337 | button, 338 | html input[type="button"], /* 1 */ 339 | input[type="reset"], 340 | input[type="submit"] { 341 | -webkit-appearance: button; /* 2 */ 342 | cursor: pointer; /* 3 */ 343 | } 344 | 345 | /** 346 | * Re-set default cursor for disabled elements. 347 | */ 348 | 349 | button[disabled], 350 | html input[disabled] { 351 | cursor: default; 352 | } 353 | 354 | /** 355 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 356 | * 2. Remove excess padding in IE 8/9/10. 357 | */ 358 | 359 | input[type="checkbox"], 360 | input[type="radio"] { 361 | box-sizing: border-box; /* 1 */ 362 | padding: 0; /* 2 */ 363 | } 364 | 365 | /** 366 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 367 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 368 | * (include `-moz` to future-proof). 369 | */ 370 | 371 | input[type="search"] { 372 | -webkit-appearance: textfield; /* 1 */ /* 2 */ 373 | box-sizing: content-box; 374 | } 375 | 376 | /** 377 | * Remove inner padding and search cancel button in Safari 5 and Chrome 378 | * on OS X. 379 | */ 380 | 381 | input[type="search"]::-webkit-search-cancel-button, 382 | input[type="search"]::-webkit-search-decoration { 383 | -webkit-appearance: none; 384 | } 385 | 386 | /** 387 | * Remove inner padding and border in Firefox 4+. 388 | */ 389 | 390 | button::-moz-focus-inner, 391 | input::-moz-focus-inner { 392 | border: 0; 393 | padding: 0; 394 | } 395 | 396 | /** 397 | * 1. Remove default vertical scrollbar in IE 8/9. 398 | * 2. Improve readability and alignment in all browsers. 399 | */ 400 | 401 | textarea { 402 | overflow: auto; /* 1 */ 403 | vertical-align: top; /* 2 */ 404 | } 405 | 406 | /* ========================================================================== 407 | Tables 408 | ========================================================================== */ 409 | 410 | /** 411 | * Remove most spacing between table cells. 412 | */ 413 | 414 | table { 415 | border-collapse: collapse; 416 | border-spacing: 0; 417 | } 418 | html { 419 | box-sizing: border-box; 420 | font-size: 16px; 421 | font-size: 1rem; 422 | } 423 | body { 424 | color: #444; 425 | } 426 | /** @define Comment; use strict */ 427 | 428 | /** 429 | * Display-type utilities 430 | */ 431 | 432 | .u-block { 433 | display: block !important; 434 | } 435 | 436 | .u-hidden { 437 | display: none !important; 438 | } 439 | 440 | /** 441 | * Completely remove from the flow but leave available to screen readers. 442 | */ 443 | 444 | .u-hiddenVisually { 445 | position: absolute !important; 446 | overflow: hidden !important; 447 | width: 1px !important; 448 | height: 1px !important; 449 | padding: 0 !important; 450 | border: 0 !important; 451 | clip: rect(1px, 1px, 1px, 1px) !important; 452 | } 453 | 454 | .u-inline { 455 | display: inline !important; 456 | } 457 | 458 | /** 459 | * 1. Fix for Firefox bug: an image styled `max-width:100%` within an 460 | * inline-block will display at its default size, and not limit its width to 461 | * 100% of an ancestral container. 462 | */ 463 | 464 | .u-inlineBlock { 465 | display: inline-block !important; 466 | max-width: 100%; /* 1 */ 467 | } 468 | 469 | .u-table { 470 | display: table !important; 471 | } 472 | 473 | .u-tableCell { 474 | display: table-cell !important; 475 | } 476 | 477 | .u-tableRow { 478 | display: table-row !important; 479 | } 480 | /** @define Avatar; use strict */ 481 | /** 482 | * Image utilties 483 | */ 484 | .u-imgCircle { 485 | border-radius: 50%; 486 | } 487 | .Avatar { 488 | background-image: -webkit-linear-gradient(top, #fff 0%, #c4d0ff 100%); 489 | background-image: linear-gradient(to bottom, #fff 0%, #c4d0ff 100%); 490 | } 491 | .Avatar-img { 492 | display: block; 493 | border-radius: 5px; 494 | } 495 | .Comment { 496 | max-width: 700px; 497 | margin: 25px; 498 | margin: 1.5625rem; 499 | background-color: #aaa; 500 | } 501 | .Comment-user { 502 | font-size: 14px; 503 | font-size: 0.875rem; 504 | border: 1px solid #ddd; 505 | } 506 | /** @define Post; use strict */ 507 | .Post { 508 | background-color: #fff; 509 | border-radius: 5px; 510 | } 511 | .Post-content { 512 | font-size: 18px; 513 | font-size: 1.125rem; 514 | color: #000; 515 | width: 33.333333333333336%; 516 | } 517 | .Post-content:hover { 518 | color: #008000; 519 | } 520 | -------------------------------------------------------------------------------- /gulp/notifyError.js: -------------------------------------------------------------------------------- 1 | var notify = require('gulp-notify'); 2 | 3 | module.exports = function() { 4 | notify.onError({ 5 | title: 'Compile Error', 6 | message: '<%= error.message %>' 7 | }).apply(this, arguments); 8 | 9 | this.emit('end'); 10 | }; 11 | -------------------------------------------------------------------------------- /gulp/paths.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var paths = { 4 | css: { 5 | stylusSrc: path.resolve('./stylus/**/*.styl'), 6 | dest: './dist', 7 | tmpDir: path.resolve('./.css-compiled'), 8 | mainFile: 'main.css', 9 | builtFile: 'components.css' 10 | } 11 | }; 12 | 13 | module.exports = paths; 14 | -------------------------------------------------------------------------------- /gulp/tasks/css.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var gulp = require('gulp'); 3 | var stylus = require('gulp-stylus'); 4 | var rename = require('gulp-rename'); 5 | var postcss = require('gulp-postcss'); 6 | var clip = require('gulp-clip-empty-files'); 7 | var bemLinter = require('postcss-bem-linter'); 8 | var cssnext = require('cssnext'); 9 | var del = require('del'); 10 | var notifyError = require('../notifyError'); 11 | var paths = require('../paths'); 12 | 13 | /** 14 | * Compile all Stylus into CSS files, placed in a temp directory 15 | */ 16 | gulp.task('stylus', function() { 17 | return gulp.src(paths.css.stylusSrc) 18 | .pipe(stylus().on('error', notifyError)) 19 | .pipe(gulp.dest(paths.css.tmpDir)); 20 | }); 21 | 22 | /** 23 | * Lint all built CSS files individually 24 | */ 25 | gulp.task('bemlint', ['stylus'], function() { 26 | return gulp.src(path.join(paths.css.tmpDir, '**/*.css')) 27 | .pipe(clip()) 28 | .pipe(postcss([ 29 | bemLinter(), 30 | ]).on('error', notifyError)) 31 | }); 32 | 33 | /** 34 | * Process CSS files with PostCSS and generate built file 35 | */ 36 | gulp.task('postcss', ['stylus', 'bemlint'], function() { 37 | return gulp.src(path.join(paths.css.tmpDir, paths.css.mainFile)) 38 | .pipe(postcss([ 39 | cssnext() 40 | ]).on('error', notifyError)) 41 | .pipe(rename(paths.css.builtFile)) 42 | .pipe(gulp.dest(paths.css.dest)); 43 | }); 44 | 45 | /** 46 | * Nuke temp CSS files 47 | * */ 48 | gulp.task('clean', ['stylus', 'bemlint', 'postcss'], function(cb) { 49 | del([paths.css.tmpDir], cb); 50 | }); 51 | 52 | gulp.task('css', ['stylus', 'bemlint', 'postcss', 'clean']); 53 | -------------------------------------------------------------------------------- /gulp/tasks/default.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var paths = require('../paths'); 3 | 4 | gulp.task('default', ['css']); 5 | gulp.task('watch', function() { 6 | gulp.watch(paths.css.stylusSrc, ['css']); 7 | }); 8 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var taskDir = './gulp/tasks'; 5 | var tasks = fs.readdirSync(taskDir); 6 | 7 | tasks.forEach(function(file) { 8 | require(path.resolve(taskDir, file)) 9 | }); 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stylus-suit", 3 | "version": "2.0.5", 4 | "description": "", 5 | "repository": "https://github.com/simonsmith/suitcss-with-stylus", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Simon Smith", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "cssnext": "^1.2.3", 14 | "del": "^1.1.1", 15 | "gulp": "^3.8.11", 16 | "gulp-clip-empty-files": "^0.1.1", 17 | "gulp-concat": "^2.5.2", 18 | "gulp-notify": "^2.2.0", 19 | "gulp-postcss": "^5.0.1", 20 | "gulp-rename": "^1.2.2", 21 | "gulp-stylus": "^2.0.1", 22 | "postcss-bem-linter": "^0.2.0" 23 | }, 24 | "dependencies": { 25 | "normalize-css": "^2.3.1", 26 | "suitcss-utils-display": "^0.4.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /stylus/base/base.styl: -------------------------------------------------------------------------------- 1 | /** 2 | * Base styles and any SUIT vars 3 | */ 4 | 5 | /** 6 | * Placed in a CSS block so that they are picked up by Rework after 7 | * Stylus compilation 8 | */ 9 | @css { 10 | @custom-media --sm-viewport (min-width: 36.25em); 11 | @custom-media --md-viewport (min-width: 49.375em); 12 | @custom-media --lg-viewport (min-width: 72.1875em); 13 | 14 | @import 'normalize-css'; 15 | } 16 | 17 | @require "vars"; 18 | 19 | html 20 | box-sizing border-box 21 | font-size 1rem 22 | 23 | body 24 | color $text-color 25 | -------------------------------------------------------------------------------- /stylus/base/vars.styl: -------------------------------------------------------------------------------- 1 | /** 2 | * Colors 3 | */ 4 | $text-color = #444 5 | -------------------------------------------------------------------------------- /stylus/components/avatar.styl: -------------------------------------------------------------------------------- 1 | /** @define Avatar; use strict */ 2 | 3 | @css { 4 | @import "suitcss-utils-display"; 5 | @import "../utils/img"; 6 | } 7 | 8 | $Avatar-img-borderRadius = 5px 9 | 10 | .Avatar 11 | background-image linear-gradient(to bottom, #FFFFFF 0%, #C4D0FF 100%) 12 | 13 | .Avatar-img 14 | display block 15 | border-radius $Avatar-img-borderRadius 16 | -------------------------------------------------------------------------------- /stylus/components/comment.styl: -------------------------------------------------------------------------------- 1 | /** @define Comment; use strict */ 2 | 3 | @css { 4 | @import "suitcss-utils-display"; 5 | @import "./avatar"; 6 | } 7 | 8 | $Comment-bgColor = #aaa 9 | $Comment-user-borderColor = #ddd 10 | 11 | .Comment 12 | max-width 700px 13 | margin 1.5625rem 14 | background-color $Comment-bgColor 15 | 16 | .Comment-user 17 | font-size .875rem 18 | border 1px solid $Comment-user-borderColor 19 | -------------------------------------------------------------------------------- /stylus/components/post.styl: -------------------------------------------------------------------------------- 1 | /** @define Post; use strict */ 2 | 3 | @css { 4 | @import "suitcss-utils-display"; 5 | @import "./avatar"; 6 | } 7 | 8 | $Post-bgColor = #fff 9 | 10 | .Post 11 | background-color $Post-bgColor 12 | border-radius 5px 13 | 14 | .Post-content 15 | font-size 1.125rem 16 | color: #000 17 | width (100% / 3) 18 | 19 | &:hover 20 | color green 21 | -------------------------------------------------------------------------------- /stylus/main.styl: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the manifest for all your top level components. 3 | * Use @css so that the imports are left alone by Stylus and picked up 4 | * by Rework 5 | */ 6 | 7 | @css { 8 | @import "./base/base"; 9 | @import "./components/comment"; 10 | @import "./components/post"; 11 | } 12 | -------------------------------------------------------------------------------- /stylus/utils/img.styl: -------------------------------------------------------------------------------- 1 | /** 2 | * Image utilties 3 | */ 4 | 5 | .u-imgCircle 6 | border-radius 50% --------------------------------------------------------------------------------