Using Tailwind + CSS Modules + SASS and Stylelint with Next.js

When I first built this site I chose SCSS along with CSS Modules to style it because I was familiar with this approach. That was pretty easy to do with help of the respective sections in Next.js documentation.

After having a working version of the site I revisited the idea to finally play a little with Tailwind CSS and see if it could fit with in my workflow and styles architecture. I set the following requirements that I wanted the integration had to satisfy:

  • It should allow to define base styles for the whole site.
  • It should be possible to keep styles separated in SCSS partial files.
  • It shoulld allow to define styles using Tailwind in CSS Modules.
  • Allow me to take advantage of Tailwind CSS and its flexibility.
  • Allow nesting styles with SCSS

After a couple hours trying different approaches and configurations I finally was able to come up with a working solution that satisfies my needs.

Let's jump into the solution and implementation!

1. Configuration

1.1. Pre-requisites

I previously had SCSS and CSS Modules working on my site. Follow the instructions in the docs and you should be ready.

1.2. Add Tailwind CSS to the Project

For this I simply followed the Tailwind CSS example in the Next.js repository.

1.3. Configure Tailwind

Nothing special to do here. I just modified tailwind.config.js to adjust the paths to my components and pages folders for the purge directive as I have my source code under the ./src. I also extended the colors and fonts configurations.

module.exports = {
  purge: [
    './src/components/**/*.{js,ts,jsx,tsx}',
    './src/pages/**/*.{js,ts,jsx,tsx}'
  ],
  theme: {
    extend: {
      colors: {
        black: '#000000',
        cornflower: {
          default: '#8ec6eb',
          medium: '#b1d7f1',
          light: '#e5f2fa'
        },
        'steel-blue': {
          default: '#4b89b3',
          dark: '#162a39'
        },
        white: {
          default: '#ffffff',
          'transparent-10': 'rgba(255 255 255 / 10%)'
        }
      },
      fontFamily: {
        primary: ['"Noto Serif"', 'serif'],
        secondary: ['"Quattrocento Sans"', 'sans-serif']
      }
    }
  },
  variants: {},
  plugins: []
};

2. Using Tailwind in Base Styles

2.1. Creating the Main SCSS file for Styles.

I Added a src/styles/main.scss containing the imports of SCSS partials files. As you can see in the following example it's not really different than what you do in the normal implementation:

@tailwind base;

/* Write your own custom base styles here */
@import './_base.scss';
@import './_typography.scss';

/* Start purging... */
@tailwind components;
/* Stop purging. */

/* Write you own custom component styles here */

/* Start purging... */
@tailwind utilities;
/* Stop purging. */

/* Your own custom utilities */

The only difference here is that we use imports instead of declaring styles right in the file. This is something I didn't like about Tailwind but this seemed a good workaround for me.

2.2. Declaring Base Styles Using Tailwind

Let's take a look on how to work with Tailwind in our SCSS files.

// file: ./src/styles/_typography.scss

// Importing external CSS code, fonts, etc.
@import url('https://fonts.googleapis.com/css2?family=Quattrocento+Sans:ital,wght@0,400;0,700;1,400&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif:ital,wght@0,400;0,700;1,400&display=swap');

// Using Tailwind with `@apply` in rules
h1,
h2,
h3 {
  @apply my-4
    font-secondary
    text-cornflower
    font-normal;
}

h1 {
  @apply text-3xl;
  // You can use normal CSS properties too for properties Tailwind doesn't handle:
  background-color: rgba(0, 0, 0, 0.35);

  @screen lg {
    // Important: note that you can't use `lg:text-4xl` as you would
    // normally do when working with classes in HTML. For this you
    // must use the `@screen` directive and the respective breakpoint.
    @apply text-4xl;
  }
}

a {
  @apply text-cornflower
    underline;

  &:hover {
    // Important: similar to what we did for media queries, we must
    // declare styles for pseudo-selectors using its own rule.
    // Using `hover:text-white` will throw an error when styles compile.
    @apply text-white;
  }
}

2.3. Import Styles in ./pages/_app.js

I simply added the SCSS file import:

import Head from 'next/head';

import '../styles/main.scss';

// This default export is required in a new `pages/_app.js` file.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

That's it! You should have your base styles implementation working wonderfully at this point! Important: this requires to restart the Next.js service.

3. Using Tailwind in CSS Modules

To add styles using Tailwind in our CSS Modules for components is pretty much the same we did for our base styles.

3.1. Import the styles module file in our component

// file: `./src/components/Header/Header.jsx`

import styles from './Header.module.scss';

const Header = () => {
  return <header className={styles.wrapper}>{/* component code... */}</header>;
};

export default Header;

3.2. Add module styles

I used @apply, nesting and Media Queries the same way I did for base styles:

.wrapper {
  @apply py-4
    border-b
    border-solid
    border-white-transparent-10;

  @screen lg {
    @apply border-0
      pt-10
      pb-8;
  }
}

Done! Now your component is styled with Tailwind CSS! 🙌

4. Bonus: Configure Stylelint to Format and Order Properties automatically

Note: this is intended for VSCode users but can be easily done in npm scripts or translated to other code editors.

4.1. Install Dependencies

yarn add --dev stylelint stylelint-config-recommended-scss stylelint-order stylelint-scss

4.2. Add Stylelint Configuration

/* file: ./.stylelintrc.json (remove this comment, no valid JSON) */

{
  "extends": "stylelint-config-recommended-scss",
  "plugins": ["stylelint-order", "stylelint-scss"],
  "rules": {
    "order/properties-alphabetical-order": true,
    "scss/at-rule-no-unknown": null,
    "scss/at-import-no-partial-leading-underscore": null
  }
}

4.3. Install VSCode extension and configure it for your project folder

// file: ./.vscode/settings.json

{
  "editor.formatOnSave": true,
  // Run Stylelint fix when you save the file
  "editor.codeActionsOnSave": {
    "source.fixAll.stylelint": true
  },
  // Recommended config for the extension
  "css.validate": false,
  "less.validate": false,
  "scss.validate": false
}

You're ready. Now when you save your .scss file you should see Stylelint applying the fixes and sorting properties:

Stylelint in action

Well. It's not very hard to get it to work once you've put all the pieces together and connected correctly. Don't you think?

I hope these notes can be helpful to anyone who may be having a hard time implementing something similar. I would love to see your comments, corrections or questions as well as alternative approaches you could have.

Published on 8/17/2020 by Sebastián Veggiani. This note is about: styles, css, tailwind-css, css-modules, stylelint, next-js

Found this useful? Please consider supporting me...
Buying me a Cafecito!