CSS preprocessors for the best of both worlds (From the Front 2014)

CSS preprocessors for the best of both worlds (From the Front 2014)

While purists propagate to not mix content structure (HTML), presentation (CSS) and behavior (JavaScript) layers for the reason of maintainability, concepts like OOCSS aim at the same goal, but from a different angle: reusable CSS code, at the expense of bloated, presentational mark-up, violating the separation of concerns. Can’t we have both, clean mark-up and reusable CSS? The benefits of both approaches without the drawbacks?
Yes, we can. This talk shows how to take the OO concept out of the HTML and put it where it belongs to: into the style sheet. Not directly into the CSS though, but into an intermediate layer provided by a CSS preprocessor like Sass.
Like Ogers and onions, concepts have layers. Unlike Ogers and onions, they don’t always stink, but eventually lead to the best of both worlds.

Video: https://www.youtube.com/watch?v=ot-LoU0MGb0

051dbe29a972707cde167602b38c9778?s=128

Gunnar Bittersmann

September 18, 2014
Tweet

Transcript

  1. OF BOTH WORLDS CSS PREPROCESSORS

  2. CSS PREPROCESSORS

  3. OF BOTH WORLDS

  4. OUT OF YOUR BEANS COFFEE GRINDERS 98 € PICK Peugeot

    Coffee grinder 76 € PICK Kalita Coffee grinder 54 € PICK Heyde Coffee grinder
  5. <html id="page-products"> <ul class="product-list"> <li class="product-item"> <img class="product-image" src="…" alt="…"/>

    <span class="product-brand">…</span> <span class="product-model">…</span> <span class="product-description">…</span> <span class="product-price">…</span> <button class="btn btn-cta">Pick me!</button> </li> ộ </ul>
  6. .product-list { … } .product-item { … } .product-image {

    … } .product-brand { … } .product-model { … } .product-description { … } .product-price { … } .btn { … } .btn-cta { … }
  7. OUT OF YOUR BEANS COFFEE GRINDERS BUY Peugeot Coffee grinder

    Kalita Coffee grinder 98 € 76 € In your basket: total amount 174 €
  8. <html id="page-checkout"> <ul class="checkout-list"> <li class="checkout-item"> <img class="checkout-image" src="…" alt="…"/>

    <span class="checkout-brand">…</span> <span class="checkout-model">…</span> <span class="checkout-description">…</span> <span class="checkout-price">…</span> </li> ộ </ul>
  9. .product-list { … } .product-item { … } .product-image {

    … } .product-brand { … } .product-model { … } .product-description { … } .product-price { … } .btn { … } .btn-cta { … } .checkout-list { … } .checkout-item { … } .checkout-image { … } .checkout-brand { … } .checkout-model { … } .checkout-description { … } .checkout-price { … }
  10. <html id="page-products"> <ul class="product-list"> <li> <img class="product-image" src="…" alt="…"/> <span

    class="brand">…</span> <span class="model">…</span> <span class="description">…</span> <span class="price">…</span> <button>Pick me!</button> </li> ộ </ul>
  11. <html id="page-checkout"> <ul class="product-list"> <li> <img class="product-image" src="…" alt="…"/> <span

    class="brand">…</span> <span class="model">…</span> <span class="description">…</span> <span class="price">…</span> </li> ộ </ul>
  12. #page-products .product-list { … } #page-products .product-list li { …

    } #page-products .product-list .product-image { … } #page-products .product-list .brand { … } #page-products .product-list .model { … } #page-products .product-list .description { … } #page-products .product-list .price { … } #page-products .product-list button { … } #page-checkout .product-list { … } #page-checkout .product-list li { … } #page-checkout .product-list .product-image { … } #page-checkout .product-list .brand { … } #page-checkout .product-list .model { … } #page-checkout .product-list .description { … } #page-checkout .product-list .price { … }
  13. #page-products { ộ .product-list { ộ li { … }

    .product-image { … } .brand { … } .model { … } .description { … } .price { … } button { … } } } nesting
  14. #page-checkout { ộ .product-list { ộ li { … }

    .product-image { … } .brand { … } .model { … } .description { … } .price { … } } } nesting
  15. @import "_page-products"; @import "_page-checkout"; ộ modularization

  16. @import "_page-products"; @import "_page-checkout"; ộ modularization @import "_colors"; @import "_type";

    @import "_image-replacement"; @import "_page-layout";
  17. Atomic design (Brad Frost)

  18. Atomic design (Brad Frost) ATOMS MOLECULES ORGANISMS TEMPLATES PAGES

  19. no classes! Look ma,

  20. <html id="page-products" vocab="http://schema.org/"> <ul class="product-list"> <li typeof="Product"> <img property="image" src="…"

    alt="…"/> <span property="name">…</span> <span property="brand">…</span> <span property="model">…</span> </span> <span property="description">…</span> <span property="offers" typeof="Offer"> <span class="price">…</span> <link property="availability" href="http://schema.org/InStock"/> </span> <button>Pick me!</button> </li> ộ </ul>
  21. #page-products { ộ .product-list { ộ li { … }

    [property="image"] { … } [property="brand"] { … } [property="model"] { … } [property="description"] { … } [property="price"] { … } button { … } } }
  22. None
  23. One more cup of coffee, please! <html lang="en"> <h1 >One

    more cup of coffee, please!</h1>
  24. ONE MORE CUP OF COFFEE, PLEASE! <html lang="en"> <h1 class="uppercase">One

    more cup of coffee, please!</h1> .uppercase { text-transform: uppercase }
  25. one more cup of coffee, please! <html lang="en"> <h1 class="lowercase">One

    more cup of coffee, please!</h1> .uppercase { text-transform: uppercase } .lowercase { text-transform: lowercase }
  26. UN’ ALTRA TAZZA DI CAFFÈ, PER FAVORE! <html lang="it"> <h1

    class="uppercase">Un’ altra tazza di caffè, per favore!</h1> .uppercase { text-transform: uppercase } .lowercase { text-transform: lowercase }
  27. un’ altra tazza di caffè, per favore! <html lang="it"> <h1

    class="lowercase">Un’ altra tazza di caffè, per favore!</h1> .uppercase { text-transform: uppercase } .lowercase { text-transform: lowercase }
  28. NOCH EIN TÄSSCHEN KAFFEE, BITTE! <html lang="de"> <h1 class="uppercase">Noch ein

    Tässchen Kaffee, bitte!</h1> .uppercase { text-transform: uppercase } .lowercase { text-transform: lowercase }
  29. noch ein tässchen kaffee, bitte! <html lang="de"> <h1 class="lowercase">Noch ein

    Tässchen Kaffee, bitte!</h1> .uppercase { text-transform: uppercase } .lowercase { text-transform: lowercase }
  30. Noch ein Tässchen Kaffee, bitte! <html lang="de"> <h1 class="lowercase">Noch ein

    Tässchen Kaffee, bitte!</h1> .uppercase { text-transform: uppercase } .lowercase { text-transform: lowercase } .lowercase:lang(de) { text-transform: none } ✘  
  31. PRESENTATIONAL MARKUP

  32. Noch ein Tässchen Kaffee, bitte! <html lang="de"> >Noch ein Tässchen

    Kaffee, bitte!</h1> h1 { text-transform: lowercase } h1:lang(de) { text-transform: none } ✔   <h1
  33. QA'VIN LATLH TU'LUM HINOBNES! <html lang="tlh"> <h1>qa'vIn latlh tu'lum HInobneS!</h1>

    h1 { text-transform: uppercase }
  34. qa'vin latlh tu'lum hinobnes! <html lang="tlh"> <h1>qa'vIn latlh tu'lum HInobneS!</h1>

    h1 { text-transform: lowercase }
  35. qa'vIn latlh tu'lum HInobneS! <html lang="tlh"> <h1>qa'vIn latlh tu'lum HInobneS!</h1>

    h1 { text-transform: lowercase } :lang(tlh) { text-transform: none !important} ✔  
  36. <div id="pick-me" class="hidden"> .hidden { display: none }

  37. <div id="pick-me" class="hidden"> .hidden { display: none !important }

  38. <div id="pick-me" class="hidden"> .hidden { display: none !important } $('#pick-me').show();

    document.querySelector('#pick-me').style.display = 'block';
  39. <div id="pick-me" class="hidden"> .hidden { display: none !important } $('#pick-me').show();

    document.querySelector('#pick-me').style.display = 'block';
  40. $('#pick-me').removeClass('hidden'); document.querySelector('#pick-me').classList.remove('hidden'); <div id="pick-me" class="hidden"> .hidden { display: none !important

    }
  41. <div id="pick-me" hidden> $('#pick-me').removeAttr('hidden'); document.querySelector('#pick-me').removeAttribute('hidden');

  42. <div id="pick-me" hidden> $('#pick-me').removeAttr('hidden'); document.querySelector('#pick-me').removeAttribute('hidden'); [hidden] { display: none !important

    }
  43. Structure (HTML/DOM) Behavior (JavaScript) Presentation (CSS)

  44. Structure (HTML/DOM) Presentation (CSS) Behavior (JavaScript) Separation of concerns

  45. OOCSS SEND ORDER CHANGE ADDRESS <button type="submit" class="btn btn-large btn-red">send

    order</button> <button type="button" class="btn btn-small btn-black">change address</button> <button type="button" class="btn btn-small btn-black">change basket</button> CHANGE BASKET
  46. SEND ORDER CHANGE ADDRESS <button type="submit" class="btn btn-large btn-red">send order</button>

    <button type="button" class="btn btn-small btn-black">change address</button> <button type="button" class="btn btn-small btn-black">change basket</button> CHANGE BASKET
  47. .btn { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius:

    0.375em; } .btn-large { padding: 0.375em; font-size: 1.25rem; } .btn-small { padding: 0.25em font-size: 1rem; } .btn-red { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } .btn-black { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } Stylesheet OOCSS
  48. OOCSS Object Oriented CSS CSS “object”: a repeating visual pattern,

    which can be abstracted into an independent snippet of HTML, CSS, and possibly JavaScript. Goal: reusable code, maintainability, performance Principles: 1.  Separate structure and skin 2.  Separate container and content Means: •  class selectors •  no element type selectors •  no ID selectors •  no descendant combinators •  presentational classes in the mark-up
  49. OOCSS { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius:

    0.375em; } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } btn btn-large btn-small btn-red btn-black <button> <button> <button> Markup Stylesheet class selector class attribute “CSS classes”
  50. OOCSS { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius:

    0.375em; } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } btn btn-large btn-small btn-red btn-black <button> <button> <button> Markup Stylesheet class selector class attribute “CSS classes”
  51. PRESENTATIONAL MARKUP

  52. None
  53. None
  54. OOCSS

  55. “CSS CLASSES”

  56. None
  57. None
  58. OOCSS Object Oriented CSS CSS “object”: a repeating visual pattern,

    which can be abstracted into an independent snippet of HTML, CSS, and possibly JavaScript. Goal: reusable code, maintainability, performance Principles: 1.  Separate structure and skin 2.  Separate container and content Means: •  class selectors •  no element type selectors •  no ID selectors •  no descendant combinators •  presentational classes in the mark-up
  59. None
  60. OOCSS Pros •  presentational markup •  bigger HTML files • 

    reusable code units •  smaller CSS file •  selector specificity •  selector performance Cons
  61. None
  62. OOCSS Pros •  presentational markup •  bigger HTML files • 

    reusable code units •  selector specificity •  selector performance Cons
  63. None
  64. OOCSS Pros •  presentational markup •  bigger HTML files • 

    reusable code units •  selector performance Cons
  65. None
  66. OOCSS Pros •  presentational markup •  bigger HTML files • 

    reusable code units Cons
  67. OOCSS { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius:

    0.375em; } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } btn btn-large btn-small btn-red btn-black <button> <button> <button> Markup Stylesheet
  68. { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em;

    } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } btn btn-large btn-small btn-red btn-black <button> <button> <button> Markup Stylesheet
  69. { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em;

    } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } btn btn-large btn-small btn-red btn-black <button> <button> <button> Markup Stylesheet
  70. { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em;

    } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } { @include btn; @include btn-large; @include btn-red; } { @include btn; @include btn-small; @include btn-black; } @mixin btn @mixin btn-large @mixin btn-small @mixin btn-red @mixin btn-black [type="submit"] [type="button"] mixins
  71. { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em;

    } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } { @include btn; @include btn-large; @include btn-red; } { @include btn; @include btn-small; @include btn-black; } @mixin btn @mixin btn-large @mixin btn-small @mixin btn-red @mixin btn-black [type="submit"] [type="button"] <button type="submit">send order</button> <button type="button">change address</button> <button type="button">change basket</button> mixins
  72. [type="submit"] { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius:

    0.375em; padding: 0.375em; font-size: 1.25rem; background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } [type="button"] { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em; padding: 0.25em; font-size: 1rem; background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } { @include btn; @include btn-large; @include btn-red; } { @include btn; @include btn-small; @include btn-black; } [type="submit"] [type="button"]
  73. .btn .btn-large .btn-small .btn-red .btn-black { min-width: 8rem; font-family: "League

    Gothic"; text-align: center; border-radius: 0.375em; } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } [type="button"] [type="submit"] extends { @extend .btn; @extend .btn-large; @extend .btn-red; } { @extend .btn; @extend .btn-small; @extend .btn-black; }
  74. .btn .btn-large .btn-small .btn-red .btn-black { min-width: 8rem; font-family: "League

    Gothic"; text-align: center; border-radius: 0.375em; } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } [type="button"] [type="submit"] extends { @extend .btn; @extend .btn-large; @extend .btn-red; } { @extend .btn; @extend .btn-small; @extend .btn-black; } , [type="submit"], [type="button"] , [type="submit"] , [type="button"] , [type="submit"] , [type="button"]
  75. .btn .btn-large .btn-small .btn-red .btn-black { min-width: 8rem; font-family: "League

    Gothic"; text-align: center; border-radius: 0.375em; } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } [type="button"] [type="submit"] extends { @extend .btn; @extend .btn-large; @extend .btn-red; } { @extend .btn; @extend .btn-small; @extend .btn-black; } , [type="submit"], [type="button"] , [type="submit"] , [type="button"] , [type="submit"] , [type="button"] <button type="submit">send order</button> <button type="button">change address</button> <button type="button">change basket</button>
  76. { @extend %btn; @extend %btn-large; @extend %btn-red; } { @extend

    %btn; @extend %btn-small; @extend %btn-black; } %btn %btn-large %btn-small %btn-red %btn-black { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em; } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } [type="button"] [type="submit"] [type="submit"] placeholders
  77. { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em;

    } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } [type="submit"], [type="button"] [type="submit"] [type="button"] [type="submit"] [type="button"] { @extend %btn; @extend %btn-large; @extend %btn-red; } { @extend %btn; @extend %btn-small; @extend %btn-black; } [type="button"] [type="submit"] [type="submit"] placeholders
  78. { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em;

    } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } [type="submit"], [type="button"] [type="submit"] [type="button"] [type="submit"] [type="button"] { @extend %btn; @extend %btn-large; @extend %btn-red; } { @extend %btn; @extend %btn-small; @extend %btn-black; } [type="button"] [type="submit"] [type="submit"] placeholders
  79. { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em;

    } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } %btn %btn-large %btn-small %btn-red %btn-black <button> <button> <button> Markup CSS Sass
  80. %btn %btn-large %btn-small %btn-red %btn-black #page-other [type="submit"] { @extend %btn;

    @extend %btn-small; @extend %btn-red; } { @extend %btn; @extend %btn-large; @extend %btn-red; } { @extend %btn; @extend %btn-small; @extend %btn-black; } #page-checkout #page-checkout { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em; } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } [type="submit"] [type="button"]
  81. SEND ORDER CHANGE ADDRESS <button type="submit" id="ctrl-send-order">send order</button> <button type="button"

    id="ctrl-change-address">change address</button> <button type="button" id="ctrl-change-basket">change basket</button> CHANGE BASKET
  82. #ctrl-send-order #ctrl-change-address, #ctrl-change-basket { @extend %btn; @extend %btn-large; @extend %btn-red;

    } { @extend %btn; @extend %btn-small; @extend %btn-black; } { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em; } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } %btn %btn-large %btn-small %btn-red %btn-black
  83. @mixin btn { min-width: 8rem; font-family: "League Gothic"; text-align: center;

    border-radius: 0.375em; } %btn { @include btn; } @mixin %btn-large { padding: 0.375em; font-size: 1.25rem; } %btn-large { @include btn-large; } ộ mixins and extends
  84. None
  85. None
  86. nesting modularization mixins extends placeholders OOSass

  87. None
  88. PRESENTATIONAL MARKUP

  89. Structure (HTML/DOM) Presentation (CSS) Behavior (JavaScript) Separation of concerns

  90. { min-width: 8rem; font-family: "League Gothic"; text-align: center; border-radius: 0.375em;

    } { padding: 0.375em; font-size: 1.25rem; } { padding: 0.25em font-size: 1rem; } { background-color: hsl(2, 42%, 39%); background-image: linear-gradient(…); } { background-color: hsl(0, 0%, 20%); background-image: linear-gradient(…); } btn btn-large btn-small btn-red btn-black <button> <button> <button> Markup Stylesheet
  91. None
  92. WHICH SIDE ARE YOU ON?

  93. None