Create custom button component in react

Learn how to create your own custom button component in react.

Buttons are the most common component or as we say an element in the web app but using the native HTML element has some drawbacks like styles differ for different browsers.

We can overcome this design issues buy using custom class for styling just like many other CSS frameworks like Bootstrap and Other does.

But creating custom component will provide us extra control over it and also we can extend it as per our need.

So let’s start creating our button component in React.

Before creating any component, I will recommend you to list down the requirements that you want in your component like style, behavior, ability to extend etc.

Here I will be creating a button which will have.

1. Different variants of style and hover effect.
2. Can have either only name (label) or can have child in it (Like an icon).
3. Click event handler.

We will need few extra npm packages to create the component.
1. PropTypes:- To make sure we receive the required props.
2. Classnames:- To use CSS class as an object and other things.

In order to make classnames work we will need to rename our CSS file to index.module.css.

React button component folder structure

This way we won’t need to configure anything.

First, import all the required packages and setup the class.

import React, { Component } from "react";
import styles from "./index.module.css";
import cx from "classnames";
import PropTypes from "prop-types";

class Button extends Component {
  //Code goes here
}
 
export default Button;

Then we will be defining the props we expect and also set the defaults if the required props is not passed.

Inside our class, define the static method for props validation.

class Button extends Component {
  static propTypes = {
    onClick: PropTypes.func,
    children: PropTypes.node,
    variant: PropTypes.string,
    className: PropTypes.string,
    label: PropTypes.string,
    size: PropTypes.string,
    disabledClassName: PropTypes.string,
    disabled: PropTypes.bool
  };

  static defaultProps = {
    className: "",
    label: "",
    size: "",
    variant: "basic",
    disabled: false,
    disabledClassName: ""
  };
}

As you can we are keeping our component very flexible by allowing it to pass custom CSS class for different state of button.

Now to apply different styles together we will can club them together and use it at once. The classes can be toggled based on the boolean value with the help of classnames. This will be inside our render method.

const {
  className,
  size,
  variant,
  disabled,
  disabledClassName
} = this.props;

const _className = cx(
  className,
  styles[size],
  styles.button,
  styles[variant],
  {
    [styles.disabled]: disabled,
    [disabledClassName]: disabled
  }
);

And we will be using a function which we will be returning the child that will be rendered inside the button.

First priority will be given to the label, then to the child element passed other wise default text Button will be returned.

renderChildren = () => {
 const { label, children } = this.props;

 if (label) {
   return label;
 }

 if (children) {
   return children;
 }

 return "Button";
};

And also we are expecting a function called onClick from parent which will notify the parent if the button is clicked.

We will call this function inside our event handler which will check if the button is in disabled state or not and then call the function.

handleButtonClick = event => {
  const { onClick, disabled } = this.props;

  if (disabled) return;

  onClick && onClick({ event });
};

Now render the button

render() {
  return (
    <div onClick={this.handleButtonClick} className={_className}>
       {this.renderChildren()}
    </div>
  );
}

Complete react button component code

//index.js
import React, { Component } from "react";
import styles from "./index.module.css";
import cx from "classnames";
import PropTypes from "prop-types";

class Button extends Component {
  static propTypes = {
    onClick: PropTypes.func,
    children: PropTypes.node,
    variant: PropTypes.string,
    className: PropTypes.string,
    label: PropTypes.string,
    size: PropTypes.string,
    disabledClassName: PropTypes.string,
    disabled: PropTypes.bool
  };

  static defaultProps = {
    className: "",
    label: "",
    size: "",
    variant: "basic",
    disabled: false,
    disabledClassName: ""
  };

  handleButtonClick = event => {
    const { onClick, disabled } = this.props;

    if (disabled) return;

    onClick &&
      onClick({
        event
      });
  };

  renderChildren = () => {
    const { label, children } = this.props;

    if (label) {
      return label;
    }

    if (children) {
      return children;
    }

    return "Button";
  };

  render() {
    const {
      className,
      size,
      variant,
      disabled,
      disabledClassName
    } = this.props;

    const _className = cx(
      className,
      styles[size],
      styles.button,
      styles[variant],
      {
        [styles.disabled]: disabled,
        [disabledClassName]: disabled
      }
    );

    return (
      
{this.renderChildren()}
); } } export default Button;

Now here is our style sheet.

In which we have a base class which defines the structure or the basic layout of the button.

.button {
  position: relative;
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  padding: 0.7em 1.2em;
  cursor: pointer;
  user-select: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  min-width: 96px;
  border-radius: 4px;
  background-color: #fff;
  border: 1px solid;
  color: #fff;
  -webkit-transition: background 0.2s ease;
  -moz-transition: background 0.2s ease;
  -o-transition: background 0.2s ease;
  transition: background 0.2s ease;
}

Now define the size of the button. As we have set the padding property with em values they will automatically change according to the font size.

So all we have to do is increase or decrease the font size.

.medium {
  font-size: 16px;
}

.large {
  font-size: 18px;
}

And the last but not the least, different variants of the button and their hover effects

.outline {
  background-color: #fff;
  border-color: #3777d1;
  color: #3777d1;
}

.outline:hover {
  background-color: #3777d1;
  border-color: #3777d1;
  color: #fff;
}

.basic {
  background-color: #3777d1;
  border-color: #3777d1;
  color: #fff;
}

.basic:hover {
  background-color: #fff;
  border-color: #fff;
  color: #3777d1;
}

.link {
  background-color: transparent;
  color: #4c4c4c;
  box-shadow: none;
}

.link:hover {
  color: #40a9ff;
  background-color: transparent;
}

.secondary {
  background-color: #fff;
  border-color: rgb(186, 186, 186);
  color: #4c4c4c;
}

.secondary:hover {
  background-color: #eee;
  border-color: #eee;
}

.primary {
  background-color: #2fcb53;
  border-color: #2fcb53;
  color: #fff;
}

.primary:hover {
  background-color: #48dd84;
  border-color: #48dd84;
}

.danger {
  color: #fff;
  background-color: rgb(214, 69, 64);
  border-color: rgb(214, 69, 64);
}

.danger:hover {
  background-color: rgb(208, 50, 45);
  border-color: rgb(208, 50, 45);
}

.disabled {
  background-color: #fff;
  border-color: #bababa;
  color: #bababa;
  box-shadow: none;
  opacity: 0.7;
  cursor: not-allowed;
  border: 1px solid;
}

.disabled:hover {
  background-color: #fff;
  color: #bababa;
  border-color: #bababa;
}

You can add as many variants as you want.

Complete CSS code

//index.modules.css
.button {
  position: relative;
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  padding: 0.7em 1.2em;
  cursor: pointer;
  user-select: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  min-width: 96px;
  border-radius: 4px;
  background-color: #fff;
  border: 1px solid;
  color: #fff;
  -webkit-transition: background 0.2s ease;
  -moz-transition: background 0.2s ease;
  -o-transition: background 0.2s ease;
  transition: background 0.2s ease;
}

.medium {
  font-size: 16px;
}

.large {
  font-size: 18px;
}

.outline {
  background-color: #fff;
  border-color: #3777d1;
  color: #3777d1;
}

.outline:hover {
  background-color: #3777d1;
  border-color: #3777d1;
  color: #fff;
}

.basic {
  background-color: #3777d1;
  border-color: #3777d1;
  color: #fff;
}

.basic:hover {
  background-color: #fff;
  border-color: #fff;
  color: #3777d1;
}

.link {
  background-color: transparent;
  color: #4c4c4c;
  box-shadow: none;
}

.link:hover {
  color: #40a9ff;
  background-color: transparent;
}

.secondary {
  background-color: #fff;
  border-color: rgb(186, 186, 186);
  color: #4c4c4c;
}

.secondary:hover {
  background-color: #eee;
  border-color: #eee;
}

.primary {
  background-color: #2fcb53;
  border-color: #2fcb53;
  color: #fff;
}

.primary:hover {
  background-color: #48dd84;
  border-color: #48dd84;
}

.danger {
  color: #fff;
  background-color: rgb(214, 69, 64);
  border-color: rgb(214, 69, 64);
}

.danger:hover {
  background-color: rgb(208, 50, 45);
  border-color: rgb(208, 50, 45);
}

.disabled {
  background-color: #fff;
  border-color: #bababa;
  color: #bababa;
  box-shadow: none;
  opacity: 0.7;
  cursor: not-allowed;
  border: 1px solid;
}

.disabled:hover {
  background-color: #fff;
  color: #bababa;
  border-color: #bababa;
}

Input

ReactDOM.render(
  <div className="abc">
    <Button label="Basic" variant="basic" />
    <Button label="Link" variant="link" />
    <Button label="Secondary" variant="secondary" />
    <Button label="Danger" variant="danger" />
    <Button label="Disabled" disabled={true} />
    <Button label="Primary" variant="primary" />
    <Button label="Outline" variant="outline" />
    <Button label="Medium" variant="primary" size="medium" />
    <Button label="Large" variant="outline" size="large" />
  </div>,
  document.getElementById("root")
);

Output

React Button Variant