@mshafiqyajid/react-switch
Headless toggle switch hook and styled component. Controlled and uncontrolled, sizes, tones, label positioning, loading spinner. Async onChange (Promise-driven pending state with revert on rejection).
Playground #
Enable notifications
Props
TSX
import { SwitchStyled } from "@mshafiqyajid/react-switch/styled";
import "@mshafiqyajid/react-switch/styles.css";
<SwitchStyled
checked={checked}
onChange={setChecked}
label="Enable notifications"
/>confirm guard demo — async, rejects ~30% of the time
The switch enters a pending (grey) state for ~900ms. If the guard rejects, it snaps back.
Guarded toggleInstall #
npm install @mshafiqyajid/react-switch Quick start #
import { SwitchStyled } from "@mshafiqyajid/react-switch/styled";
import "@mshafiqyajid/react-switch/styles.css";
const [on, setOn] = useState(false);
<SwitchStyled checked={on} onChange={setOn} label="Enable notifications" /> Async toggle #
Return a Promise from onChange to drive the pending state automatically. The switch shows a spinner during the promise, blocks further clicks, and reverts the optimistic value on rejection.
<SwitchStyled
defaultChecked
label="Email notifications"
onChange={async (next) => {
await fetch("/api/prefs/email", {
method: "POST",
body: JSON.stringify({ enabled: next }),
});
}}
/> Headless #
import { useSwitch } from "@mshafiqyajid/react-switch";
const { switchProps, isChecked, isPending, toggle } = useSwitch({
defaultChecked: false,
onChange: async (next) => {
await fetch("/api/prefs", { method: "POST", body: String(next) });
},
});
return (
<button {...switchProps} className={isChecked ? "on" : "off"} disabled={isPending}>
<span className="thumb" />
</button>
); API #
| Prop | Type | Default | Description |
|---|---|---|---|
| checked | boolean | — | Controlled checked state |
| defaultChecked | boolean | false | Uncontrolled initial state |
| onChange | (v: boolean) => void | Promise<void> | — | Called on toggle. Return a Promise to drive a pending state automatically; reverts on rejection. |
| size | "sm" | "md" | "lg" | "md" | Size |
| tone | "neutral" | "primary" | "success" | "danger" | "primary" | Color when on |
| label | ReactNode | — | Label text |
| labelPosition | "left" | "right" | "right" | Label side |
| loading | boolean | false | Force spinner in thumb. Auto-set when onChange returns a Promise. |
| disabled | boolean | false | Disable the switch |
The hook also returns isPending and the rendered switch lands aria-busy="true" + data-pending="true" while the promise is in flight.