Avoiding CSS order of appearance problems with CSS Modules
Tags: css, javascript, webdev, architecture
Published: 2024-04-17 11:42:17

When I started web development, I wrote pure CSS, then I moved on to BEM (Block, Element, Modifier) methodology and LESS/SASS, and eventually adopted PostCSS and CSS Modules when I started using modern JavaScript frameworks and bundlers.

For me, CSS Modules primarily solved the inconvenience of managing unwieldy long class names to avoid global namespace pollution and accidental style overrides. And despite all the new CSS libraries popping up lately, I still love the simplicity of using CSS modules.

In this article I want to shed light on one challenge I've encountered when using CSS Modules:

## Position and order of appearance

In CSS (Cascading Style Sheets) the order in which rules appear affects the final styling of elements. Later-defined CSS rules have higher precedence and can override earlier ones. This is one of four stages in [the cascade algorithm](https://web.dev/learn/css/the-cascade), the others are specificity, origin, and importance.

Sometimes, the order of CSS rules can create confusing scenarios. Imagine you have a `div` and apply multiple classes that set `color` like this:

```html
<div class="red blue">Hello World</div>
```

What color will "Hello World" have? To be able to answer this question with certainty, you need to know the order in which the classes are defined.

In this case, the obvious solution is to use only one class:

```html
<div class="red">Hello World</div>
```

However, the reality looks more like this:

```html
<button class="my-fancy-button blue">Send Hello</button>
```

`.my-fancy-button` is a class that defines the appearance of a fancy button, and `.blue` is a utility class to set the color blue in this context. For this to work, you will have to to make sure that `.blue` is defined later.

## Bundlers
This next example illustrates an order of appearance problem you may encounter when using CSS Modules in any bundler setup.

Let's say, you create a React component and style it using a custom class. In addition the component is designed to accept a `className` prop that can be used to pass an additional class from outside.

```jsx
import { button } from "styles.module.css";

function Button(props) {
  return (
    <button className={concatClassNames(button, props.className)}>
      {props.label}
    </button>
  );
}
```

Subsequently, you use this component, utilizing a helper class to override specific styles.

```jsx
import { Button } from "components/Button";
import { blue } from "../../helpers.module.css";

function BlueButton() {
  return (
    <div>
      <Button className={blue} label="Click Me" />
    </div>
  );
}
```
This will work like expected because bundlers typically sequence generated/imported CSS based on the first occurrence. You can think about it like a global style sheet; whenever CSS is imported, it's appended to a list of style rules.

`blue` is imported after `Button`, so it is added later and overwrites the color defined by the local class `button` imported inside `Button`

The import order is crucial here; if you would import `blue` before `Button`, the utility class wouldn't be able to override the color anymore:

```jsx
// If the utility class is imported earlier, it doesn't 
// have the ability to override styles from Button.
import { blue } from "../../helpers.module.css";
import { Button } from "components/Button";
```

Ok, so we just need to make sure to always import such utility classes last, right? 

In general this is true, but it might be more difficult than you expect, hang on.

### The hidden first occurrence

Let’s assume you import `BlueButton` and also the same helper class `blue` in another file:

```jsx
import { BlueButton } from "components/BlueButton";
import { MyHeading } from "components/MyHeading";
…
// The utility class is imported last (in this file)
import { blue } from "../../helpers.module.css";

function MyCoolInterface(props) {
  return (
    <div>
      <MyHeading className={blue}>
        Click the blue button:
      </MyHeading>
      <BlueButton />
      …
    </div>
  );
}
```

Despite `blue` being imported last _in this file_, it's first occurrence is inside `BlueButton`. This means that the class `blue` will be added ahead of any CSS imported after `BlueButton`. **In this example the class `blue` won't have precedence to override styles defined in `MyHeading`.**

While in this particular case you could theoretically move the `MyHeading` import to the top, there are situations where reordering imports merely transfers the problem to another location.

### Avoiding hidden first occurrences

An effective solution to avoid this problem would be a steadfast rule: **always import CSS in an application only once, and never in multiple locations.** 

By following this rule, you ensure that each import is the first occurrence, and you can stay confident about the sequence. For each file where this rule is not followed, expect that the rules might spontaneously receive lower precedence than they currently have in your context.

To use classes in multiple locations you can still import the styles once at a higher level, such as your main file or an `App` component. Then share the classes by passing them down to children. 

In React, you could do this via the Context API:

```jsx
import App from "./App"
import * as helperStyles from "helpers.module.css"

<MyContext.Provider value={{ helperStyles }}>
  <App />
</MyContext.Provider>

```

But you could still run into order of appearance issues.

Imagine you create a component `MyHeading` that uses a class provided by `MyContext` and in addition accepts a `className` from outside:

```jsx
function MyHeading({children, className}) {
  const { helperStyles } = useContext(MyContext);
  return (
    <h1
      // This can get you into trouble
      className={concatClassNames(helperStyles.green, className)}
    >{props.children}</h1>
  )
}

```

The file `helpers.module.css` defines the two utility classes `blue` and `green`:

```css
.blue {
  color: blue;
}

.green {
  color: green;
}
```

When you subsequently use `MyHeading` and pass it the `blue` class obtained from `MyContext` like `<MyHeading className={blue} />`, the `blue` class and the `red` class would both be applied. And `blue` won't have sufficient precedence to override the color because it is defined first in `helpers.module.css`.

I do like the pattern of concatenating class names across multiple levels in the component tree. But to keep this pattern intuitive you need to make sure that the precedence of rules resembles the hierarchy in your components.

You can accomplish this by adhering to the aforementioned rule **and** avoiding the combination of shared class names at multiple levels.

## Conclusion

While CSS Modules and similar tools offer elegant solutions to avoid namespace conflicts, managing the precedence of style rules is still relevant.

Thank you for reading! Let me know if you found it worthwhile reading and I hope this article has piqued your curiosity. Feel free to share your thoughts and experiences on this topic in the comments below.