24
Creating a Switch Toggle in React using styled-components.
I always find myself every now and again creating a "Switch" or "Toggle" component for a client project.
After making them quite a few times I've decided to put my findings down in this post.
They can be super easy to make, and there's a few nuances that go with them. Let's begin.
Note: I've built this using the technologies I use the most:
react
,typescript
andstyled-components
. But the CSS can be applied to any frontend stack :)
The whole component is built using just 4 components.
import styled from "styled-components";
const Label = styled.label``;
const Input = styled.input``;
const Switch = styled.div``;
const ToggleSwitch = () => {
return (
<Label>
<span>Toggle is off</span>
<Input />
<Switch />
</Label>
);
};
This gives us something like this:
Now we actually don't want to show the <input>
. But we do want it to be of type="checkbox"
.
This allows the user to be able to click on anything inside the <label>
to trigger the onChange
event, including our <span>
element.
Note: It's important here to keep the input in the DOM by setting
opacity: 0
andposition: absolute
. Why?
-
opacity: 0
will hide it from the user -
position: absolute
takes the element out of the normal doument flow. - This allows the user to "tab" to the label/input and use the spacebar to toggle the element.
const Input = styled.input`
opacity: 0;
position: absolute;
`;
// Set type to be "checkbox"
<Input type="checkbox" />
I'll add a few styles to the <label>
component, it's wrapping everything, so I want it to be display: flex
to align the <span>
and <Switch />
vertically.
The gap
gives us a straight forward 10px gap between elements, and the cursor: pointer
gives the user visual feedback saying "Hey! 👋 you can click me!".
I'll also add styling to the <Switch />
element.
const Label = styled.label`
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
`;
const Switch = styled.div`
width: 60px;
height: 32px;
background: #b3b3b3;
border-radius: 32px;
`
We now have something like this:
Next up I'm going to create a pseudo-element on the <Switch />
element. This will act as our switches "lever".
const Switch = styled.div`
position: relative; /* <-- Add relative positioning */
width: 60px;
height: 32px;
background: #b3b3b3;
border-radius: 32px;
padding: 4px; /* <!-- Add padding
/* Add pseudo element */
&:before {
content: "";
position: absolute;
width: 28px;
height: 28px;
border-radius: 35px;
top: 50%;
left: 4px; /* <!-- Make up for padding
background: white;
transform: translate(0, -50%);
}
`;
Now we have something that resembles a toggle switch:
To animate the switch to be in the "on" position when it's pressed I need to move the const Switch = styled.div
variable declaration to be above the const Input = styled.input
variable. This is so we can reference the Switch
from within Input
.
Using the :checked
pseudo-class selector and the adjacent sibling combinator, we can make our switch turn green.
const Input = styled.input`
display: none;
&:checked + ${Switch} {
background: green;
}
`;
Now in that same nested css structure, we can target the :before
pseudo-element of the Switch
element:
const Input = styled.input`
display: none;
&:checked + ${Switch} {
background: green;
&:before {
transform: translate(32px, -50%);
}
}
`;
Now all we have to do animate this into action is to add transition: 300ms
to our Switch
and the Switch
:before
pseudo-element
const Switch = styled.div`
position: relative;
width: 60px;
height: 28px;
background: #b3b3b3;
border-radius: 32px;
padding: 4px;
transition: 300ms all;
&:before {
transition: 300ms all;
content: "";
position: absolute;
width: 28px;
height: 28px;
border-radius: 35px;
top: 50%;
left: 4px;
background: white;
transform: translate(0, -50%);
}
`;
I'll add a basic onChange
handler and useState
hook to allow us to store the value of the checked input and change the text depending on the value:
const ToggleSwitch = () => {
const [checked, setChecked] = useState(false); // store value
const handleChange = (e: ChangeEvent<HTMLInputElement>) => setChecked(e.target.checked)
return (
<Label>
<span>Toggle is {checked ? 'on' : 'off'}</span>
<Input checked={checked} type="checkbox" onChange={handleChange} />
<Switch />
</Label>
);
};
And now we have a super simple working switch toggle:
Here's a CodeSandbox link
These things can be over-engineered sometimes, and there's also plenty of ways to recreate them.
If you wanna follow me on twitter for dev-related tweets you can find me here
24