All recipes
Recipe

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
May 19
Extended to
Jun 2

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()}
/>