Custom Pricing

extends regular pricing plans, with more flexibilty that allow to add custom features with custom pricing.

Installation

Usage

for one select (radio input) use <CustomPricingCheckedControl />

function SelectItem({
  id,
  label,
  price,
  checked,
  handleChange,
  groupId,
}: {
  id: string;
  label: string;
  price: string;
  checked: boolean;
  handleChange: () => void;
  groupId: string;
}) {
  return (
    <div className="relative group rounded-full transition-colors duration-200 has-[:checked]:text-white grid grid-cols-1 grid-rows-1 *:col-start-1 *:row-start-1">
      <input
        className="size-full [all:unset]"
        type="checkbox"
        name={id}
        value={id}
        id={id}
        checked={checked}
        onChange={handleChange}
      />
      <label
        htmlFor={id}
        className="relative p-2 z-2 text-nowrap pointer-events-none flex gap-px flex-wrap justify-center items-center text-xs font-medium"
      >
        {label} <span className="text-[10px] tabular-nums">{price}</span>
      </label>
      {checked && (
        <motion.div
          layoutId={groupId}
          className="size-full rounded-full"
          style={{
            background: 'var(--primary)',
            backgroundImage: 'linear-gradient(180deg, #444 0%, #000 100%)',
          }}
          transition={{
            type: 'spring',
            stiffness: 260,
            damping: 20,
            mass: 1,
          }}
        />
      )}
    </div>
  );
}
function Selects() {
  return (
    <div
      className="relative flex bg-input rounded-full ring ring-ring/40 p-0.5 w-fit border"
      id="custom-pricing-selects"
    >
      <CustomPricingCheckedControl
        id="landing page"
        defaultChecked={true}
        price={1500}
        group="website-type"
        render={({ id, checked, toggleChecked }) => (
          <div className="relative ">
            <SelectItem
              price={'$1.500'}
              id={id}
              checked={checked}
              handleChange={() => toggleChecked(id)}
              label="Landing page"
              groupId="custom-pricing-selects"
            />
          </div>
        )}
      />

      <CustomPricingCheckedControl
        id="business website"
        price={3000}
        group="website-type"
        render={({ id, checked, toggleChecked }) => (
          <div className="relative ">
            <SelectItem
              price={'$3.000'}
              id={id}
              checked={checked}
              handleChange={() => toggleChecked(id)}
              label="Business website"
              groupId="custom-pricing-selects"
            />
          </div>
        )}
      />
    </div>
  );
}

for toggle checks use <CustomPricingCheckedControl /> with type="checkbox"

<CustomPricingCheckedControl
  id="seo-optimization"
  price={200}
  type="checkbox"
  render={({ id, checked, toggleChecked }) => (
    <div className="flex items-center justify-between p-3 rounded-xl border bg-card/50 transition-colors hover:bg-card/80">
      <div className="space-y-0.5">
        <Label
          htmlFor={id}
          className="text-sm font-medium leading-none cursor-pointer"
        >
          SEO Optimization
        </Label>
      </div>
      <div className="flex items-center gap-3">
        <span className="text-xs font-medium tabular-nums">$200</span>
        <Switch
          checked={checked}
          onCheckedChange={() => toggleChecked(id)}
          id={id}
        />
      </div>
    </div>
  )}
/>

for quantity controls use <CustomPricingQuantityControl />

<CustomPricingQuantityControl
  id="number of pages"
  unitPrice={100}
  render={({ id, subtotal, quantity, handleChange }) => (
    <div className="space-y-1">
      <div className="flex items-center gap-2 justify-between">
        <Label className="text-sm font-medium" htmlFor={id}>
          Number of pages
        </Label>

        <SlidingNumber
          value={subtotal}
          className="text-xs font-medium text-muted-foreground tracking-tight"
        />
      </div>
      <Slider
        id={id}
        min={0}
        max={10}
        step={1}
        value={[quantity]}
        onValueChange={handleChange}
      />
    </div>
  )}
/>

for total controls use <CustomPricingTotalControl />

<CustomPricingTotalControl
  render={({ total }) => (
    <span className="tabular-nums font-semibold">${total}</span>
  )}
/>

Props

CustomPricingQuantityControl

PropTypeDefault
id
string
-
render
{ id: string, unitPrice: number, subtotal: number, quantity: number, updateQuantity: (id: string, quantity: number) => void, handleChange: (val: number[]) => void }
-
unitPrice?
number
-
defaultValue?
number
-

CustomPricingCheckedControl

PropTypeDefault
id
string
-
render
{ id: string, checked: boolean, price: number, toggleChecked: (id: string) => void }
-
type?
checkbox | radio
-
group?
string
-

CustomPricingTotalControl

PropTypeDefault
render
{ total: number }
-