Recovery App Onboarding Excellence
Build compassionate, effective onboarding experiences for recovery and wellness applications that serve vulnerable populations with dignity and practical utility.
When to Use
✅ USE this skill for:
-
First-time user onboarding flows
-
Feature discovery and app tours
-
Progressive disclosure design
-
Permission request timing and framing
-
Welcome screens and value propositions
-
Recovery program selection flows
-
Crisis resource integration
-
Privacy and anonymity communication
❌ DO NOT use for:
-
General mobile responsive design → use mobile-ux-optimizer
-
Marketing/conversion optimization → use seo-visibility-expert
-
Native iOS/Android development → use platform-specific skills
-
Database schema design → use supabase-admin
Core Principles
- Compassion First, Features Second
Recovery users are often in vulnerable states. Every onboarding decision must consider:
❌ ANTI-PATTERN: "Sign up to track your progress!" ✅ CORRECT: "You're taking a brave step. Let's set up a private space for your journey."
❌ ANTI-PATTERN: Requiring account creation before showing any value ✅ CORRECT: Let users explore core features anonymously, then offer accounts for persistence
- Value Before Commitment
Show meaningful value before asking for personal information:
WRONG ORDER:
- Create account
- Enter personal details
- Grant permissions
- Finally see the app
RIGHT ORDER:
-
Show immediate value (meeting finder, crisis resources)
-
Demonstrate app benefits
-
Offer optional account for saved data
-
Request permissions contextually
-
Non-Judgmental Language
Avoid shame-inducing or triggering language:
❌ "How many days since your last relapse?" ✅ "What's your current sobriety date?"
❌ "You failed to complete..." ✅ "No worries—pick up where you left off"
❌ "Are you an alcoholic or drug addict?" ✅ "Which recovery programs interest you?"
Mobile Onboarding UX (2025 Best Practices)
Progressive Disclosure
Break complex onboarding into digestible stages:
// ✅ GOOD: Staged onboarding with clear progress const ONBOARDING_STAGES = [ { id: 'welcome', required: false }, // Emotional connection { id: 'programs', required: false }, // Personalization { id: 'features', required: false }, // Value discovery { id: 'preferences', required: false }, // Customization { id: 'safety', required: false }, // Crisis setup ];
// Each stage should be: // - Skippable (never trap users) // - Under 60 seconds to complete // - Visually distinct with progress indicators // - Reversible (can go back)
Permission Priming (28% Higher Grant Rate)
Never request permissions out of context:
// ❌ BAD: Immediate system permission dialog useEffect(() => { requestLocationPermission(); }, []);
// ✅ GOOD: Contextual priming before system dialog function MeetingSearchPrimer() { return ( <div className="p-4 bg-blue-50 rounded-lg mb-4"> <MapPin className="text-blue-500 mb-2" /> <h3>Find Meetings Near You</h3> <p className="text-sm text-gray-600 mb-3"> To show meetings within walking distance, we need your location. Your location stays on your device. </p> <button onClick={handleEnableLocation} className="btn-primary"> Enable Location </button> <button onClick={handleSkip} className="btn-text"> Search by ZIP instead </button> </div> ); }
"Everboarding" - Beyond First Launch
Onboarding isn't a one-time event:
// Feature discovery on first use of each feature
function useFeatureOnboarding(featureId: string) {
const [hasSeenTooltip, setHasSeen] = useLocalStorage(onboard:${featureId}, false);
return { showTooltip: !hasSeenTooltip, dismissTooltip: () => setHasSeen(true), }; }
// Example: First time using gratitude journal function GratitudeJournal() { const { showTooltip, dismissTooltip } = useFeatureOnboarding('gratitude');
return ( <> {showTooltip && ( <FeatureTooltip title="Daily Gratitude" description="Write 3 things you're grateful for. Research shows this builds resilience." onDismiss={dismissTooltip} /> )} {/* Journal UI */} </> ); }
Skeleton Loaders, Never Spinners
Recovery users in crisis need immediate feedback:
// ❌ BAD: Spinner creates anxiety {isLoading && <Loader2 className="animate-spin" />}
// ✅ GOOD: Skeleton shows structure immediately {isLoading && <MeetingCardSkeleton count={5} />}
// Skeleton implementation function MeetingCardSkeleton() { return ( <div className="p-4 bg-leather-800 rounded-lg animate-pulse"> <div className="h-4 bg-leather-700 rounded w-3/4 mb-2" /> <div className="h-3 bg-leather-700 rounded w-1/2 mb-4" /> <div className="flex gap-2"> <div className="h-6 w-16 bg-leather-700 rounded" /> <div className="h-6 w-16 bg-leather-700 rounded" /> </div> </div> ); }
Recovery App Feature Categories
Essential Features (Showcase First)
-
Meeting Finder - Core utility, immediate value
-
Crisis Resources - Life-saving, always accessible
-
Safety Planning - Personal support network
Engagement Features (Second Priority)
-
Daily Check-ins - HALT awareness
-
Recovery Journal - Reflection and growth
-
Gratitude Practice - Positive reinforcement
Advanced Features (Discover Over Time)
-
Sobriety Counter - Milestone tracking
-
Community Forum - Peer support
-
Recovery Plan - AI-assisted guidance
-
Online Meetings - Virtual options
Feature Card Pattern
interface OnboardingFeature { id: string; icon: React.ComponentType; title: string; description: string; highlight: string; // Key benefit (e.g., "24/7 support available") color: string; // Brand color for visual distinction category: 'essential' | 'engagement' | 'advanced'; }
const FEATURES: OnboardingFeature[] = [ { id: 'meetings', icon: MapPin, title: 'Find Meetings Near You', description: 'Search thousands of AA, NA, CMA, SMART, and other recovery meetings.', highlight: '100,000+ meetings nationwide', color: 'bg-blue-500', category: 'essential', }, { id: 'crisis', icon: Phone, title: 'Crisis Resources', description: 'One-tap access to crisis hotlines and emergency support.', highlight: '24/7 support available', color: 'bg-red-500', category: 'essential', }, // ... more features ];
Crisis Resource Integration
Critical: Only 45% of mental health apps include crisis resources. This is non-negotiable for recovery apps.
Always-Visible Crisis Access
// Crisis resources should be accessible from EVERY screen function Navigation() { return ( <nav> {/* Normal nav items */} <CrisisButton className="fixed bottom-20 right-4" /> </nav> ); }
// Crisis button design function CrisisButton({ className }: { className?: string }) { return ( <Link href="/crisis" className={cn( 'flex items-center justify-center', 'w-14 h-14 rounded-full', 'bg-red-600 hover:bg-red-700', 'shadow-lg shadow-red-600/30', 'text-white', className )} aria-label="Crisis resources" > <Phone size={24} /> </Link> ); }
Onboarding Safety Plan Step
// Include safety planning in onboarding, but make it optional function SafetyPlanStep() { return ( <div className="space-y-4"> <h2>Your Safety Network</h2> <p className="text-gray-600"> Having support contacts ready can make all the difference. This is optional—you can set this up anytime. </p>
<EmergencyContactInput
label="Emergency Contact"
placeholder="Someone who can help in a crisis"
/>
<SupportPersonInput
label="Support Person"
placeholder="Sponsor, therapist, or trusted friend"
/>
<div className="flex gap-2 mt-6">
<button className="btn-secondary flex-1" onClick={handleSkip}>
Set Up Later
</button>
<button className="btn-primary flex-1" onClick={handleSave}>
Save Contacts
</button>
</div>
</div>
); }
Recovery Program Selection
Inclusive Multi-Selection
Support multiple recovery pathways without judgment:
const RECOVERY_PROGRAMS = [ { id: 'aa', name: 'Alcoholics Anonymous', short: 'AA', color: 'bg-blue-500' }, { id: 'na', name: 'Narcotics Anonymous', short: 'NA', color: 'bg-purple-500' }, { id: 'cma', name: 'Crystal Meth Anonymous', short: 'CMA', color: 'bg-pink-500' }, { id: 'smart', name: 'SMART Recovery', short: 'SMART', color: 'bg-green-500' }, { id: 'dharma', name: 'Recovery Dharma', short: 'Dharma', color: 'bg-amber-500' }, { id: 'ha', name: 'Heroin Anonymous', short: 'HA', color: 'bg-red-500' }, { id: 'oa', name: 'Overeaters Anonymous', short: 'OA', color: 'bg-teal-500' }, { id: 'other', name: 'Other/Multiple', short: 'Other', color: 'bg-gray-500' }, ];
// UI should allow multiple selections function ProgramSelection({ selected, onChange }) { return ( <div className="grid grid-cols-2 gap-3"> {RECOVERY_PROGRAMS.map((program) => ( <ProgramCard key={program.id} program={program} isSelected={selected.includes(program.id)} onToggle={() => toggleProgram(program.id)} /> ))} </div> ); }
Onboarding Flow Architecture
State Management
interface OnboardingState { currentStep: number; completedSteps: string[]; selectedPrograms: string[]; meetingPreferences: { formats: ('in-person' | 'online' | 'hybrid')[]; days: string[]; times: ('morning' | 'afternoon' | 'evening')[]; }; safetyContacts: { emergency?: string; support?: string; }; skippedSteps: string[]; }
// Persist across sessions function useOnboardingState() { return useLocalStorage<OnboardingState>('onboarding', defaultState); }
Step Navigation
function OnboardingWizard() { const [state, setState] = useOnboardingState(); const currentStep = STEPS[state.currentStep];
const goNext = () => { setState(prev => ({ ...prev, currentStep: Math.min(prev.currentStep + 1, STEPS.length - 1), completedSteps: [...prev.completedSteps, currentStep.id], })); };
const goBack = () => { setState(prev => ({ ...prev, currentStep: Math.max(prev.currentStep - 1, 0), })); };
const skip = () => { setState(prev => ({ ...prev, skippedSteps: [...prev.skippedSteps, currentStep.id], })); goNext(); };
return ( <div className="min-h-screen flex flex-col"> <ProgressIndicator current={state.currentStep} total={STEPS.length} completedSteps={state.completedSteps} />
<main className="flex-1 p-4">
<CurrentStepComponent {...currentStep} state={state} setState={setState} />
</main>
<footer className="p-4 border-t border-leather-700">
<div className="flex gap-3">
{state.currentStep > 0 && (
<button onClick={goBack} className="btn-secondary flex-1">
Back
</button>
)}
{currentStep.skippable && (
<button onClick={skip} className="btn-text">
Skip
</button>
)}
<button onClick={goNext} className="btn-primary flex-1">
{state.currentStep === STEPS.length - 1 ? 'Get Started' : 'Continue'}
</button>
</div>
</footer>
</div>
); }
Micro-Interactions
Entry Animations
// Staggered entrance for lists
function StaggeredList({ items, renderItem }) {
return (
<div className="space-y-3">
{items.map((item, index) => (
<div
key={item.id}
className="animate-fade-in-up"
style={{ animationDelay: ${index * 100}ms }}
>
{renderItem(item, index)}
</div>
))}
</div>
);
}
Carousel Navigation
// Swipe gesture support for mobile feature tours function useSwipeNavigation({ onNext, onPrev, threshold = 50 }) { const [touchStart, setTouchStart] = useState<number | null>(null); const [touchEnd, setTouchEnd] = useState<number | null>(null);
const onTouchStart = (e: React.TouchEvent) => { setTouchEnd(null); setTouchStart(e.targetTouches[0].clientX); };
const onTouchMove = (e: React.TouchEvent) => { setTouchEnd(e.targetTouches[0].clientX); };
const onTouchEnd = () => { if (!touchStart || !touchEnd) return; const distance = touchStart - touchEnd; if (distance > threshold) onNext(); if (distance < -threshold) onPrev(); };
return { onTouchStart, onTouchMove, onTouchEnd }; }
Accessibility Requirements
WCAG AA Compliance
-
Contrast: 4.5:1 for text < 18px, 3:1 for text >= 18px
-
Touch targets: Minimum 44x44px
-
Focus indicators: Visible outline on all interactive elements
-
Screen readers: Announce step changes with aria-live
Reduced Motion
@media (prefers-reduced-motion: reduce) { .animate-fade-in-up, .animate-slide-in { animation: none; opacity: 1; transform: none; } }
Testing Checklist
Functional Testing
-
All steps can be completed
-
All steps can be skipped
-
Back navigation works
-
Progress persists across sessions
-
Swipe gestures work on mobile
-
Keyboard navigation works
Accessibility Testing
-
Screen reader announces step changes
-
All interactive elements have labels
-
Focus order is logical
-
Color contrast meets WCAG AA
-
Touch targets are 44x44px minimum
Performance Testing
-
Initial load under 2 seconds
-
Step transitions under 300ms
-
No layout shifts during animations
-
Works offline after first load
Emotional Testing
-
Language is non-judgmental
-
Skip options are clear
-
No pressure tactics
-
Crisis resources visible
-
Privacy messaging clear
References
See /references/ for detailed guides:
-
competitor-analysis.md
-
I Am Sober, Nomo, Sober Grid patterns
-
wellness-patterns.md
-
Headspace, Calm, Daylio insights
-
crisis-integration.md
-
Emergency resource best practices