deepmoon

Pricing Toggle

$24/mo

Installation

$ npx shadcn@latest add https://deepmoon.dev/r/pricing-toggle.json

Props

PricingToggleProps
PropTypeRequired
valuePricingPeriodYes
onChange(value: PricingPeriod) => voidYes
annualDiscountLabelstringNo
classNamestringNo

Code

pricing-toggle.tsx
import * as React from "react";

import { cn } from "@/lib/utils";

export type PricingPeriod = "monthly" | "annual";

export interface PricingToggleProps {
  value: PricingPeriod;
  onChange: (value: PricingPeriod) => void;
  annualDiscountLabel?: string;
  className?: string;
}

export function PricingToggle({ value, onChange, annualDiscountLabel = "Save 20%", className }: PricingToggleProps) {
  return (
    <div
      role="group"
      aria-label="Billing period"
      className={cn("inline-flex items-center gap-1 rounded-full border border-border bg-muted p-1", className)}
    >
      {(["monthly", "annual"] as const).map((period) => (
        <button
          key={period}
          type="button"
          aria-pressed={value === period}
          onClick={() => onChange(period)}
          className={cn(
            "flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm font-medium outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring",
            value === period ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground",
          )}
        >
          {period === "monthly" ? "Monthly" : "Annual"}
          {period === "annual" ? (
            <span
              className={cn(
                "rounded-full px-1.5 py-0.5 text-xs font-semibold",
                value === "annual" ? "bg-success/15 text-success" : "bg-muted-foreground/10 text-muted-foreground",
              )}
            >
              {annualDiscountLabel}
            </span>
          ) : null}
        </button>
      ))}
    </div>
  );
}