All recipes

Trial extension — before / after dates

Shows the trial transition — current end date, arrow, extended date — rather than just the days gained. The framing reads as a billing detail, which fits B2B trials tied to deployments, deliverable deadlines, or multi-product funnels.

custom recipe

Have more time on us

Your trial gets extended by 14 days.

Trial ends
Jun 10
Extended to
Jun 24

The full source

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

components/TrialExtensionDateArrow.tsxtsx
/**
 * Trial-extension offer that shows the *transition* — the current end date,
 * an arrow, and the new end date — instead of just the days gained. Reads
 * more "before / after" than "+N days." Use it when the actual dates
 * matter to your customer (billing pipelines, planned launches, deliverable
 * deadlines).
 *
 *   <CancelFlow
 *     ...
 *     components={{ TrialExtensionOffer: TrialExtensionDateArrow }}
 *   />
 *
 * Pass `currentEndsAt` as a `Date` via `step.data` or close over your own
 * billing data when you instantiate the override — the demo below assumes
 * the trial ends today.
 */
import type { OfferDecision, OfferStepProps } from '@churnkey/react/core'

export function TrialExtensionDateArrow({
  title,
  description,
  offer,
  onAccept,
  onDecline,
  isProcessing,
}: OfferStepProps) {
  const o = offer as OfferDecision & { days: number }
  const headline = title ?? offer.copy.headline
  const body = description ?? offer.copy.body

  const current = new Date()
  const extended = new Date()
  extended.setDate(extended.getDate() + o.days)

  return (
    <div className="ck-step ck-step-offer">
      {headline && <h2 className="ck-step-title">{headline}</h2>}
      {body && <p className="ck-step-description">{body}</p>}

      <div
        style={{
          display: 'grid',
          gridTemplateColumns: '1fr auto 1fr',
          alignItems: 'center',
          gap: 12,
          padding: '24px 20px',
          background: 'var(--ck-color-surface-muted)',
          borderRadius: 'var(--ck-radius-lg)',
          margin: '8px 0 20px',
        }}
      >
        <DateBlock label="Trial ends" date={current} dim />
        <ArrowGlyph />
        <DateBlock label="Extended to" date={extended} />
      </div>

      <button
        type="button"
        className="ck-button ck-button-primary"
        onClick={() => onAccept()}
        disabled={isProcessing}
      >
        {isProcessing ? 'Processing...' : offer.copy.cta}
      </button>
      <button type="button" className="ck-button-link" onClick={onDecline}>
        {offer.copy.declineCta}
      </button>
    </div>
  )
}

function DateBlock({ label, date, dim }: { label: string; date: Date; dim?: boolean }) {
  const month = date.toLocaleString('en-US', { month: 'short' })
  const day = date.getDate()
  return (
    <div style={{ textAlign: 'center' }}>
      <div
        style={{
          fontSize: 11,
          fontWeight: 600,
          letterSpacing: '0.08em',
          textTransform: 'uppercase',
          color: 'var(--ck-color-text-muted)',
          marginBottom: 4,
        }}
      >
        {label}
      </div>
      <div
        style={{
          fontFamily: 'var(--ck-font-display)',
          fontSize: 26,
          fontWeight: 500,
          color: dim ? 'var(--ck-color-text-muted)' : 'var(--ck-color-text)',
          letterSpacing: '-0.012em',
          fontVariantNumeric: 'tabular-nums',
        }}
      >
        {month} {day}
      </div>
    </div>
  )
}

function ArrowGlyph() {
  return (
    <svg
      width="22"
      height="14"
      viewBox="0 0 22 14"
      fill="none"
      stroke="var(--ck-color-text-muted)"
      strokeWidth="1.75"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden
    >
      <line x1="2" y1="7" x2="19" y2="7" />
      <polyline points="14 2 19 7 14 12" />
    </svg>
  )
}

Wire it into a flow

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

CancelButton.tsxtsx
import { CancelFlow } from '@churnkey/react'
import '@churnkey/react/styles.css'
import { TrialExtensionDateArrow } from './components/TrialExtensionDateArrow'

<CancelFlow
  steps={[
    {
      type: 'survey',
      reasons: [
        {
          id: 'not-ready',
          label: 'Not ready to commit yet',
          offer: { type: 'trial_extension', days: 14 },
        },
        /* ... */
      ],
    },
    { type: 'confirm' },
  ]}
  components={{ TrialExtensionOffer: TrialExtensionDateArrow }}
  handleTrialExtension={async (o) => myBilling.extendTrial({ days: o.days })}
  handleCancel={async () => myBilling.cancel()}
/>