TL;DR Use a button with role=switch and aria-checked, style both states with Tailwind's aria-checked variants, and move the thumb with a transform and a slightly overshooting cubic-bezier. Add a hidden input so the value posts with a form, give it a visible label, and turn the motion off under prefers-reduced-motion.
Ask Cursor or v0 for a settings page and you usually get toggles that work with a mouse and little else. The thumb snaps, screen readers announce a checkbox or nothing at all, and the value never reaches the server action because there is no input behind it. I make animated React components at qodesign, and the toggle is the piece I rewrite most often in generated code.
This guide builds one switch component with plain React and Tailwind. No animation library, about 50 lines.
aria-checked: and group-aria-checked: are built into Tailwind. The track and the thumb read the same attribute that screen readers read, so the visual state can never drift from the announced one.
The easing cubic-bezier(.34,1.56,.64,1) goes past 1, so the thumb overshoots a few pixels and settles. That small bounce is most of what makes a switch feel physical. Keep it near 300ms; longer starts to feel slow when someone flips five settings in a row.
group-active:scale-90 squeezes the thumb while the pointer is down. It is a tiny press state, and it costs nothing.
label htmlFor points at the button, so clicking the text flips the switch and the accessible name comes from the label.
A sun and moon, or a check and a cross, helps when color alone carries the state. Put two icons in the thumb and cross-fade them with the same attribute:
A theme switch is the one toggle people notice, so it can carry more personality. The Day/Night Toggle rolls the sun down and brings in the stars, and it ships with a PROMPT.md so Cursor or v0 can wire it into your theme provider. It is free in the free kit along with the open-source pieces on GitHub. If you want the whole page to change with the theme, the circular reveal theme toggle post shows the View Transitions part. For a toggle people drag instead of click, see the draggable pull cord and the Pull-Cord Login that grew out of it.
Use a native button with role="switch" and aria-checked set to the current state, and tie a visible label to it with htmlFor. The button gives you focus, Space and Enter for free, and screen readers announce it as a switch that is on or off.
Put the state in aria-checked and style it with the aria-checked and group-aria-checked variants. Move the thumb with translate-x and a transition-transform, and use an easing curve that overshoots a little, such as cubic-bezier(.34,1.56,.64,1), for a springy feel.
Render a hidden input next to the switch with the field name and a value of on or off. The form posts it like any other field, and the server action reads it with formData.get.
Use a switch for settings that take effect right away, such as notifications or dark mode. Use a checkbox when the choice only applies after the person submits a form, or when they pick several items from a list.