← subterrane

You Might Not Need styled-components

May 26, 2021 · 2 min read

styled-components can be quite nice for whipping up components but for old farts like me that are more familiar with old-timey CSS, I prefer to use CSS Modules.

When using CSS Modules, we quickly find out that while somethings are simpler (media queries) some things are not so easy (how the $@^% do I use a theme?).

Theming

styled-components has a ThemeProvider which is nice. Grab some JSON from a server somewhere and pass it to the ThemeProvider and you can use it in your styles.

We can do that, too. First, we make a Theme Context component. The entire thing is 4 lines, one line is blank and we probably don't even need the first line, anyway...

import { createContext } from "react";

export const ThemeContext = createContext();
export const ThemeProvider = ThemeContext.Provider;

To use it, we import the ThemeProvider, then wrap it around some JSX that we need to style:

import { ThemeProvider } from "./ThemeContext";

<ThemeProvider value={theme}>
  <Button>Press</Button>
</ThemeProvider>

Here is a simplified example of a Button component using the theme:

import React, { useContext } from "react";
import styles from "./Button.module.css";
import { ThemeContext } from "./ThemeContext";

export const Button = (props) => {
  const theme = useContext(ThemeContext);

  const style = {
    "--bg-color": theme.backgroundColor,
    "--fg-color": theme.foregroundColor,
    ...props.style
  };

  return (
    <button
      className={styles.Button}
      style={style}
    />
  );
};

The values we need in the theme are passed to the CSS through the style attribute. The CSS module can use the CSS variables like so:

.Button {
  color: var(--fg-color);
  background-color: var(--bg-color);
}

Adapting Based On Props

Sometimes you want to change the styles on a component based on some props. This becomes quite easy with CSS.

<Button size="large">
  Primary Large
</Button>

CSS

.Button {
  font-size: 14px;
}

.Button[data-size=large] {
  font-size: 16px;
}

Window.matchMedia

The last trick is rather rare, but I ran into an issue where a breakpoint was not known in advance and I had to use logic in javascript to determine if a class should be added to a component or not.

Window.matchMedia allowed me to solve this problem.

This allowed my component to accept a breakpoint prop and add a CSS class if the window was sized smaller than the breakpoint.

const maxWidth = width =>
  window.matchMedia(`(max-width: ${width}px)`).matches;

const getItemClasses = breakpoint => {
  const names = [styles.Item];
  if (maxWidth(breakpoint)) names.push(styles.ItemSmall);
  return names.join(" ");
};

return (
  <div className={getItemClasses(breakpoint)}>{children}</div>
);

Conclusion

These tricks covered the features of styled-components that I used but allowed me to use CSS Modules instead. I hope you find them useful.