Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Why I'm Recommending Sass and BEM

Why I'm Recommending Sass and BEM

If we had a 100% React SPA, I would be all for using CSS Modules and PostCSS, as we would have complete control in the JavaScript environment.

Unfortunately, as we're creating a hybrid React and static app, we have to make concessions.

We should have only one system of writing CSS, naming and styling components for both React and static, so I recommend we use (node) Sass and BEM. We don't want developers to have to learn and switch between different methods when they're working in different parts of the site.

Luckily, using Sass and BEM together is a tried and tested solution to various problems: consistency, architecture, componentisation, specificity, performance and more.

Consistency

To create a unified look and feel across the site, we need to have standard variables which are shared across all components (static & React), layout modules, utilities, z-indexes etc.

If we were to use CSS Modules and PostCSS for separate parts of the app, we would need to try and find a solution where we could share variables across CSS and JS, which won't be easy in my opinion.

While I understand that components should be completely portable, that also means they're full of "magic numbers" as we can't re-use consistent spacings, font sizes and colours. And only our React components would suffer from this, which again, makes our codebase even more inconsistent.

We shouldn't be concerned with what technology renders the component, just that they should all be created equally.

Sass Maps

Historically, and effectively, I use Sass maps to store more complex data structures, and use helper methods and looping to access them where necessary.

Let's look at customisable grid:

$l-grid-breakpoints-and-sizes: (
    0:    (20%, 25%, 33.3333%, 40%, 50%, 60%, 66.6666%, 80%, 100%),
    760:  (50%),
    1000: (25%, 33.3333%, 50%),
    1200: (33.3333%, 66.6666%),
    1400: (25%)
);

With this approach, we can specify exactly which grid percentages are created for specific breakpoints. This allows us to:

  1. Only output the amount of CSS that's necessary
  2. Be aware of what we're using, so we can more easily consolidate and normalise when we may get designs that "stray" from what we've done before.
  3. Hopefully stop us from just adding any random values.

To use it, we'd loop through the map in Sass, and output our relevant classes to produce something like:

.l-grid__cell--60 {
  flex-basis: 60%;
  max-width: 60%; }

.l-grid__cell--66 {
  flex-basis: 66.6666%;
  max-width: 66.6666%; }

.l-grid__cell--80 {
  flex-basis: 80%;
  max-width: 80%; }

.l-grid__cell--100 {
  flex-basis: 100%;
  max-width: 100%; }

@media (min-width: 47.5em) {
  .l-grid__cell--50-at-760 {
    flex-basis: 50%;
    max-width: 50%; } }

@media (min-width: 62.5em) {
  .l-grid__cell--25-at-1000 {
    flex-basis: 25%;
    max-width: 25%; }
  .l-grid__cell--33-at-1000 {
    flex-basis: 33.3333%;
    max-width: 33.3333%; }
  .l-grid__cell--50-at-1000 {
    flex-basis: 50%;
    max-width: 50%; } }

This is not possible to do with PostCSS. Iterating over maps is not supported and doesn't look like it will be any time soon.

Similarly, spacings and colours use complex maps, and are often iterated over to produce "utility" classes, like so.

$u-spacings: (
    default: 10px,
    large: 20px,
    zero: 0
) !default;

$u-spacing-types: (
    margin: (
        m: margin,
        mb: margin-bottom,
        ml: margin-left,
        mr: margin-right,
        mt: margin-top
    ),
    padding: (
        p: padding,
        pb: padding-bottom,
        pl: padding-left,
        pr: padding-right,
        pt: padding-top
    )
) !default;

@each $spacing-name, $spacing-value in $u-spacings {
    @each $type, $map in $u-spacing-types {
        @each $class-name, $property-name in get($u-spacing-types, $type) {
            %u-spacing-#{$class-name}-#{$spacing-name},
            .u-spacing-#{$class-name}-#{$spacing-name} {
                @each $property in $property-name {
                    #{$property}: $spacing-value !important;
                }
            }
        }
    }
}

which would result in:

.u-spacing-m-default {
  margin: 10px !important; }

.u-spacing-mb-default {
  margin-bottom: 10px !important; }

.u-spacing-ml-default {
  margin-left: 10px !important; }

.u-spacing-mr-default {
  margin-right: 10px !important; }

.u-spacing-mt-default {
  margin-top: 10px !important; }
  
.u-spacing-p-default {
  padding: 10px !important; }

.u-spacing-pb-default {
  padding-bottom: 10px !important; }

.u-spacing-pl-default {
  padding-left: 10px !important; }

.u-spacing-pr-default {
  padding-right: 10px !important; }

.u-spacing-pt-default {
  padding-top: 10px !important; }

I can also use the spacings from within my components, with a spacing(SPACING_NAME) helper method, like so:

.component-name {
    padding: spacing(default);
}

z-index conflicts are often an interesting problem, and one which I haven't experienced in years, due to a neat, Sass solution I came up with.

We set up a $z-indexes map like so:

$z-indexes: (
    main: (
        foo,
        bar,
        tooltip
    ),
    header: (),
    modal: ()
);

and we can apply the relevant z-index like this:

/* .header */
.header { z-index: z(header); }

/* .main elements */
.main { z-index: z(main); }
.foo { z-index: z(main, foo); }
.bar { z-index: z(main, bar); }
.tooltip { z-index: z(main, tooltip); }

/* .modal */
.modal { z-index: z(modal); }

which results in:

/* .header */
.header { z-index: 2; }

/* .main elements */
.main { z-index: 1; }
.foo { z-index: 1; }
.bar { z-index: 2; }
.tooltip { z-index: 3; }

/* .modal */
.modal { z-index: 3; }

In this example, nothing within .main will ever be able to be above .header, and .modal sits atop everything. Basically, you’ll never have z-index conflicts again \o/

Architecture

I have written quite a lot about architecture, but quickly, I like to break my styles down to:

  • UI Framework
  • Settings
  • Mixins
  • Animations
  • Base
  • Objects
  • Components
  • Layout
  • Pages
  • Utilities

This allows us to clearly separate individual parts of the application.

Within each group, partials should be added alphabetically, with a Sass partial for every root Block. This enables you to easily locate a particular partial, and highlights that with BEM, the order in which they're included is not important.

For React and static components, I envisage that they will follow the same structure and naming convention as the styles, so we can easily locate them. Thus, we might have something like:

css/
    components/
        _a-react-component.scss
        _a-static-component.scss 

components/
    react/
        AReactComponent.js
    static/
        a-static-component.hbs

The components would be written in BEM, as it really is a beautiful way of describing hierarchy and variations within a component.

In fact, I like it so much, that I ported the concept to React Native!

We also shouldn't have to do any additional styling work when converting a static component to React and vice-versa. Using one system for everything, makes all maintenance easier.

Future-proofing

It is one of Sass's main goals to not care about the syntax of CSS, thus it will not add any features that causes compatibility issues with the CSS spec.

So, unlike using PostCSS currently, we can actually benefit from using "static" Sass variables, and dynamic, official CSS ones (custom properties).

Within a Sass file, we can write:

.component-name {
     border-width: $component-name-border-width;
     color: var(--component-name-color);
 }

Which would allow us to have consistent app-wide variables that browser doesn't need to know about, as well as taking advantage of theme-level, client-side variables in modern browsers.

So, although using PostCSS for variables is "spec-compliant", since they're not backwards compatible, they currently all get converted to "static" values. Interestingly, if we decided to remove PostCSS when the feature was widely available, we'd actually do ourselves harm.

Since Sass variables are pre-compiled and represented as standard string values in the outputted CSS, they are more performant, and a lot smaller.

To validate this, I did a quick experiment.

  1. I created 1000 random colors
  2. I assigned these to "static" class names and applied them to 1000 divs; and
  3. I assigned these to CSS variables, and applied them to 1000 divs.

The difference was quite astounding.

Static Variables:

  • CSS size: 33 kB, 7.22 kB gzip
  • Average rendering time: 15ms

CSS Variables:

  • CSS size: 57 kB, 12.7 kB gzip
  • Average rendering time: 51ms

Since the variables aren't pre-compiled, they have to uniquely exist in the resulting CSS which bloats the stylesheet, and as they're "live" variables, the browser takes longer to render them, as it has extra work to do under-the-hood.

You can see my tests here:

  1. Static variables
  2. CSS Variables

My takeaway: CSS variables are a sometimes food™

Conclusion

Sass and PostCSS can co-exist as they can solve different problems. Sass allows us to write CSS in more of a "programming" way, which provides multiple opportunities for building scalable, maintainable applications.

PostCSS has tried to recreate some of these features, but it cannot compete (yet).

While PostCSS has been touted as the "Babel of CSS", this isn't strictly true. The majority of Babel usage is to only convert from one versioned spec to another, while PostCSS plugins are written by and for anyone, sometimes to add future specs, polyfills, completely random things or partially recreate Sass's features. As our requirements and needs will change over time, we will constantly have to try and find plugins and solutions just to be able to do what we can with Sass out-of-the-box – as we're already struggling to do.

So, hopefully this justifies my decision of sticking with Sass and BEM, but please, let's discuss it.

And if you'd like to read more of "my" front-end guidelines, feel free. It's still a work-in-progress, but I like to think there's some good stuff in there :)

Now let's go build some great stuff!

P.S. We also need to create a universal SVG <use> icon system, and not use a combination of icon fonts (which are really bad) and custom SVGs in JavaScript.

Consistency ftw!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment