Toggle switch in react

Learn how to create a toggle switch in React

Toggle switch is used for boolean representation of any thing. For example, Visible or Not, Dark Or Light mode, Yes or No like this.

Toggle switch in javascript

Day night toggle switch with animation

To create a toggle-switch we make use of the checkbox to determine the checked and the unchecked state and based on that we do some styling with minor animations to represent the shift in state.

These types of components can be extended for different use case, this it is better to define the props upfront so that we are aware what we have to develop.

ToggleSwitch.propTypes = {
  name: PropTypes.string,
  defaultChecked: PropTypes.bool,
  onChange: PropTypes.func,
  checked: PropTypes.bool,
  className: PropTypes.string,
  rounded: PropTypes.bool,
  variant: PropTypes.oneOf(["primary", "success", "danger"]),
  /* Children to show on active state  */
  checkedChildren: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.element
  ]),
  /* Children to show on inactive state */
  uncheckedChildren: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.element
  ]),
  innerRef: PropTypes.instanceOf(Element)
};

ToggleSwitch.defaultProps = {
  defaultChecked: false,
  checked: false,
  variant: "primary"
}

I have defined the three variants for the style primary, success, danger and to make this component controlled I am accepting the onChange function and the checked in prop, and also pass the reference to access the component's properties in uncontrolled environment.

With this lets defined the skeleton of the component.

import React, { useState } from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import styles from "./style.css";

const ToggleSwitch = ({
      checked, 
      defaultChecked, 
      name, 
      onChange, 
      rounded, 
      checkedChildren,
      uncheckedChildren,
      className,
      variant,
      innerRef
    }) => {

  const [_checked, setChecked] = useState(defaultChecked || checked || false);
};

export default React.forwardRef((props, ref) => {
  return ;
});

We are forwarding the reference in case the component needs to referenced.

For designing, we create an overlay with CSS style above the checkbox which is visible to the user, but whenever the click event is triggered it will take place on the checkbox only.

    const _className = cx("wrapper", className);

    const _slideClassName = cx("slider", variant, {
      ["round"]: rounded
    });

    const _checkedChildrenClassName = cx("children", "checked", {
      ["visible"]: _checked
    });

    const _uncheckedChildrenClassName = cx("children", "unchecked", {
      ["visible"]: !_checked
    });

    return (
      <span className={_className}>
        <span className={"switch"}>
          <input
            type="checkbox"
            checked={_checked}
            onChange={handleChange}
            name={name}
            ref={innerRef}
          />

          {/* Overlay */}
          <span className={_slideClassName} />

          {/* Childrens */}
          <>
            <span className={_checkedChildrenClassName}>
              {checkedChildren || null}
            </span>
            <span className={_uncheckedChildrenClassName}>
              {uncheckedChildren || null}
            </span>
          </>
        </span>
      </span>
    );

We keep the z-index of the checkbox to 1 but keep it's opacity to 0, which makes the element hidden to the user but it is still interactable. The overlay is absolutely place above the input which keeps it visible but when you click on it, you actually click the checkbox.

.switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}

.switch input {
  opacity: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  position: relative;
  cursor: pointer;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

Finally, on the click of the toggle switch we update the state and trigger the onChange function.

const handleChange = e => {
    const { checked } = e.target;
    setChecked(checked);
    onChange &&
      onChange({
        name,
        checked
      });
  };

Putting it all together

import React, { useState } from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import styles from "./style.css";

const ToggleSwitch = ({
      checked, 
      defaultChecked, 
      name, 
      onChange, 
      rounded, 
      checkedChildren,
      uncheckedChildren,
      className,
      variant,
      innerRef
    }) => {

  const [_checked, setChecked] = useState(defaultChecked || checked || false);
  
  const handleChange = e => {
    const { checked } = e.target;
    setChecked(checked);
    onChange &&
      onChange({
        name,
        checked
      });
  };

    const _className = cx("wrapper", className);

    const _slideClassName = cx("slider", variant, {
      ["round"]: rounded
    });

    const _checkedChildrenClassName = cx("children", "checked", {
      ["visible"]: _checked
    });

    const _uncheckedChildrenClassName = cx("children", "unchecked", {
      ["visible"]: !_checked
    });

    return (
      <span className={_className}>
        <span className={"switch"}>
          <input
            type="checkbox"
            checked={_checked}
            onChange={handleChange}
            name={name}
            ref={innerRef}
          />

          {/* Overlay */}
          <span className={_slideClassName} />

          {/* Childrens */}
          <>
            <span className={_checkedChildrenClassName}>
              {checkedChildren || null}
            </span>
            <span className={_uncheckedChildrenClassName}>
              {uncheckedChildren || null}
            </span>
          </>
        </span>
      </span>
    );
};

ToggleSwitch.propTypes = {
  name: PropTypes.string,
  defaultChecked: PropTypes.bool,
  onChange: PropTypes.func,
  checked: PropTypes.bool,
  className: PropTypes.string,
  rounded: PropTypes.bool,
  variant: PropTypes.oneOf(["primary", "success", "danger"]),
  /* Children to show on active state  */
  checkedChildren: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.element
  ]),
  /* Children to show on inactive state */
  uncheckedChildren: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.element
  ]),
  innerRef: PropTypes.instanceOf(Element)
};

ToggleSwitch.defaultProps = {
  defaultChecked: false,
  checked: false,
  variant: "primary"
}

export default React.forwardRef((props, ref) => {
  return <ToggleSwitch {...props} innerRef={ref} />;
});

Style

.wrapper {
  display: inline-flex;
  margin: 0 10px;
}

.switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}

.switch input {
  opacity: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  position: relative;
  cursor: pointer;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

/* Different variants*/
input:checked + .slider.primary {
  background-color: #2196f3;
}

input:focus + .slider.primary {
  box-shadow: 0 0 1px #2196f3;
}

input:checked + .slider.danger {
  background-color: #ff5722;
}

input:focus + .slider.danger {
  box-shadow: 0 0 1px #ff5722;
}

input:checked + .slider.success {
  background-color: #7cb342;
}

input:focus + .slider.success {
  box-shadow: 0 0 1px #7cb342;
}

input:checked + .slider:before {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}

/* Rounded sliders */
.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}

/*Childrens style */
.children {
  position: absolute;
  top: 58%;
  height: 26px;
  width: 26px;
  text-align: center;
  line-height: 26px;
  transition: 0.12s;
  opacity: 0;
  transform: translateY(-50%);
}

.checked {
  left: 2px;
}

.unchecked {
  right: 2px;
}

.visible {
  opacity: 1;
}

Test

import React from "react";
import ToggleSwitch from "./App";
import { CheckOutlined, CloseOutlined } from "@ant-design/icons";

const Test = () => {
  return <>
    <ToggleSwitch name="abc" rounded={true} checked={true}/>
    <ToggleSwitch name="abc" variant="success" />
    <ToggleSwitch
      name="abc"
      rounded={true}
      variant="danger"
      defaultChecked={true}
      checkedChildren={<CheckOutlined />}
      uncheckedChildren={<CloseOutlined />}
    />
    <ToggleSwitch
      name="abc"
      defaultChecked={true}
      variant="danger"
      checkedChildren={<CheckOutlined />}
      uncheckedChildren={<CloseOutlined />}
    />
  </>
};

export default Test;

Output

React Toggle Switch