loading...

Creating a common component library with CSS and SASS in React.

akirautio profile image Aki Rautio Updated on ・5 min read

There are more than enough options when deciding a toolset for building the common components in React. In this post, we are going to explore how to build common components with CSS and SASS and what kind of benefits and pitfalls there are.

From the all options, CSS files with SASS addition has the lowest learning curve because these tools are used very similarly to a normal web page development. CSS files are very simple to import to React component and SASS gives some nice optional additions like variables and calculations which ease up more complex work.

Setup

Setting up a CSS and SASS files to React project also very simple to do whether you are using Create React App or Webpack.

Create React App

If you are using Create React App you just need to install node-sass.

npm install node-sass --save-dev

Webpack

With webpack, the project needs a bit more configuration but setup is still very straight forward. Besides node-sass we need to install sass-loader, css-loader and style-loader.

npm install node-sass sass-loader css-loader style-loader --save-dev

And then add all these to webpack.config.js

{
  test: /\.s[ac]ss$/i,
  use: ['style-loader', 'css-loader', 'sass-loader'],
}

Parcel

Parcel handles both css and scss out of the box so you can start using them immediately without any configuration.

Importing css and scss files

Importing the scss / css files inside a component happens like any other import in javascript. For example:

import React from 'react'
import './style.scss'

const Box = () => (
   <div className="box">Box</div>
 )
export default Box

Once CSS file is imported, the content becomes available everywhere in the application (as long as it is imported). This means you don't necessary have to import the css file where you are using the classes. In a sense this gives a huge flexibility regarding how to handle the styles. In a most extreme way you could import the css in a root JS file but I wouldn't suggest to do that.

How to structure the common components with css

Since CSS has a global namespace, it's very likely that at some point in a project two classnames overlap or otherwise cause issues. To mitigate these this, it's advisable to use some naming convention to separate the component from each other ensure that all unique components have their own classnames. One of the most popular is Block, Element,Modifier (BEM).

The idea with naming convention is the use same way to determine the classnames so that they are easy to recognize and reuse. Instead of making the classes be applicable for one use case like:

.disabled_btn {
 border: 1px solid $color-border;
 border-radius: $space-s;
  background-color: $color-disabled;
  color: $color-light-text;

We would split the classes to be more generic and only add the necessary additions to more specific classes.

.btn {
  border: 1px solid $color-border;
  border-radius: $space-s;
  color: $color-dark-text;
}

.btn--disabled {
  background-color: $color-disabled;
  color: $color-light-text;
} 

How to use classnames properly

This structure is very extendable and easy to use in different situations. We can use the css straight away:


const CoolButton = ({ children, classNames, onClick }) => (
  <button className={classNames} onClick={onClick}>{children}<button>
)

or build a layer inside the component to handle classnames:

import classnames from 'classnames'

const Button = ({children, disabled, onClick}) => (
  <button 
    className={classnames('btn',{ 'btn--disabled': disabled })}
    onClick={onClick}
  >
   {children}
  <button>
)

Note that in the latter example we are using classnames which helps out handle multiple class names tremendously.
While both ways of using class names are correct, I would strongly recommend using the second one where class names are handled inside the common component and only properties are exposed outside.

Having properties handle the class name changes limits the amount of available different ways the component can be manipulated, which simplifies the testing and ensuring that the changes won't break the design.

Unfortunately, there is no way to extend the common component when only exposing the properties so if you need an extendable component, it could be done by creating a base component with className property and build the used component without className property.

For example if we extend the previous example the used component would look like:

const Button = ({children, disabled, onClick}) => (
  <CoolButton 
    classNames={classnames('btn',{ 'btn--disabled': disabled })}
    onClick={onClick}
  >
   {children}
  <button>
)

This way we get both extendability and limited amount of options.

Using variables from SCSS

Consistent design most often has a defined color palette and standardized spacing. With CSS and SCSS this can be done with variables added to be imported in the root javascript file (for example App.js)

The scss file could look like:


$background-Color: global #fff,
$primary1Color: global #408bbd,
$primary2Color: global #408bbd,
$secondary1Color: global none,
$secondary2Color: global #61b0e7,
...
$sSpacing: global '3px',
$mSpacing: global '6px',
$lSpacing: global '12px',
...

And to be used other scss files like:


.btn--primary {
   background-color: $primary1Color;
   padding: $sSpacing;
}

If the theme file starts to get bigger, there are also way to use SASS functions and mixins to help out keeping a better structure and easing the usages.

The benefit of using the global variables comes yet again from restrictions. When you limit yourself to use theme variables when defining colors or spacing, you also make sure that there is an easy overview what different option are used. This makes things yet again easier to test and ensure that everything works as they should.

Benefits and drawbacks of using CSS and SASS while creating the common components

As we have seen, CSS and SASS bring a powerful way to do common components/design without adding too much complexity. The component library would be easy to understand even by the developers who haven't done a lot with React and it most likely would be understandable for the people who mainly use only HTML + CSS.

The biggest advantage to use CSS and SASS is the convertibility. Since the styles are separated from the React component, the style can be reused between the frameworks. This gives a huge advantage if the same design is shared between an application that is not done only with React.

There are a couple of drawbacks too. Handling the class names manually creates a lot of possibilities to create an unmaintainable mess. The naming convention will help but this needs be managed constantly (or to have proper Eslint rules).

In my opinion, this toolset is still relevant and should be seen as an equal option when deciding what to use in a project.

Repository css-sass has an example how to use this in a project.

This is a second post from the series Speed up development by creating a common Component library. The later posts will cover up the other options to build the common component library and how to document the library.

Discussion

pic
Editor guide
Collapse
michaeloryl profile image
Michael Oryl

I don't see how this is creating a library of components. For example, if you use Create-React-App like you suggest, the 'yarn build' command makes a deployable bundle of the entire application, not something you could deploy to the NPM registry and pull in and use in another application (such as one itself built with CRA).

Am I missing something, or perhaps not taking "library" to mean the same thing that you are?

I've got existing applications that share copies of the same components. I want to package these common components up as something that each application could list as a dependency in package.json instead of doing a copy/paste. I'm using SCSS in the way you show here (importing), but I just don't see any means to actually make a library of components from what you have here.

Any help would be appreciated. Thanks.

Collapse
akirautio profile image
Aki Rautio Author

Great question! :) You are absolutely right that building the package with yarn build will result an application instead of package. The focus in here is more to show power of different tools when developing the common components. Creating a package would be probably a good topic for an article but here couple of main points:

The package is just one directory inside node_modules which can be imported from the application the way you please. This means that in a simplest form you don't have to do any builds because that's a job for the application which consumes the package. Usually some preparation is still done so that imports don't look like following and correct packages are also installed when consuming the package.

import Button from 'library/src/components/Button/Button'

Now this is where the personal preferences comes to play since there are a lot of tools and ways to achieve the same results. If you are looking any tools to build cjs, esm or umd files for a package I would suggest to try rollup.js. Note that SASS won't be needed to build at this point because that's the job for the application who consumes the common library.

For configuring the package.json file, I would suggest to look this Creating your first npm package .

Hopefully these helped :)