DEV Community 👩‍💻👨‍💻

Carlos Castro
Carlos Castro

Posted on

Writing your first React UI Library - Part 3: CSS Modules

css and html

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

lerna add rollup-plugin-postcss --scope=@cddev/phoenix-builder
Enter fullscreen mode Exit fullscreen mode

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

// 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
  ],
};
Enter fullscreen mode Exit fullscreen mode

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

.Button {
  background-color: red;
}
Enter fullscreen mode Exit fullscreen mode

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

phoenix-button/lib/phoenix-button.js

import React from 'react';
import styles from './styles.css';
const Button = ({ children }) => <button className={styles.Button}>{children}</button>;
export { Button };
Enter fullscreen mode Exit fullscreen mode

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

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:

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;
  },
};
Enter fullscreen mode Exit fullscreen mode

Voila!

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

red button with css modules

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

.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;
}
Enter fullscreen mode Exit fullscreen mode

Now for the code in the button:

phoenix-button/lib/phoenix-button.js

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 };
Enter fullscreen mode Exit fullscreen mode

And then enhancing our stories like this:

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

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>
);
Enter fullscreen mode Exit fullscreen mode

Now you should be able to see some variants of your fancy button
green button

outline white button

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

.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;
}
Enter fullscreen mode Exit fullscreen mode

phoenix-text/lib/phoenix-text.js

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 };
Enter fullscreen mode Exit fullscreen mode

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

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>;
Enter fullscreen mode Exit fullscreen mode

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

Top comments (1)

Collapse
 
rishisahu20 profile image
rishisahu20 • Edited on

Hi Carlos i'm not able to import css like this

import "./styles.css";

can you please explain why it's happening?

And another thing when i use rollup newer version after publishing it doesn't import css

Classic DEV Post from 2020:

js visualized

🚀⚙️ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! 🥳

Happy coding!