over 4 years ago
{React is a library for building front-end applications by creating components and composing them into a complex user interface. A component is a Javascript function/class, usually written in .jsx files, that manages its state, DOM structure, behaviors, and user interactivity. The component style is defined either as a separate style (.css/.scss) file or directly in the Javascript file using a concept named css-in-js.
When using a separate style file, by default, all styles are global, as demonstrated later in this post, and you need to work harder to prevent collisions between components styles. This post suggests ways to encapsulate components styles and making them unique per component easily.
Writing styles in a separate file is trivial as you write standard css definitions. Usually, you create a .css/.scss file with the same name of the component and then import it in the component Javascript file, making it part of the deployed package. Alternatively, you can use a library that follows the css-in-js principles and directly writes the styles inside the Javascript file using JSON objects. CSS-in-js is not a particular library; it represents a group of libraries following the same ideas.
Both ways are popular; each has its benefits and drawbacks. When using either, you write the styles next to the component definition, so probably expect that the component styles will affect only that component. Effectively at runtime, all the components' styles are added to the page head together, so there is no separation between components styles, and collision is inevitable.
CSS-in-js prevents collisions by generating unique selectors for each component. With separated .css/.scss file, it is the responsibility of the library you choose to prevent the collision, if anything.
Tip: When switching between css definition to JSON representation, you can assist with an online converter like css to React.
The screenshot below shows two boxes, both with green background, even though the first one should have a red background.
Here is the code that leads to that unfortunate outcome.
.box { background: green; }
import React from 'react';
import './GreenBox.css';
export function GreenBox() {
return <div className="box">
Green box
</div>
}
.box { background: red; }
import React from 'react';
import './RedBox.css';
export function RedBox() {
return <div className="box">
Red box
</div>
}
We defined two different components; each has its styles. Both have a css selector named .box
with a different background color. When compiling the code, the bundler, a tool that generates the deployable files, groups all the styles together and, at runtime, inject them to the page header. Effectively at runtime, they are not bound to the original component, and other rules apply to determine which style to show when. In our case, the computed style of .box
will show green background for both components. A computed style is a set of all the values of all the css properties relevant to the element after resolving all the duplication and performing the computation of the relevant properties.
There are popular methodologies, like BEM, ABEM, Atomic, SMACSS, that provide conventions to prevent name collisions in the browser.
Using conventions is excellent in general, but with modern libraries, there are some great alternatives, and you will probably prefer enforcing it at the API level to automate the process and leveraging authoring tools for component-based applications.
To automate it instead of counting on conventions, We will use css module css modules, a library that will make sure each component is using unique selectors at runtime
CSS modules can be used in any Javascript project regardless of the devop tools you used to setup your project. But with "Create React App", a powerful toolchain to build React applications, it is even easier as "Create React App" comes with css modules already activated and ready to be used.
I must admit that it is not a straightforward process to set up css modules manually; I'm not sure why but the css modules documentation doesn't provide an easy guide to follow. I suggest you check if the tool you are using already supports css modules. Consider other alternatives or google for a guide that shows you how to tweak your bundler configuration.
If you are using "Create React App", go ahead to the next section, otherwise you can check out the CSS modules examples section in their documentation site.
To use css modules with any component in your project, you need to rename the style file by adding .module
before the file extension. Refactoring the example from before will result in the desired outcome.
Change the file name, add .module
to the name. In the file content, you can use any css class name you want.
.box { background: green; }
Import the .module.css
file with default module classes
. Then, use classes
when setting the child className
property.
import React from 'react';
import classes from './GreenBox.module.css'; // <--- 1. import classes from the module css file
export function GreenBox() {
return <div className={classes.box}> {/* <--- 2. use classes file extension */}
Green box
</div>
}
Change also this file name, add .module
to the name. In the file content, you can use any css class name you want.
.box { background: red; }
Import the .module.css
file with default module classes
. Then, use classes
when setting the child className
property.
import React from 'react';
import classes from './RedBox.module.css'; // <--- 1. import classes from the module css file
export function RedBox() {
return <div className={classes.box}> {/* <--- 2. use classes file extension */}
Red box
</div>
}
Your components are now using css modules, which guarantee that you will never have name collision between components again. The image below shows the class names at runtime with and without CSS modules.
In your javascript files, all class names are converted to lower case to keep the Javascript convention for constants. For example, class name .home-view
is converted to homeView
and can be used as classes.homeView
.
You are encouraged to use simple class names, at runtime each name will become unique but will still include the original name to simplify code debugging.
As mentioned earlier, css modules are not part of the "Create React App" tool and can be used by any project regardless of the tools you are using. If you set up CSS modules on your own, make sure you enable important css modules plugins like global preprocessor.
When using The .scss
file, you can continue using nested classes. The CRA tool is smart enough to handle nested classes.
.box { background: green; .text { font-size: 30px; } }
import React from 'react';
import classes from './GreenBox.module.css';
export function GreenBox() {
return <div className={classes.box}>
<div className={classes.text}>Green box </div>
</div>
}
In the example above, You can nested class names directly from classes
like classes.text
.
If you have a module that is using a class name from a global CSS file, it might indicate that you are missing a component.
Imagine you have a black box class name that blocks interactions with elements below it. Instead of just using a global class .black-box
and calling it from multiple components; you should have a component named BlackBox
with a matching CSS module. This way, you keep your code more straightforward and easier to maintain.
Below is a partial example demonstrating the Box
component using the global class name black-box
that is defined in a global CSS file.
return ( <div className={classes.box}> <div>Some important content</div> { isBlocked && <div className="black-box" /> } </div> )
Below is an example of a BlackBox
component that will handle this feature.
return ( <div className={classes.mapView}> <div>Some important content</div> <BlackBox visible={isBlocked} /> </div> )
You can declare global selectors both in .css files and .scss files. Using global selectors is useful if you want to override styles of third party components.
In your css file, write the following to declare a global selector that will not be handled by the CSS module compiler.
:global { .global-class-name { color: green; } }
Setting global class names in scss files is even more robust as you can limit a global selector to affect only nested component children.
.box { :global { .global-class-name { color: green; } } }
To read more about global selectors, see their documentation.
In this post, I demonstrated a way to avoid style collision between components when using the "Create React App", with minimal effort by leveraging the power of css modules.
I have used it in many React projects that didn't require advanced styling support. Still, I recommend trying out css-in-jss next time you start a new project and play with it before settling with css styles file. It might surprise you.
Photo by Amanda Vick on Unsplash