James Swanson
James Swanson

Reputation: 61

How to style the same component in multiple ways using CSS Modules in React?

I have just recently switched to using CSS Modules in my project and have come into a bit of an issue.

In the simple code example below, I am creating an input component that I want to be able to reuse as a form field when creating forms. The issue comes in when I want to be able to style the input component differently in certain situations.

FormInput.js

const FormInput = props => (
  <FormControl>
    <InputLabel>{props.label}</InputLabel>
    <Input />
  </FormControl>
);

Form.js

import React from 'react';
import Button from '@material-ui/core/Button';
import Input from './input';

const Form = () => (
  <form>
    <Input label="Name" />
    <Button> Submit </Button>
  </form>
);

I know this can be done using styled-components, but I am really looking for a solution that uses CSS Modules. Any help would be appreciated

Upvotes: 1

Views: 4481

Answers (2)

Matt Carlotta
Matt Carlotta

Reputation: 19772

You can simply import the css and pass it as a prop to Input. In this case, you can pass it down as className.

Note: As you'll notice below, CSS can be a bit redundant when it comes to styling nested and pseudo elements, which is why I'd highly recommend SASS (scss or less) for preprocessed stylesheets (post-processing will convert the SASS stylesheets to a plain CSS stylesheet for you).

Working example (reusing Input but styling it with different classes):

Edit React CSS Modules


components/Input (it accepts a className string, onChange function (required), label string, name string (required), and a value string)

import React from "react";
import PropTypes from "prop-types";

const Input = ({ className, onChange, label, name, value }) => (
  <div className={className}>
    <label htmlFor={name}>{label}: </label>
    <input value={value} onChange={onChange} name={name} type="text" />
  </div>
);

// PropTypes ensures that passed down props adhere to the type checking
// rules defined below
Input.propTypes = {
  className: PropTypes.string,
  onChange: PropTypes.func.isRequired,
  label: PropTypes.string,
  name: PropTypes.string.isRequired,
  value: PropTypes.string
};

export default Input;

styles.css (you'll need to use camelCase instead of snake-case for classNames)

.appContainer {
  text-align: center;
  padding: 20px;
}

input {
  height: 40px;
  vertical-align: middle;
  display: inline-block;
  border: 0 none;
  padding: 0 10px;
  background: #fff;
  color: #666;
  border: 1px solid #e5e5e5;
  transition: 0.2s ease-in-out;
  transition-property: color, background-color, border;
  font-size: 15px;
}

.nameField {
  font-weight: bold;
  color: blue;
  margin-bottom: 20px;
}

.nameField > input {
  color: green;
}

.emailField {
  font-weight: bold;
  color: red;
  margin-bottom: 20px;
}

.emailField > input {
  color: blue;
}

.resetButton {
  cursor: pointer;
  background-color: transparent;
  color: #222;
  border: 1px solid #e5e5e5;
  margin: 0;
  overflow: visible;
  box-sizing: border-box;
  padding: 0 30px;
  vertical-align: middle;
  font-size: 14px;
  line-height: 38px;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
  transition: 0.1s ease-in-out;
  transition-property: color, background-color, border-color;
}

.resetButton:hover {
  background-color: transparent;
  color: #222;
  border-color: #b2b2b2;
}

.resetButton:focus {
  outline: none;
}

components/App (import all of the css as classes and apply them as needed -- you can also use ES6 destructuring to pull out single classes, for example: import { appContainer } from "./styles.css";)

import React from "react";
import { render } from "react-dom";
import Input from "./components/Input";
import useFieldHandler from "./hooks/useFieldHandler";
import classes from "./styles.css";

const App = () => {
  const { values, handleChange, resetValues } = useFieldHandler({
    name: "",
    email: ""
  });

  return (
    <div className={classes.appContainer}>
      <h1>CSS Modules</h1>
      <Input
        label="Name"
        name="name"
        className={classes.nameField}
        value={values.name}
        onChange={handleChange}
      />
      <Input
        label="Email"
        name="email"
        className={classes.emailField}
        value={values.email}
        onChange={handleChange}
      />
      <div className={classes.btnContainer}>
        <button
          type="button"
          className={classes.resetButton}
          onClick={resetValues}
        >
          Reset
        </button>
      </div>
    </div>
  );
};

render(<App />, document.getElementById("root"));

Upvotes: 1

cullanrocks
cullanrocks

Reputation: 497

So what I was explaining above is to pass a unique id as a prop and then you need to use a template literal to append that to the existing class name in the Input component.

FormInput.js

const FormInput = props => (
  <FormControl>
    <InputLabel>{props.label}</InputLabel>
    <Input uniqueClass='unique-class'/>
  </FormControl>
);
Form.js

import React from 'react';
import Button from '@material-ui/core/Button';
import Input from './input';

const Form = () => (
  <form>
    <Input uniqueClass='unique-class' label="Name" />
    <Button> Submit </Button>
  </form>
);

Upvotes: 0

Related Questions