All recipes
Recipe
Contact — support team card
The default ContactOffer is a single button that opens a mailto, tel, or chat URL. This recipe overrides it to show an avatar and an SLA reassurance block above the button, so 'talk to support' has the visual weight of a discount or pause offer. Useful when your support team is a real save channel.
custom recipe
Talk to us first?
Our team would love to help resolve any issues.
Support team
Average reply: under 2 hours
The full source
Copy this file into your project. It's the same file the showcase preview above renders from.
components/ContactWithSupportCard.tsxtsx
/**
* Contact offer with a support-team avatar block. Adds a face/SLA card
* above the action button — useful when "talk to support" is the offer
* and you want the accept button to compete visually with save offers.
*
* Wire it as a per-type override:
*
* <CancelFlow
* ...
* components={{ ContactOffer: ContactWithSupportCard }}
* />
*
* Edit the SUPPORT constant for your own team's info — name, SLA copy,
* avatar character/icon.
*/
import type { OfferStepProps } from '@churnkey/react/core'
const SUPPORT = {
name: 'Support team',
slaCopy: 'Average reply: under 2 hours',
avatar: '💬', // emoji or swap for an <img src=... /> with your team's photo
}
export function ContactWithSupportCard({
title,
description,
offer,
onAccept,
onDecline,
isProcessing,
}: OfferStepProps) {
const headline = title ?? offer.copy.headline
const body = description ?? offer.copy.body
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 className="ck-offer-card">
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 12,
padding: 16,
background: 'var(--ck-color-primary-soft)',
borderRadius: 'var(--ck-radius-md)',
marginBottom: 20,
}}
>
<div
style={{
width: 40,
height: 40,
borderRadius: 999,
background: 'var(--ck-color-surface)',
color: 'var(--ck-color-primary)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 20,
flexShrink: 0,
}}
aria-hidden
>
{SUPPORT.avatar}
</div>
<div>
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ck-color-text)' }}>{SUPPORT.name}</div>
<div style={{ fontSize: 12, color: 'var(--ck-color-text-secondary)', marginTop: 1 }}>{SUPPORT.slaCopy}</div>
</div>
</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>
</div>
)
}
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 { ContactWithSupportCard } from './components/ContactWithSupportCard'
<CancelFlow
steps={[
{
type: 'survey',
reasons: [{
id: 'need-help',
label: 'I need help with something',
offer: {
type: 'contact',
url: 'mailto:support@example.com',
label: 'Email support',
},
}],
},
{ type: 'confirm' },
]}
components={{ ContactOffer: ContactWithSupportCard }}
handleCancel={async () => myBilling.cancel()}
/>