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.
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;