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.
Css-in-js or separated styles file?
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.
Building a box component with styles
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.
GreenBox.css (the styles file)
.box {
background: green;
}
GreenBox.jsx (the Javascript file)
import React from 'react';
import './GreenBox.css';
export function GreenBox() {
return <div className="box">
Green box
</div>
}
RedBox.css (the styles file)
.box {
background: red;
}
RedBox.jsx (the Javascript file)
import React from 'react';
import './RedBox.css';
export function RedBox() {
return <div className="box">
Red box
</div>
}
The problem
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.
If you wonder why the browser prioritized the green background over the red one, it happens because the order of CSS styles matters. You can read more in article Precedence in CSS published at css-tricks.
How to avoid that problem
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
Prepare your project to support css modules
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.
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.
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.
How to fix our boxes components using css modules
This section focuses on using css modules with the "Create React App" tool.
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.
GreenBox.module.css
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;
}
GreenBox.jsx
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>
}
RedBox.module.css
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;
}
RedBox.jsx
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>
}
Problem solved
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.
Tips when using CSS modules
Tip #1 - don't worry about awkward class names formatting
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
.
Tip #2 - pick simple names
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.
Tip #3 - css modules library is not tight to "Create React App"
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.
Tip #4 - css modules library supports nested class names
When using The .scss
file, you can continue using nested classes. The CRA tool is smart enough to handle nested classes.
GreenBox.module.css
.box {
background: green;
.text {
font-size: 30px;
}
}
GreenBox.jsx
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
.
Tip #5 - avoid using global class names with your own components
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>
)
Tip #6 - css module library lets you override global class names in css file
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;
}
}
Tip #7 - css module library lets you override global class names in scss file
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.
Final words
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
Top comments (2)
Interesting read, currently I've been using BEM but I'd be interested to experiment with this.
Does it cope with pseudo selectors, media queries and more advanced features like subsequent-sibling combinators?
Hi,
Thanks for reading. css module library has its limitations. Some things fit nicely into the css modules design, for example, tip #7 that lets you set
:global
selectors under you component selector. It also lets you handle nesting in.scss
by flatting their names when exporting them as a javascript object. But since it must flat the list of selectors, you "lose" some flexibility and some features.I recommend you to give css-in-js a try, I'm using it in large scale projects and can do everything you mentioned and even more because you can use javascript to perform calculations or manage a theme at runtime.
Thanks Again :)