January 17, 2020

Context Based Styles With CSS Modules

When I first began using CSS modules, something that tripped me up was where to put styles for child components. At the time, the most intuitive place was within the parent component. Having worked a lot with Sass over the past few years, I’ve become accustomed to nesting styles almost like you’d nest components.

For example:

    // sidebar.js
    
    import React from "react"
    import Button from "../library/button"
    import style from "./sidebar.module.css"
    
    export default const Bio = () => {
    	return (
    		<div className={style.bio}>
    			<p>Some interesting stuff about me...</p>
    			<Button className={style.bioButton}>Follow Me!</Button>
    		</div>
    	)
    }
    // sidebar.module.scss
    
    .bio{
    	// some styles
    	.bioButton{
    		width: 100%;
    	}
    } 

While this is a perfectly valid approach, there are a few issues. Some of these issues exist even when not using CSS modules (although much more apparent when you are). A better (IMO) approach would be something like this:

Note that these are different files than in the last example

    // button.js
    
    import React from "react"
    import style from "./button.module.css"
    
    export default const Button = ({children}) => {
    	return (
    		<button className={style.button}>{children}</button>
    	)
    }```
    /* button.module.css */
    
    .button{
    	background-color: aqua;
    	width: auto;
    	height: 45px
    }
    
    /* Good */
    :global(.bio) .button{
    	width: 100%;
    }
    
    /* Even Better */
    :global(.sidebar) .button,
    :global(.card) .button{
    	width: 100%;
    }

You’re probably wondering this :global selector is about. This is a feature of CSS modules that allows us to switch to the global scope. So in the case of :global(.bio) selector, the code will look for the .bio class anywhere in the global scope and when the CSS is compiled, .bio will not be hashed along with .button. After the compilation, the CSS will look something like this:

    .bio ._style__button_425452028{
    }
    
    /* button.module.css */
    
    ._style__button_425452028{
    	background-color: aqua;
    	width: auto;
    	height: 45px
    }
    
    /* Good */
    .bio ._style__button_425452028{
    	width: 100%;
    }
    
    /* Even Better */
    .sidebar ._style__button_425452028,
    .card ._style__button_425452028{
    	width: 100%;
    }

Soooo, why is this better? Let’s get into it…

It’s more modular / portable

In the first example in this post, the Button styles are nested inside of the styles for the Bio component. This means that Button has an implicit dependency on Bio. If we attempt to use Button in another context expecting the button to look the same, we will be sorely disappointed. Not only that, we will need to repeat this style everywhere that we want a Button at 100% width.

In the next example, the styles for Button are completely self contained within its CSS module. If a 100% width Button is only ever used inside of a .bio then the “Good’ example will suffice. But lets say that isn’t the case. Let’s say what we really want is for every Button in the sidebar (which happens to be where the Bio is used) to be 100% the width of its parent. Not only that, we also want Buttons in Card components to be 100% width. Who knows, by the time this site is finished there could be myriad contexts where we want a 100% width button. If we had Button styles sprinkled throughout other modules, we’d end up with a lot of duplicate code and the Button itself would likely not work as we’d expect if it were used in another project or in a new context.

It makes it easier to reason about your code

You’ve probably heard at one point or another about how, as developers, we spend most of our time reading code, not writing it. Code written without care for how it’s architected makes this fact even more apparent. Modular code is great but it requires us to be a bit more deliberate in how we organize things. A good rule of thumb for CSS modules (and how they are intended to be used) is for each file to correspond to a single component and nothing more (not even child components). This way, you can be absolutely sure of where to look when debugging a component. No need to traverse multiple files or rely on the web inspector to hunt down a style.

Getting into CSS in JS

I’ve heard CSS modules referred to as the gateway drug to CSS in JS and I agree with that but we’ll save discuss that more in another post.

Resources

https://github.com/webpack-contrib/css-loader#scope

https://css-tricks.com/css-modules-part-1-need/


I'm a front-end designer and developer based in Columbus, Ohio.