Blog · · 11 min

The subscribe-and-save selector

Never pre-select the subscription. In the EU a default the consumer has to reject is not consent and the payment is reimbursable under Article 22 of Directive 2011/83; in the US the recurring charge needs express informed consent under ROSCA. The working shape is two explicit choices side by side, the one-time purchase visible and selectable, the saving written in currency, the frequency chosen before add-to-cart, and the cancellation terms in the card rather than behind a link.

By

The most expensive line in a subscribe-and-save component is defaultChecked. A subscription selected for the buyer is not a subscription they agreed to, and in Europe a payment obtained through a default the consumer had to reject is reimbursable. Everywhere else it is the fastest route to a chargeback, because the second charge arrives on a card whose owner remembers buying a jar of coffee once.

So the component starts with the one-time purchase selected, shows both options at the same size, writes the saving in money, asks for the frequency before the add rather than after, and puts the cancellation sentence inside the card where the choice is made.

Why is the pre-ticked subscription the expensive mistake?

Because it produces a charge you cannot keep.

Article 22 of Directive 2011/83/EU requires the trader to obtain express consent to any payment in addition to the remuneration agreed for the main contractual obligation, and states that where consent was inferred from a default option the consumer had to reject, the consumer is entitled to reimbursement. A pre-selected subscription is that default. Every recurring charge it produced is claimable.

The same article is why the order bump has to start unchecked. A subscription is the same legal shape with a longer tail: the bump takes one extra payment, the subscription takes one every month until someone notices.

In the United States the statute is ROSCA, 15 U.S.C. §8403, which for a negative option feature requires the material terms to be disclosed clearly and conspicuously before billing information is obtained, express informed consent to the charge, and a simple mechanism to stop the recurring charges. A pre-ticked box fails the second of those on its face.

What is requiredWhere it comes fromWhat the component has to render
Express consent to the recurring paymentEU 2011/83, Article 22The subscription option starts unselected, always
The duration and how to end it, before orderingEU 2011/83, Article 6(1)(o)A commitment sentence inside the card, not behind a link
Clear disclosure before billing details are takenROSCA, 15 U.S.C. §8403 (US)The same sentence, on the product page and again at checkout
A simple way to stop the chargesROSCA, 15 U.S.C. §8403 (US)A cancel path that is not email-only, described where you sell it

Read the second row carefully, because it is the one that is usually missing from otherwise careful implementations. The obligation is to give the terms before the order, not to have them somewhere on the site. A subscription whose duration and exit are only in the terms and conditions page has not disclosed them where the decision was made.

What do the two choices have to say?

Two cards, same width, same weight, same border treatment until one is selected. The moment one of them is visually quieter than the other, you are back to a default with extra steps.

The one-time card says the price and nothing else. The subscription card says four things, in this order:

  1. The recurring price, in money. $25.50 every month.
  2. What that saves, in money. You keep $4.50 on each delivery.
  3. The frequency control, revealed when the card is selected.
  4. The commitment sentence. What is charged today, what is charged next, when, and how to stop it.

Money before percentage, and if you only have room for one, money. Save 15% requires the buyer to know the base price, multiply, and convert the result into something they recognise. You keep $4.50 on each delivery is already the number they would have arrived at. The same reasoning drives the currency-first rule on the tier ladder.

![Two thick paper tickets on wet black slate under a hard raking light, one lying flat and letterpressed, the other standing on edge and casting a long shadow across the first, deckled edges catching the light.](/blog/deux-tickets.webp "Two tickets, the same size, one of them chosen. The moment one is printed smaller than the other, the choice has already been made for the buyer.")

What does the choice group look like in code?

type Plan = 'once' | 'subscription'

type Props = {
  priceCents: number
  discountBps: number // 1500 = 15%
  currency: string
  locale: string
  frequencies: number[] // in days, e.g. [30, 60, 90]
}

export function PurchaseMode({
  priceCents,
  discountBps,
  currency,
  locale,
  frequencies,
}: Props) {
  const [plan, setPlan] = useState<Plan>('once') // never 'subscription'
  const [everyDays, setEveryDays] = useState(frequencies[0])

  const line = subscriptionLine({
    priceCents,
    discountBps,
    everyDays,
    currency,
    locale,
  })

  return (
    <fieldset className="purchase-mode">
      <legend className="sr-only">How would you like to buy this?</legend>

      <label className="mode" data-selected={plan === 'once'}>
        <input
          type="radio"
          name="purchase-mode"
          value="once"
          checked={plan === 'once'}
          onChange={() => setPlan('once')}
        />
        <span className="mode__title">One-time purchase</span>
        <span className="mode__price">{money(priceCents, currency, locale)}</span>
      </label>

      <div className="mode" data-selected={plan === 'subscription'}>
        <label className="mode__head">
          <input
            type="radio"
            name="purchase-mode"
            value="subscription"
            checked={plan === 'subscription'}
            aria-describedby="subscription-terms"
            onChange={() => setPlan('subscription')}
          />
          <span className="mode__title">Subscribe and save</span>
          <span className="mode__price">{line.price}</span>
          <span className="mode__saving">{line.saving}</span>
        </label>

        {plan === 'subscription' && (
          <fieldset className="mode__frequency">
            <legend>Delivered</legend>
            {frequencies.map((days) => (
              <label key={days} className="chip">
                <input
                  type="radio"
                  name="frequency"
                  value={days}
                  checked={everyDays === days}
                  onChange={() => setEveryDays(days)}
                />
                {everyLabel(days)}
              </label>
            ))}
          </fieldset>
        )}

        <p id="subscription-terms" className="mode__terms">
          {line.commitment}
        </p>
      </div>
    </fieldset>
  )
}

Four decisions are encoded in that markup and each one is deliberate.

The subscription option is a <div> wrapping a <label>, not one big label, because a label may contain only one form control. Wrap the frequency radios inside the same label and the browser will route a tap on a frequency chip to the wrong input.

There is no defaultChecked and no prop that could supply one. The initial state is a literal in the component. If a store wants the subscription pre-selected, they have to edit the file, which is exactly the amount of friction that decision deserves.

The terms are tied to the input with aria-describedby. A screen reader announcing Subscribe and save, radio and stopping there has not disclosed anything. With the description attached, the recurring charge and the exit are read as part of the option.

The frequency lives inside the subscription card and only when it is selected. Outside the card it is a control with no owner; always visible it implies the subscription is already chosen. Rendered on selection, it is the second half of one decision.

The frequency values are radios, not a <select>. Three options behind a chevron on a phone means an operating system wheel picker covering the product, for a choice between three things that fit on one line. The general argument is in the variant selector.

How do you write the recurring price and the commitment?

One function, so the four strings can never disagree with each other.

// subscriptionLine.ts
export function money(cents: number, currency: string, locale: string): string {
  return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(
    cents / 100,
  )
}

export function everyLabel(days: number): string {
  if (days % 30 === 0) {
    const months = days / 30
    return months === 1 ? 'every month' : `every ${months} months`
  }
  if (days % 7 === 0) {
    const weeks = days / 7
    return weeks === 1 ? 'every week' : `every ${weeks} weeks`
  }
  return `every ${days} days`
}

export function subscriptionLine({
  priceCents,
  discountBps,
  everyDays,
  currency,
  locale,
}: {
  priceCents: number
  discountBps: number
  everyDays: number
  currency: string
  locale: string
}) {
  const recurringCents =
    priceCents - Math.round((priceCents * discountBps) / 10_000)
  const savedCents = priceCents - recurringCents
  const every = everyLabel(everyDays)
  const amount = money(recurringCents, currency, locale)

  return {
    price: `${amount} ${every}`,
    saving: `You keep ${money(savedCents, currency, locale)} on each delivery`,
    commitment:
      `${amount} charged today, then ${amount} ${every}. ` +
      `No minimum number of deliveries. ` +
      `Change the date, skip a delivery or cancel from your account, ` +
      `up to 48 hours before the next charge.`,
  }
}

Every clause in commitment is a factual claim about your billing system, so delete the ones that are not true rather than softening them. If there is a minimum term, the sentence says the minimum term. If cancellation goes through support rather than an account page, the sentence says support and gives the address. A commitment line that overstates how easy the exit is fails the same disclosure test as one that hides the exit, and it fails it with a paper trail.

everyLabel exists because every 30 days and every month are not the same promise, and the one you print is the one you have to bill. Pick the unit your billing system actually uses. A monthly subscription billed on the same calendar date is not a 30-day cycle, and a buyer who was told 30 days will eventually count.

Where does the frequency question go?

Before the add to cart, always.

A subscription added to the cart at a frequency the buyer has not chosen is a subscription with a default frequency, which is the same problem as a default plan wearing a smaller hat. It also produces the worst version of the checkout: the buyer reaches the payment step and discovers a delivery schedule they have to go back and change.

The order on the product page, from the top: gallery, name, price, variant axes, purchase mode, frequency if subscribed, add to cart, reassurance line. The button then carries the resolved choice, which is the argument made in full in add to cart button:

Subscribe · $25.50 every month

not Add to cart above an unresolved pair of cards.

At the checkout the terms appear a second time, immediately above the button, because that is where the payment is authorised and that is where the disclosure has to be readable. The rest of that page is covered in checkout page design, field by field.

What breaks a subscribe-and-save block

  • The one-time option rendered smaller, greyer, or as a link. That is a default in disguise, and it is read as one.
  • A saving shown only as a percentage. It is arithmetic handed to the buyer at the moment you wanted the decision to be effortless.
  • A first-order discount larger than the recurring one, without saying so. If month one is 30% off and month two is 15%, the second charge is a surprise. Print both figures in the commitment sentence.
  • A frequency <select> with eight values. Nobody wants a delivery every 11 days. Offer three.
  • A cancellation that is harder than the subscription. If it took one tap to start, an email thread to stop is a mechanism failure, not a retention tactic.
  • The terms in a tooltip. A disclosure that requires a hover does not exist on a phone.

We build uxgen, an MCP server that gives Claude 168 selling components and writes them into your store as HTML you own, from $19 a month. The legal shape above is not ours to charge for, and the consent rules are encoded in the components we publish in the commerce kit under MIT.

FAQ

Should subscribe and save be pre-selected by default?

No. Under Article 22 of Directive 2011/83/EU, consent inferred from a default option the consumer has to reject is not express consent, and the additional payment is reimbursable. Under ROSCA in the United States a negative option feature requires express informed consent to the recurring charge. Start with the one-time purchase selected and let the buyer choose the subscription deliberately.

How should the subscription saving be displayed?

In currency first, as an amount per delivery: You keep $4.50 on each delivery. A percentage asks the buyer to know the base price, multiply, and convert the result into a figure they recognise, which is three operations at the moment you wanted the decision to be simple. Show the percentage after the money if you show it at all.

Where do the cancellation terms have to appear?

Inside the card where the subscription is chosen, and again immediately above the payment button at checkout. Article 6(1)(o) of Directive 2011/83/EU requires the duration and the conditions for terminating a contract of indeterminate duration to be given before the order, and ROSCA requires the material terms to be disclosed before billing information is taken. A link to a policy page is not a disclosure at the point of decision.

When should the buyer choose the delivery frequency?

Before the add to cart, inside the subscription card, revealed when that card is selected. A frequency chosen after the add is a default frequency the buyer did not pick, and it surfaces at checkout as a schedule they have to go back and change. Offer three options as visible radio buttons rather than a dropdown.

uxgen is a service of UXGen AI, LLC — 131 Continental Dr, Suite 305, Newark, DE 19713, United States.

© 2026 UXGen AI, LLC. All rights reserved.