All recipes
Default

Default confirm

DefaultConfirm is the component the SDK uses for every confirm step unless you pass components.Confirm. It renders a title, an optional description, an optional losses bullet list, a period-end notice derived from subscriptions[0].status.currentPeriod.end, and a danger-colored cancel button paired with a secondary keep button. Fork it when the confirm screen needs to look fundamentally different — see the Confirm — featured image recipe.

default component

Cancel your subscription?

You'll lose access to:
  • Saved projects
  • Team comments
  • Brand kit

Your access continues until June 14, 2026.

The full source

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

components/MyConfirm.tsxtsx
import { formatPeriodEnd } from '../../core/format'
import type { ConfirmStepProps } from '../../core/types'
import { cn } from '../../core/utils'
import { RichText } from '../rich-text'

export function DefaultConfirm({
  title,
  description,
  subscriptions,
  losses,
  lossesLabel,
  confirmLabel,
  goBackLabel,
  onConfirm,
  onGoBack,
  isProcessing,
  classNames,
}: ConfirmStepProps) {
  const hasLosses = Array.isArray(losses) && losses.length > 0
  const periodEnd = formatPeriodEnd(subscriptions)
  return (
    <div className={cn('ck-step ck-step-confirm', classNames?.root)}>
      <h2 className={cn('ck-step-title', classNames?.title)}>{title}</h2>
      {description && <RichText html={description} className={cn('ck-step-description', classNames?.description)} />}

      {hasLosses && (
        <div className={cn('ck-loss-block', classNames?.lossList)}>
          <div className={cn('ck-loss-label', classNames?.lossLabel)}>{lossesLabel ?? "You'll lose access to:"}</div>
          <ul className="ck-loss-list">
            {losses.map((item) => (
              <li key={item} className={cn('ck-loss-item', classNames?.lossItem)}>
                <span aria-hidden className={cn('ck-loss-bullet', classNames?.lossBullet)}>
                  ●
                </span>
                <span>{item}</span>
              </li>
            ))}
          </ul>
        </div>
      )}

      {periodEnd && (
        <p className={cn('ck-period-end', classNames?.periodEndNotice)}>Your access continues until {periodEnd}.</p>
      )}

      <button
        type="button"
        className={cn('ck-button ck-button-danger', classNames?.confirmButton)}
        onClick={onConfirm}
        disabled={isProcessing}
      >
        {isProcessing ? 'Processing...' : confirmLabel}
      </button>
      <button type="button" className={cn('ck-button-link', classNames?.goBackButton)} onClick={onGoBack}>
        {goBackLabel}
      </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 DefaultConfirm automatically on confirm steps.
// Copy the source into your own component to fork it from this baseline.

import { CancelFlow } from '@churnkey/react'

<CancelFlow
  steps={[
    { type: 'survey', reasons: [/* ... */] },
    {
      type: 'confirm',
      title: 'Cancel your subscription?',
      // Optional. Renders a styled bullet list between the description
      // and the period-end notice. Naming what the customer loses
      // concretely is more honest than a generic line in the description.
      losses: ['Saved projects', 'Team comments', 'Brand kit'],
      // Optional. Defaults to "You'll lose access to:".
      // lossesLabel: 'Cancelling means losing:',
      confirmLabel: 'Yes, cancel',
      goBackLabel: 'Keep my subscription',
    },
  ]}
  handleCancel={async () => myBilling.cancel()}
/>