T
css-in-js.webpall articles

Will React Server Components kill CSS-in-JS? Why We Switched from Styled Components to SCSS-Modules πŸ’€πŸ’…

image of the author - Łukasz Fiuk

Łukasz Fiuk

9 minutes read ⏳

You might wonder... Why? πŸ€”πŸ˜¨

Before we even begin, I'd like to explain why I'm even considering switching from Styled Components to scss-modules. Over the past few years, since I first used Styled Components, I fell in love with CSS-in-JS solutions. Why? Mainly because my markup has become much more readable, and I've been able to share variables between JavaScript and my CSS. On top of that, adding dynamic styles or conditional props has been a breeze. Reusing media queries or common variables just worked, and I could use TypeScript everywhere.

So why would I even consider switching from CSS-in-JS to a more traditional approach like scss-modules? Well, there are a few reasons why some people don't like Styled Components, with the three main points of critique being:

  • 🐌 Performance: Styled Components require a JavaScript runtime, and therefore could be slightly less performant than pure CSS.
  • πŸ™†πŸΎβ€β™‚οΈ Extra configuration / dependencies: Using Styled Components requires depending on external packages and extra configuration (not always) that doesn't work outside the React Ecosystem.
  • 🧠 Additional abstractions: Styled Components require getting used to a new syntax, and it takes some time to become familiar with it.

I was cool with all these cons, but then Next.js 13.4 launched with React Server Components and finally stable app directory. Those Server Components bring one huge issue - we cannot render Styled Components on the server, because they rely on web APIs and React Context. That’s painful, because even though we could still use Styled Components inside β€œclient” components, we cannot use power of RSC to its full potential as almost all of my components require some form of styling… Because of this, the Next.js team recommends using css-modules, and I'm going to stick to this recommendation.

Quick note - I know there are CSS-in-JS alternatives that could work, like Linaria, but after reading a long list of potential issues on GitHub, I decided to bite the bullet and finally go back to almost pure CSS. It's been quite a refreshing experience. 🌱

Configuration πŸ”§

Great, with that out of the way, let's focus on actually moving from Styled Components to scss-modules. I'm using Next.js 13.4 with an app directory, but the process should look the same if you are using older versions with pages, and it should be very similar even if you aren't using Next.js at all.

Previously, I mentioned that scss-modules doesn't require setup, but that's only partially true. You can use CSS modules in Next.js without any configuration, but I'd like to use the Sass SCSS syntax. For that, you'll need to actually install Sass as a dev dependency:

πŸ’Ύ >_ terminal
npm install --save-dev sass

OR if you are using yarn

πŸ’Ύ >_ terminal
yarn add sass --dev

Yay, you can now use scss-modules in your Next.js project! Just create files with a .module.scss extension, and import your classes into your component.

Here's a basic example:

πŸ’Ύ >_ txt
/1_components/
β”œβ”€β”€ Button/
β”‚   β”œβ”€β”€ Button.tsx
β”‚   └── Button.module.scss
πŸ’Ύ >_ tsx
// import all classes under styles ( or different ) namespace
import styles from "./Button.module.scss"; 

const Button = ({children}) => {
  return(
    {/* apply classes */}
    <button className={styles.button}>
      {children}
    </button>
  )
}

IntelliSense Support πŸ€–

To make our coding lives easier, let's get IntelliSense support, so it can give us with helpful hints, such as the available CSS classes we can use. To enable this functionality, we're going to install the typescript-plugin-css-modules package.

You can install it via the npm command:

πŸ’Ύ >_ terminal
npm install -D typescript-plugin-css-modules

Or if you're using Yarn:

πŸ’Ύ >_ terminal
yarn add -D typescript-plugin-css-modules

Once installed, add this plugin to your tsconfig.json:

πŸ’Ύ >_ ts
{
  "compilerOptions": {
    "plugins": [{ "name": "typescript-plugin-css-modules" }]
  }
}

You'll be rewarded with these lovely auto-complete suggestions for your CSS classes: Zrzut ekranu 2023-07-13 o 19.13.40.png

Global Styles and Theming 🌐🎨

Let's start with global styles. It's as easy as creating a stylesheet and importing it into your top-level component. For me, that's the RootLayout component.

πŸ’Ύ >_ tsx
import β€œstyles/globalStyles.scss”

export const RootLayout = ({children}) => {
  return(

    <body data-theme=”dark”>    {/* note data-theme attribute */}
      <main>
          {children}
      </main>
    </body>
  )
}

Theming? Piece of cake. 🍰 We'll use the data-theme attribute on our body tag. Our color palette and other constants will be stored in Sass variables, ready to be used anywhere in the project. Let's create a _palette.scss:

πŸ’Ύ >_ scss
/////////////////////////////////////
// Color Palette
// Names were generated by at chir.ag/projects/name-that-color/
/////////////////////////////////////

$white: #fff;
$gray: #878787;
$cod_gray: #111010;
$transparent: #00000000;

// Export the color palette to make it accessible to JS
:export {
    white: $white;
    gray: $gray;
    cod_gray: $cod_gray;
    transparent: $transparent;
}

And here's how to use these variables in our JavaScript files:

πŸ’Ύ >_ tsx
import variables from './_palette.scss';

Finally, let's use the power of data attributes to handle theming:

πŸ’Ύ >_ scss
// globalStyles.scss

body{
  [data-theme="light"] {
    --background: $white;
    --primary: $cod_gray;
    --secondary: $gray;
  }

  [data-theme="dark"] {
    --background: $cod_gray;
    --primary: $white;
    --secondary: $gray;
  }
}

And that's it! Your styles are now incredibly flexible, and it's easy to switch your design between different themes. 🎨

NOTE: I'm assigning colors to SCSS variables, so we can export our styles for usage in JavaScript. Then in the project I'm using pure css variables that will have responsive value based on current theme.

Extending Styles & Combining Classes πŸ‘ͺ

If you've used Styled Components before, you might recognize this pattern for extending styles of certain components:

πŸ’Ύ >_ tsx
export const CustomButton = styled(Button)`
    color: red;
`

Good news! You can achieve similar functionality with scss-modules. However, instead of styled components, you'll pass the className prop to your components. Here's an example:

πŸ’Ύ >_ tsx
import clsx from "clsx"

export const Button = ({className, children}) => {
  return (
    <button className={clsx(styles.button, className)}>
       {children}
    </button>
  )
}

You might be wondering about clsx - it's a tiny utility library that allows you to construct className strings in a more intuitive way. It helps in easily combining multiple classes.

To install clsx, use the following commands:

If you are using npm:

πŸ’Ύ >_ terminal
npm install clsx

Or yarn:

πŸ’Ύ >_ terminal
yarn add clsx

Now, extending and combining classes should be super easy, just pass a className πŸ€“

Reusable & Consistant MediaQueries πŸ“

In this section, we're defining breakpoints for our layout, and creating a reusable @mixin for media queries.

πŸ’Ύ >_ scss
// Define your breakpoints
$tablet: 640px;
$desktop: 1024px;
$desktop-large: 1280px;

// Define your media mixin
@mixin media($breakpoint) {
  @if $breakpoint == "tablet" {
    @media (min-width: $tablet) {
      @content;
    }
  } @else if $breakpoint == "desktop" {
    @media (min-width: $desktop) {
      @content;
    }
  } @else if $breakpoint == "desktopLarge" {
    @media (min-width: $desktop-large) {
      @content;
    }
  }
}

The @mixin directive lets you create reusable chunks of CSS. In this case, we've made a mixin called media that accepts a $breakpoint parameter.

Here's how you would use this mixin:

πŸ’Ύ >_ scss
.example {
  font-size: 1rem;

  @include media("desktop") {
    font-size: 1.5rem;
  }
}

Please note that you'll have to import your mixins, before using them in .scss files. You can use either @import or @use but that's basic Sass so I won't explain it further.

Wrapping Up πŸ“¦

At first, leaving Styled Components behind was tough. I was a big fan of CSS-in-JS. But a few weeks into using scss-modules, I've got to say - I'm having a pretty good time. My code has gotten simpler, and it's easier to read than I first thought it would be.

I hope you had fun reading this article. If you have any questions, don't hesitate to drop them in the comments section. Cheers and happy hecking!

0

Comments πŸ’¬

From Bricks to Views: Hierarchical Architecture 101 πŸ§±πŸ‘¨β€πŸ«