Using styled-components alongside css-modules

There’s definitely a lot going on when you think about how to introduce CSS into your ReactJS project.

Should you use css-modules — keep all your CSS outside of your component and load it on demand with css-loader, or maybe just include it via <link> in your HTML file. Or maybe use one of many implementations of css-in-js which might feel better for developers but make designers and people moving from other platforms eye twitch a bit?

css-modules — tried and tested

For a long time I’ve been a proponent of the first one. My Component.js would have a Component.css file living alongside it, and my code looked like this:

import Styles from "./Component.less";
class Button extends React.Component {
render() {
return <span className={Styles.btn}>{this.props.children</span>
}
}

which would allow me to keep my styles “kosher”. Adding to that decision was the fact that I often worked with designers that were skilled in HTML+CSS and would either give me the coded layout or knew just enough ReactJS to get by and provided very basic components.

When a component needed some state the usual story is to provide it by props, and maybe use some helper like classnames package:

import Styles from "./Component.less";
import classnames from "classnames";
import PropTypes from "prop-types";
class Button extends React.Component {
render() {
const classes = classNames({btn, danger: this.props.danger});
return <span className={classes}>{this.props.children</span>
}
}
Button.propTypes = {
danger: PropTypes.boolean
}

This is very “React” way — you have a set of props that dictate how the component should behave / look. Things get a little more complicated when you need to actually change some little things *just this once*. We would end up with creating custom local styles like:

.MyComponent span[class*="btn"] {
color: #444;
}

just before we’re using css-modules which result in (semi)random selectors (so our .btn changes to .btn__btn_ac327af or something similar depending on how and where we used it).

css-in-js — the sign of future to come?

Like I said, I was not very found of css-in-js solution because I’m used to writing styles as separate entities for the past 15 years. Just to get everyone on the same page, here’s an example using styled-components:

import styled from "styled-components";
const fontSize = '14px';
const Button = styled.span`
display: inline-block;
padding: 5px;
background: blue;
border-radius: 4px;
color: white;
cursor: pointer;
font-size: ${fontSize};
  &:hover {
background: black
}
`
const Item = () => <Button>Click me!</Button>

It looks even worse than “putting my HTML in my .js file” at first sight but there are some wins in it. You can use normal HTML attributes, you can mix in JS if you want to, it supports states, nesting and media queries. And no, it will not all end up in style="" but create a “real” class= attribute and pull out the CSS into head, just like css-modules.

It even allows you to style custom components (not only HTML ones) if they only accept className as prop, so:

const Button = (props) => <button className={props.className}>{props.children}</button>
const CustomButton = styled(Button)`
display: inline-block;
padding: 5px;
`
const Item = () => <CustomButton>Click me!</CustomButton>

This solution is nice, but has its own downsides — just like any other technology. That and I no longer could easily accept code from our designers and sharing code between multiple projects could become harder.

And then I had a (probably not very original idea …)

Warned you.

Best of both worlds

Since I’m not ready to let go of .css files, but at the same time like how styled-components make it easy to change the look of 3rd party components easily I figured I’ll try to combine both and ended up with a solution that I’m happy with.

Let’s start by making a normal component:

import Styles from "./Button.css";
import classnames from "classnames";
export default (props) => {
const className = classnames([
Styles.button,
props.className
]);

return (
<button className={className}>
<span className={'icon'}>?</span>
{" "}
{props.children}
</button>
);
}

now we import it to our app like always, but use styled-components to give it unique look-and-feel when needed:

import Button from "./Button/Button";
const DangerButton = styled(Button)`
background: red;
font-weight: bolder;
cursor: not-allowed;
span.icon {
color: red;
background: black;
}
`;
const ButtonWithOtherIcon = styled(Button)`
span.icon {
background: black;
color: white;
}
}
`

Now, when we run our example we can see 3 different buttons, 2 of which are custom ones created with much less hassle:

You can check the example at https://codesandbox.io/s/jRpG4q9VP

Conclusion

I really like this approach — just like before, we are able to overwrite the styles from the original component, but we no longer have to create a separate .css file just to change one or two things. Additionally we don’t need to use any “tricks” to target the element by its random class name.

The extra css — generated by styled-components — is cleanly inserted into the head after the base css for the component (so we’re not running into selector priority errors).