react-switch and react-toggle are both React components designed to render accessible toggle switches, replacing native checkboxes with stylized UI elements. react-switch focuses on providing a highly customizable, dependency-free component that closely mimics native input behavior while allowing extensive CSS control via className props. react-toggle, historically popular for its sleek default design and animation, offers a similar API but has faced maintenance concerns in recent years. Both packages aim to solve the same problem: creating a user-friendly on/off control that works across devices and screen readers, but they differ in their approach to styling, dependencies, and long-term support.
Both react-switch and react-toggle solve a common UI problem: the native HTML checkbox is hard to style consistently across browsers. These packages replace the default checkbox with a custom component that looks like a switch or toggle while keeping the underlying accessibility features intact. Let's look at how they handle customization, state management, and long-term reliability.
react-switch gives you control by exposing specific className props for every part of the switch.
// react-switch: Class-based styling
import Switch from "react-switch";
<Switch
checked={isChecked}
onChange={handleChange}
onColor="#4ade80"
offColor="#9ca3af"
className="my-custom-switch"
checkedIcon={false}
uncheckedIcon={false}
/>
react-toggle relies more on inline styles and a theme object for customization.
// react-toggle: Style object customization
import Toggle from "react-toggle";
<Toggle
checked={isChecked}
onChange={handleChange}
icons={false}
className="toggle-class"
style={{
track: { width: 40, height: 20 },
thumb: { width: 18, height: 18 }
}}
/>
react-switch treats the component as a controlled input by default.
<input type="checkbox"> visually hidden but present for screen readers.name, value, and aria-label directly.// react-switch: Native input props
<Switch
name="notifications"
value="1"
aria-label="Enable notifications"
checked={isChecked}
onChange={handleChange}
/>
react-toggle also maintains accessibility but wraps the input differently.
name and value, but sometimes need extra care to ensure label association works as expected in complex forms.// react-toggle: Input props
<Toggle
name="settings"
value="enabled"
aria-label="Toggle settings"
checked={isChecked}
onChange={handleChange}
/>
react-switch keeps animations simple and CSS-driven.
/* react-switch: CSS control */
.react-switch-handle {
transition: transform 0.2s ease;
}
// react-switch: Usage
<Switch
onChange={handleChange}
checked={isChecked}
// Animation speed controlled via CSS
/>
react-toggle has a built-in animated thumb that moves smoothly by default.
// react-toggle: Built-in animation
<Toggle
onChange={handleChange}
checked={isChecked}
// Default animation is active immediately
/>
react-switch has zero external dependencies.
// react-switch: No extra imports needed
import Switch from "react-switch";
// Only React is required
react-toggle historically depended on classnames and prop-types.
prop-types is often unnecessary, making this a slight overhead.// react-toggle: External deps
import Toggle from "react-toggle";
// Internally uses classnames and prop-types
react-switch shows consistent updates and responsiveness to React version changes.
// react-switch: Modern React support
const MySwitch = ({ checked, onChange }) => (
<Switch checked={checked} onChange={onChange} />
);
react-toggle has had periods of inactivity in its release history.
// react-toggle: Check for warnings
<Toggle
checked={checked}
onChange={onChange}
// Monitor console for deprecation warnings in strict mode
/>
While the differences matter, both packages share core functionality that makes them viable toggle solutions.
checked state externally.// Both: Controlled usage
const [checked, setChecked] = useState(false);
// Works for both Switch and Toggle
<Component
checked={checked}
onChange={(e) => setChecked(e.target.checked)}
/>
onChange prop that mimics the native input event.target.checked.// Both: Event access
handleChange = (checked, event) => {
console.log(event.target.checked);
};
disabled prop to gray out the switch and prevent interaction.// Both: Disabled state
<Component disabled={true} checked={false} onChange={() => {}} />
<label> tag or linked via id and htmlFor.// Both: Label wrapping
<label>
<Component checked={checked} onChange={handleChange} />
<span>Enable Feature</span>
</label>
// Both: Cross-device support
// No extra config needed, works on mobile and desktop out of the box
| Feature | Shared by react-switch and react-toggle |
|---|---|
| State Management | β
Controlled checked prop |
| Events | β
onChange with event object |
| Accessibility | β Hidden native checkbox input |
| Interaction | β Mouse and Touch support |
| Forms | β
name and value props |
| Feature | react-switch | react-toggle |
|---|---|---|
| Styling | π¨ Class-based, CSS control | ποΈ Inline styles, theme object |
| Dependencies | π¦ Zero dependencies | π¦ Uses classnames, prop-types |
| Maintenance | π’ Active and consistent | π‘ Periods of inactivity |
| Default Look | βͺ Basic, needs CSS | π Polished, animated out of box |
| Customization | π§ High (via classes) | π§ Medium (via style props) |
react-switch is like a blank canvas π¨βit gives you the structure and logic, but you paint the design. This is perfect for teams with a dedicated design system who need the toggle to match exact brand guidelines without fighting default styles. It is also the safer choice for long-term maintenance due to its active development status.
react-toggle is like a pre-finished piece of furniture πͺβit looks great immediately and requires little setup. This is useful for internal tools, dashboards, or prototypes where speed matters more than pixel-perfect customization. However, keep an eye on its repository activity if you plan to use it in a critical production app.
Final Thought: Both components will get the job done for a basic on/off switch. If you value control and stability, go with react-switch. If you value speed and default aesthetics, react-toggle still holds up, provided you verify its compatibility with your stack.
Choose react-switch if you need a actively maintained, dependency-free component that gives you full control over styling without fighting default CSS. It is ideal for projects requiring strict accessibility compliance and custom design systems where you need to map every state (checked, unchecked, disabled) to your own classes. Its API is straightforward and aligns well with standard React input patterns, making it a safer bet for long-term projects.
Choose react-toggle if you are maintaining a legacy codebase that already relies on it or if you specifically want its classic animated thumb design out of the box with minimal CSS effort. However, be cautious with new projects as the package has shown signs of reduced maintenance activity. It works well for quick prototypes or internal tools where the default aesthetic fits, but verify its compatibility with your current React version before committing.
A draggable toggle-switch component for React.
npm install react-switch
import React, { Component } from "react";
import Switch from "react-switch";
class SwitchExample extends Component {
constructor() {
super();
this.state = { checked: false };
this.handleChange = this.handleChange.bind(this);
}
handleChange(checked) {
this.setState({ checked });
}
render() {
return (
<label>
<span>Switch with default style</span>
<Switch onChange={this.handleChange} checked={this.state.checked} />
</label>
);
}
}
The Switch component in the above example is nested inside a label tag. This makes sure that the label text is read out to people with reduced sight who use screen readers and enables users to click on the text to toggle the switch. If you would only put some text next to the switch but not inside a label element, the screen reader will just read out "switch off" and the user will have no idea what it is for.
If you don't want to nest the switch inside a label, you can use the htmlFor attribute on the label-element and set it to the same value as the id of the switch. Alternatively, you can use the aria-labelledby or aria-label props to give the switch a label. You can see examples of this at the bottom of the demo page.
| Prop | Type | Default | Description |
|---|---|---|---|
| checked | bool | Required | If true, the switch is set to checked. If false, it is not checked. |
| onChange ([checked], [event], [id]) | func | Required | Invoked when the user clicks or drags the switch. It is passed three arguments: checked, which is a boolean that describes the presumed future state of the checked prop (1), the event object (2) and the id prop (3). |
| disabled | bool | false | When disabled, the switch will no longer be interactive and its colors will be greyed out. |
| offColor | string | '#888' | The switch will take on this color when it is not checked. Only accepts hex-colors. |
| onColor | string | '#080' | The switch will take on this color when it is checked. Only accepts hex-colors. |
| offHandleColor | string | '#fff' | The handle of the switch will take on this color when it is not checked. Only accepts hex-colors. |
| onHandleColor | string | '#fff' | The handle of the switch will take on this color when it is checked. Only accepts hex-colors. |
| handleDiameter | number | undefined | The diameter of the handle, measured in pixels. By default it will be 2 pixels smaller than the height of the switch. |
| uncheckedIcon | element or bool | Default value | An icon that will be shown on the switch when it is not checked. Pass in false if you don't want any icon. |
| checkedIcon | element or bool | Default value | An icon that will be shown on the switch when it is checked. Pass in false if you don't want any icon. |
| uncheckedHandleIcon | element | undefined | An icon that will be shown on the handle of the switch when it is not checked. |
| checkedHandleIcon | element | undefined | An icon that will be shown on the handle of the switch when it is checked. |
| boxShadow | string | undefined | The default box-shadow of the handle. You can read up on the box-shadow syntax on MDN. |
| activeBoxShadow | string | '0 0 2px 3px #3bf' | The box-shadow of the handle when it is active or focused. Do not set this to null, since it is important for accessibility. |
| height | number | 28 | The height of the background of the switch, measured in pixels. |
| width | number | 56 | The width of the background of the switch, measured in pixels. |
| className | string | undefined | Set as the className of the outer shell of the switch. Useful for positioning the switch. |
| borderRadius | number | undefined | Border radius of the switch and the handle. |
| id | string | undefined | Set as an attribute to the embedded checkbox. This is useful for the associated label, which can point to the id in its htmlFor attribute. |
NOTE: All additional props will be passed to the nested input element.
The following props have to be either 3-digit or 6-digit hex-colors: offColor, onColor, offHandleColor, and onHandleColor. This is because this library calculates intermediate color values based on the hex-color strings.
Examples of valid colors: '#abc', '#123456'
Examples of invalid colors: 'red', 'rgb(0,0,0)'
You're welcome to contribute to react-switch. Keep in mind that big changes have to be thoroughly tested on lots of different browsers and devices before they can be merged.
To set up the project:
$ npm install$ npm run devThe demo page will then be served on http://localhost:8000/ in watch mode, meaning you don't have refresh the page to see your changes.
|
Markus Englund |
Timothy McLane |
MIT