Billing Toggle

Switch between monthly and annual pricing display.

Monthly Annual Save 17%

$29/mo or $290/yr

html

<div class="billing">
  <span class="billing__label">Monthly</span>
  <label class="billing__switch">
    <input type="checkbox" class="billing__input" checked aria-label="Toggle annual billing" />
    <span class="billing__track" style="--accent:#241430;"></span>
  </label>
  <span class="billing__label">Annual <span class="billing__badge" style="background:#241430;">Save 17%</span></span>
  <p class="billing__price"><strong>$29</strong>/mo or <strong>$290</strong>/yr</p>
</div>
<style>
.billing { font: 16px system-ui, sans-serif; color: #241430; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.billing__switch { position: relative; display: inline-block; width: 44px; height: 24px; }
.billing__input { position: absolute; opacity: 0; width: 44px; height: 24px; margin: 0; cursor: pointer; }
.billing__track { position: absolute; inset: 0; background: #24143033; border-radius: 999px; }
@media (prefers-reduced-motion: no-preference) {
.billing__track { transition: background .15s; }
}
.billing__track::before { content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: #FAF7F2; }
@media (prefers-reduced-motion: no-preference) {
.billing__track::before { transition: transform .15s; }
}
.billing__input:checked + .billing__track { background: var(--accent); }
.billing__input:checked + .billing__track::before { transform: translateX(20px); }
.billing__input:focus-visible + .billing__track { outline: 2px solid var(--accent); outline-offset: 2px; }
.billing__badge { font-size: 11px; padding: 2px 6px; border-radius: 999px; color: #FAF7F2; margin-left: 4px; }
.billing__price { flex-basis: 100%; margin: 8px 0 0; font-size: 14px; color: #241430cc; }
</style>

tailwind

<div class="flex flex-wrap items-center gap-2.5 font-sans text-[#241430]">
  <span>Monthly</span>
  <label class="relative inline-block w-11 h-6">
    <input type="checkbox" class="billing-tw__input absolute opacity-0 w-11 h-6 m-0 cursor-pointer peer" checked aria-label="Toggle annual billing" />
    <span class="billing-tw__track absolute inset-0 bg-[#24143033] rounded-full motion-safe:transition-colors peer-focus-visible:outline peer-focus-visible:outline-2" style="--accent:#241430;outline-color:#241430;"></span>
  </label>
  <span>Annual <span class="text-[11px] px-1.5 py-0.5 rounded-full text-[#FAF7F2] ml-1" style="background:#241430;">Save 17%</span></span>
  <p class="basis-full m-0 mt-2 text-sm text-[#241430]/80"><strong>$29</strong>/mo or <strong>$290</strong>/yr</p>
</div>
<style>
.billing-tw__track::before { content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: #FAF7F2; }
@media (prefers-reduced-motion: no-preference) {
.billing-tw__track::before { transition: transform .15s; }
}
.billing-tw__input:checked + .billing-tw__track { background: var(--accent); }
.billing-tw__input:checked + .billing-tw__track::before { transform: translateX(20px); }
</style>

react

import { useState } from "react";

export function BillingToggle({
  monthlyPrice = 29,
  annualPrice = 290,
  defaultAnnual = true,
  accent = "#241430",
  discountBadge = "Save 17%",
}) {
  const [annual, setAnnual] = useState(defaultAnnual);
  return (
    <div style={{ font: "16px system-ui, sans-serif", color: "#241430", display: "flex", flexWrap: "wrap", alignItems: "center", gap: 10 }}>
      <span>Monthly</span>
      <label style={{ position: "relative", display: "inline-block", width: 44, height: 24 }}>
        <input type="checkbox" checked={annual} onChange={() => setAnnual((a) => !a)} aria-label="Toggle annual billing"
          style={{ position: "absolute", opacity: 0, width: 44, height: 24, margin: 0, cursor: "pointer" }} />
        <span className="bt-track" style={{ position: "absolute", inset: 0, background: annual ? accent : "#24143033", borderRadius: 999 }}>
          <span className="bt-thumb" style={{ position: "absolute", top: 3, left: annual ? 23 : 3, width: 18, height: 18, borderRadius: "50%", background: "#FAF7F2" }} />
        </span>
      </label>
      <style>{`
        @media (prefers-reduced-motion: no-preference) {
          .bt-track { transition: background .15s; }
          .bt-thumb { transition: left .15s; }
        }
      `}</style>
      <span>Annual <span style={{ fontSize: 11, padding: "2px 6px", borderRadius: 999, color: "#FAF7F2", background: accent, marginLeft: 4 }}>{discountBadge}</span></span>
      <p style={{ flexBasis: "100%", margin: "8px 0 0", fontSize: 14, color: "#241430cc" }}>
        <strong>${monthlyPrice}</strong>/mo or <strong>${annualPrice}</strong>/yr
      </p>
    </div>
  );
}