Writing your first React UI Library - Part 3: CSS Modules
Tags: css, react, javascript
Published: 2020-06-16 20:53:42

![css and html](https://res.cloudinary.com/carlosdev/image/upload/v1591933240/react-lerna-css/css-modules_kpxi0w.png)

This is the third post on a series on how to make your own UI React Library.

## What are we going to do?

- Add support for CSS Modules compilation to our builder.
- Add support for CSS Modules in Storybook.
- Enhance the styles of our UI Components similar to what you would do on a Design System.

### CSS Modules

CSS Modules are great because it lets you consume css in your components that can be locally scoped with auto-generated classes, this is great to prevent collision between classes.

Let's start adding `rollup-plugin-postcss`

```sh
lerna add rollup-plugin-postcss --scope=@cddev/phoenix-builder
```

Now it's just a matter of importing the plugin and using it on the input configuration with `modules: true` option.

`phoenix-builder/lib/phoenix-builder.js`

```js
// At the top after other imports
const postcss = require('rollup-plugin-postcss');

// Adding a new plugin with `modules: true`
const inputOptions = {
  // ... other options
  plugins: [
    postcss({
      // Key configuration
      modules: true,
    }),
    // ... after other options
  ],
};
```
Let's add some css in our `phoenix-button` to test this feature.

First create a `styles.css` next to the `phoenix-button.js`

`phoenix-button/lib/styles.css`
```css
.Button {
  background-color: red;
}
```

After this you should be able to import it in your the button and use it

`phoenix-button/lib/phoenix-button.js`
```js
import React from 'react';
import styles from './styles.css';
const Button = ({ children }) => <button className={styles.Button}>{children}</button>;
export { Button };
```

You can see above that in order to use the css modules you import the styles and then access the class like `styles.Button` as if the class became a property of `styles` object.

Running `npm run build` should compile the component as before but adding some new code to inject the css.
![injected styles](https://res.cloudinary.com/carlosdev/image/upload/v1591934009/react-lerna-css/style-inject_jlg84m.png)

### Add support for CSS Modules in storybook

We can't proceed without looking at what we are doing in terms of styles and by just importing css on the components and then on storybook it won't work, so we need to add support for css-modules on storybook.

Luckily we have almost everything setup so we just need to do a small override on the storybook webpack configuration in

`.storybook/main.js`:

```js
module.exports = {
  stories: ['../packages/**/*.stories.js'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links'],
  webpackFinal: async (config) => {
    // remove default css rule from storybook
    config.module.rules = config.module.rules.filter((f) => f.test.toString() !== '/\\.css$/');

    // push our custom easy one
    config.module.rules.push({
      test: /\.css$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            // Key config
            modules: true,
          },
        },
      ],
    });

    // Return the altered config
    return config;
  },
};
```

Voila!

Now you can `npm run storybook` and you should see your first react component using CSS Modules

![red button with css modules](https://res.cloudinary.com/carlosdev/image/upload/v1590895000/react-lerna-css/story-css-module_de4b6k.png)

### Enhancing the Button Component

This guide won't be almost finished without adding some fanciness; In this case, we're going to borrow some styles and Design System from https://www.coinbase.com/, because why not.

From their homepage we can see they have mainly two buttons: one green and one with a white outline, let's create the css for those.

`phoenix-button/lib/styles.css`

```css
.Button {
  background-color: #05b169;
  border-radius: 0.25rem;
  border: 1px solid #05b169;
  color: #fff;
  cursor: pointer;
  font-size: 1rem;
  padding: 0.75rem 1rem;
  transition: all 100ms ease-in-out;
  width: auto;
  outline: none;
}

.Button:hover,
.Button:focus {
  background-color: #00a55e;
  border-color: #00a55e;
}

.ButtonSecondary {
  background: transparent;
  border-color: #fff;
}

.ButtonSecondary:hover,
.ButtonSecondary:focus {
  background: #fff;
  border-color: #fff;
  color: #000;
}
```

Now for the code in the button:

`phoenix-button/lib/phoenix-button.js`

```jsx
import React from 'react';
import cx from 'clsx';
import styles from './styles.css';

const Button = ({ children, className, variant, ...rest }) => {
  const classes = cx(
    styles.Button,
    {
      [styles.ButtonSecondary]: variant === 'secondary',
    },
    className
  );
  return (
    <button {...rest} className={classes}>
      {children}
    </button>
  );
};

export { Button };
```

And then enhancing our stories like this:

`phoenix-button/docs/phoenix-button.stories.js`

```jsx
import React from 'react';
import { Button } from '../lib/phoenix-button';

export default { title: 'Button' };

export const primary = () => <Button>Hello Button</Button>;

export const secondary = () => (
  <div style={{ background: '#1652f0', padding: 12 }}>
    <Button variant="secondary">Hello Button</Button>
  </div>
);
```

Now you should be able to see some variants of your fancy button
![green button](https://res.cloudinary.com/carlosdev/image/upload/v1590895292/react-lerna-css/green-button_kxol1r.png)

![outline white button](https://res.cloudinary.com/carlosdev/image/upload/v1590895298/react-lerna-css/outline-white-button_ragffr.png)

### Enhancing the Text Component

We are just going to grab a couple of their sizes in their stack and not use their proprietary font.

`phoenix-text/lib/styles.css`

```css
.Text {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
    'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.5;
}

.Hero {
  font-size: 3rem;
  font-weight: 800;
  line-height: 3.25rem;
}

.Heading {
  font-size: 2.5rem;
  font-weight: 500;
}
```

`phoenix-text/lib/phoenix-text.js`

```jsx
import React from 'react';
import cx from 'clsx';
import styles from './styles.css';

const Text = ({ children, className, as = 'p', variant, ...rest }) => {
  const textVariant = styles[variant] || 'Body';
  console.log(textVariant);
  const classes = cx(
    styles.Text,
    {
      [textVariant]: variant,
    },
    className
  );
  return React.createElement(
    as,
    {
      ...rest,
      className: classes,
    },
    children
  );
};

export { Text };
```

`phoenix-text/lib/phoenix-text.stories.js`

```jsx
import React from 'react';
import { Text } from '../lib/phoenix-text';

export default { title: 'Text' };

export const Body = () => <Text>Body Text</Text>;
export const Hero = () => <Text variant="Hero">Hero Text</Text>;
export const Heading = () => <Text variant="Heading">Heading Text</Text>;
```

## Conclusion

You know have CSS Modules support both for your compiled code as well as your storybook; With this it will be a little bit more difficult to have collisions on the auto generated classes and you can even go one step further and provide the source code so your clients compile the code and generate the classes and styles themselves.

## Resources

Code: https://github.com/davixyz/phoenix/tree/part3
Github: https://github.com/davixyz
Twitter: https://twitter.com/carloscastrodev