All recipes
Default

Default pause offer

DefaultPauseOffer renders any offer of type 'pause'. The resume date sits at the top in display type (We'll see you back on July 15), with month chips (1 through offer.months) below. Picking a chip recomputes the date. Accepting passes { months } to onAccept. Fork it to swap the chips for a date picker, lock the length to a single value, or add a 'we miss you' email opt-in below the controls.

default component

Take a break instead?

We'll keep your data exactly where it is.

We'll see you back on
June 19

The full source

Copy this file into your project. It's the same file the showcase preview above renders from.

components/MyPauseOffer.tsxtsx
import { useState } from 'react'
import { formatMonthDayLong } from '../../../core/format'
import type { OfferDecision, OfferStepProps } from '../../../core/types'
import { cn } from '../../../core/utils'
import { RichText } from '../../rich-text'

// Pause offer rendered with the resume date as the typographic anchor.
// Foregrounding the date — "We'll see you back on July 15" — frames the
// decision in terms of the outcome the customer cares about, with the
// month-length chips as a subordinate adjustment.
export function DefaultPauseOffer({
  title,
  description,
  offer,
  onAccept,
  onDecline,
  isProcessing,
  classNames,
}: OfferStepProps) {
  const o = offer as OfferDecision & { months: number }
  const max = Math.max(1, o.months)
  // Start at the shortest option — pre-selecting a longer pause feels
  // presumptuous, and the upgrade is one click away.
  const [months, setMonths] = useState<number>(1)

  const headline = title ?? offer.copy.headline
  const body = description ?? offer.copy.body

  const resume = new Date()
  resume.setMonth(resume.getMonth() + months)
  const resumeDate = formatMonthDayLong(resume)

  return (
    <div className={cn('ck-step ck-step-offer', classNames?.root)}>
      {headline && <h2 className={cn('ck-step-title', classNames?.title)}>{headline}</h2>}
      {body && <RichText html={body} className={cn('ck-step-description', classNames?.description)} />}

      <div className={cn('ck-offer-card ck-pause-card', classNames?.card)}>
        <div className="ck-pause-eyebrow">We&apos;ll see you back on</div>
        <div className="ck-pause-date">{resumeDate}</div>
        <div className={cn('ck-pause-chips', classNames?.pauseSlider)}>
          {Array.from({ length: max }, (_, i) => i + 1).map((m) => (
            <button
              key={m}
              type="button"
              onClick={() => setMonths(m)}
              className={cn('ck-pause-chip', m === months && 'ck-pause-chip--selected')}
              aria-pressed={m === months}
            >
              {m} {m === 1 ? 'month' : 'months'}
            </button>
          ))}
        </div>
      </div>
      <button
        type="button"
        className={cn('ck-button ck-button-primary', classNames?.acceptButton)}
        onClick={() => onAccept({ months })}
        disabled={isProcessing}
      >
        {isProcessing ? 'Processing...' : offer.copy.cta}
      </button>
      <button type="button" className={cn('ck-button-link', classNames?.declineButton)} onClick={onDecline}>
        {offer.copy.declineCta}
      </button>
    </div>
  )
}

Wire it into a flow

Drop the file into your codebase and reference it from the appropriate prop on <CancelFlow>.

CancelButton.tsxtsx
// The SDK uses DefaultPauseOffer when an offer of type 'pause' renders.
// Copy the source to fork from this baseline.

import { CancelFlow } from '@churnkey/react'
import { MyPauseOffer } from './components/MyPauseOffer'

<CancelFlow
  steps={[
    {
      type: 'survey',
      reasons: [{
        id: 'not-using',
        label: 'Not using it enough',
        offer: { type: 'pause', months: 3 },        // max selectable length
      }],
    },
    { type: 'confirm' },
  ]}
  components={{ PauseOffer: MyPauseOffer }}
  handlePause={async (offer) => myBilling.pause({ months: offer.result.months })}
  handleCancel={async () => myBilling.cancel()}
/>