DEV Community

Michael Scherr
Michael Scherr

Posted on • Updated on

Switching from SASS to PostCSS

Ever since PostCSS came out, I've exclusively used it for Autoprefixer. I didn't really dig into the power of PostCSS and how it could actually be a comparable solution to SASS.

This article will go into depth about my skepticisms, and how I was able to find solutions using PostCSS.

The Push

What originally considered me to switch was my interest in Tailwind CSS. I love the ideas of utility libraries, but found it difficult to sell to other developers. However, we've been looking for ways to optimize development time, so Tailwind felt like a good choice. It's built with PostCSS in mind, so I at least wanted to give it a try.

Previous Approach

My previous approach was a mix of SASS, BEM, ITCSS, & SMACSS. They solved the following issues:

  • SASS - variables, loops, imports, nested selectors, etc
  • BEM - reduce specificity and allow for easier additions / modifications to code
  • ITCSS - (same as BEM), but also allows for an inverted triangle hierarchy for specificity gets more and more specific.
  • SMACSS - used mostly for modifier classes, like .js-is-active, .has-posts, etc.

This worked really well for me. I saw the power of utility libraries, but figured I would let them evolve and try them out later.

The Switch

I won't bore you with the details, but I went through many iterations like:

  • PostCSS only, no plugins besides Tailwind
  • PostCSS + SASS
  • PostCSS only, with Tailwind and other complementary plugins

The core issue with the PostCSS + SASS approach was the inevitable duplication. For example, these situations can't work together:

  * cannot set $font-size-px-base in PostCSS
  * since SASS gets run first, so would have
  * to duplicate code
@function rem($pixels, $context: $font-size-px-base) {
  @if (unitless($pixels)) {
    $pixels: $pixels * 1px;
  @if (unitless($context)) {
    $context: $context * 1px;
  @return $pixels / $context * 1rem;

  * can't use Tailwind variables
  * in the SASS function `scale` since
  * SASS runs before PostCSS
.selector {
  font-size: scale(font-size, theme('fontSize.lg'), theme('fontSize.xl'));

  * Interpolation was also a nightmare
  * I couldn't figure out how to make
  * something like this work
  * even with PostCSS Simple Vars
.selector {
  $path: theme('path.theme');

  background-image: url($(path)/test.png);

Final Solution

The final solution involved PostCSS only, with Tailwind and other complementary plugins.

When I thought about it, I was really using SASS for:

  • imports
  • mixins
  • functions
  • variables
  • nested selectors - rarely, but nice to have, especially for pseudo selectors
  • loops - very rarely, but nice to have

PostCSS has solutions for all of these once I realized the power of their plugin system. Furthermore, I found out some plugins, like for functions and mixins, allow you to inject Javascript functions into PostCSS. I was sold.


PostCSS Imports gives the exact same functionality as SASS Imports, so it was an easy switch.

@import 'tailwindcss/base';

@import 'tailwindcss/components';
@import 'components/btn';

@import 'tailwindcss/utilities';


PostCSS Functions allows you to write functions IN JAVASCRIPT and inject them into PostCSS. IN JAVASCRIPT. I LOVE JAVASCRIPT.

// functions.js

let settings = require('./settings');

let rem = (pixels, context = settings.defaults.fontSizePxBase) => {
  pixels = parseFloat(pixels);

  let result = pixels / context;

  return `${result}rem`;

module.exports = {


PostCSS Mixins is the same deal as PostCSS Functions.

// mixins.js

let placeholder = (mixin, immediateSelector = true) => {
  let vendors = [

  return vendors.reduce((prev, vendor) => {
    let selector = immediateSelector ? `&${vendor}` : vendor;

    prev[selector] = {
      '@mixin-content': {},

    return prev;
  }, {});

module.exports = {

Other Notable Plugins

Example PostCSS Config File

// postcss.config.js

let functions = require('./functions');
let mixins = require('./mixins');

module.exports = function(context) {
   * context comes from what you pass
   * into `gulp-postcss`, omitting detailing here
   * since `gulp` is an implementation detail
  let { options } = context;

  let plugins = [
      path: [options.paths.components, options.paths.styles, 'node_modules'],

  return {

I hope this at least got you interested in PostCSS and what it's powerful plugin system can provide for your next project.

Discussion (3)

michaelhays profile image
Michael Hays

Nice write-up! Check out postcss-preset-env, which does the same thing as a lot of your plugins, in just one:

michaeldscherr profile image
Michael Scherr Author

Thanks! I'll check it out soon.

epsi profile image
E.R. Nurwijayadi

Fascinating explanation.

I also make article about PostCSS.

PostCSS Loop Spacing Helper