Building TailwindCSS with Sass

Building TailwindCSS with Sass

·

5 min read

According to the 2020 state of CSS report, Tailwind CSS stood out as one of the tools with the highest satisfaction ratio (86%). Tailwind has become a go-to solution to build web applications. And the reason is justified you can build and ship websites faster and still have 100% control over the styling unlike other CSS frameworks (Bulma, bootstrap, etc).

I was always curious about how tailwind generates 3645.2kB of CSS which is full of utility classes from "p-0 , mx-1 ... pt-40" to the color variants and all.

This blog covers how you can build TailwindCSS utility classes in a very minimal way and doesn't cover the plugin/config ecosystem it provides.

We will be building it with the help of Sass (SCSS) or syntactically awesome stylesheets. If you don't know what this is you can watch this video to get an overview. Using Sass will help us to write CSS code more efficiently and programmatic.

A short demo:

Tailwind Sass demo

Browser doesn't understand what Sass actually is, so we need to compile it to CSS. We will use a bunch of tools to accomplish that. Now let's start by setting up our project.

yarn init -y
# or
npm init -y

This would create a minimal package.json file for our project. Now we will add some devDependencies

yarn add -D autoprefixer node-sass postcss postcss-cli
# or
npm install autoprefixer node-sass postcss postcss-cli --save-dev

A brief explanation of what these actually do:

  • node-sass helps in compiling .scss / .sass files to css
  • postcss parser which tokenizes CSS code to create an abstract syntax tree, we will be using it's plugins
  • autoprefixer plugin of postcss, adds vendor prefixes to CSS rule

Now, will add some scripts in package.json, feel free to modify the input and output of your build.

"scripts": {
    "build": "rm -rf build && yarn build-sass",
    "build-sass": "node-sass --output-style expanded src/index.scss ./build/index.css  && yarn  build-autoprefix",
    "build-autoprefix": "postcss --use autoprefixer --map --output ./build/index.css ./build/index.css"
}
  • build clears build directory and runs build-sass
  • build-sass compiles scss and build css in a new directory then runs build-autoprefix
  • build-autoprefix adds vendor prefixes and generates source maps

Now let's create our input file src/index.scss, if you write some valid scss code and run yarn build you should see the compiled css created.

Now that our project is setup we will look into creating text-color & background-color variants of tailwind. The base idea is to loop over all colors you have and generate utility classes (text-gray-100, bg-gray-100 ... etc).

Let's start by first creating a new directory utils and a file inside it _colors.scss. Adding a leading underscore in scss files is called partials this helps us in modularizing the code and importing it into other files.

To declare our colors we will use Maps which is provided by SASS to declare key-value pairs.

// utils/_colors.scss
$colors: (
  'gray-100' #f7fafc,
  'gray-200' #edf2f7,
  'gray-300' #e2e8f0,
  'gray-400' #cbd5e0,
  'gray-500' #a0aec0,
  'gray-600' #718096,
  'gray-700' #4a5568,
  'gray-800' #2d3748,
  'gray-900' #1a202c
);

Now to loop over these we will use @each rule this helps us to evaluate code for each element of a lists/maps to generate repetitive styles which is exactly what want to do.

// utils/_colors.scss
@each $name, $hex in $colors {
  .text-#{$name} {
    color: $hex;
  }
  .bg-#{$name} {
    background-color: $hex;
  }
}

Now let's import this partial in our input file

// src/index.scss
@import '../utils/colors';

On running yarn build you should be able to see all your utility classes being generated.

/* build/index.css */
.text-gray-100 {
  color: #f7fafc;
}

.bg-gray-100 {
  background-color: #f7fafc;
}

.text-gray-200 {
  color: #edf2f7;
}

.bg-gray-200 {
  background-color: #edf2f7;
}

... and so on

Moving on to the next part we will look into creating margin , padding utility classes. We will be using lists data type in this case.

Let's start by creating a new partial _spacing.scss and we will create 2 lists, 1st one would be $spaces which will be used to calculate the spacing (For eg: m-4 -> margin: '1rem') and the 2nd one being $sides which is basically the direction of spacing to create margin-(top, right, left, bottom) variants.

// utils/_spacing.scss
$spaces: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
  20;

$sides: 'top', 'right', 'bottom', 'left';

Now that we have our values all we have to do is now loop over them with the help of the @each rule. To generate margin/padding-top,left,bottom,right utility classes we will nest another @each rule to map over $sides lists. We will also use str-slice which returns the slice of string.

// utils/_spacing.scss
@each $space in $spaces {
  .m-#{$space} {
    margin: #{$space/4}rem;
  }

  .mx-#{$space} {
    margin-left: #{$space/4}rem;
    margin-right: #{$space/4}rem;
  }

  .my-#{$space} {
    margin-top: #{$space/4}rem;
    margin-bottom: #{$space/4}rem;
  }

  .px-#{$space} {
    padding-left: #{$space/4}rem;
    padding-right: #{$space/4}rem;
  }

  .py-#{$space} {
    padding-top: #{$space/4}rem;
    padding-bottom: #{$space/4}rem;
  }

  .p-#{$space} {
    padding: #{$space/4}rem;
  }

  @each $side in $sides {
    .m#{str-slice($side, 0, 1)}-#{$space} {
      margin-#{$side}: #{$space/4}rem;
    }

    .p#{str-slice($side, 0, 1)}-#{$space} {
      padding-#{$side}: #{$space/4}rem;
    }
  }
}

Make sure to import the partials in our input file.

// src/index.scss
@import '../utils/colors' , '../utils/spacing';

Now if you build it you should see the spacing variants generated.

/* build/index.css */
.m-0 {
  margin: 0rem;
}

.p-0 {
  padding: 0rem;
}

.mx-0 {
  margin-left: 0rem;
  margin-right: 0rem;
}

.my-0 {
  margin-top: 0rem;
  margin-bottom: 0rem;
}

... and so on

Using the same approach you can create breakpoints, typography, literally any kind of utility classes you want! I hope you found this article informative I thought this was cool enough implementation to share with you folks.

You can refer the code in this Github repo.

Let me know what you think about it, feel free to connect with me on Twitter if you have any questions. Cheers!