recovery-app-onboarding について
リカバリーやウェルネスアプリのチーム向けに、ユーザーの状況を尊重し、価値を先に示し、非審判的な言葉を使うオンボーディング設計スキルです。段階的な案内、危機支援リソース、プライバシー説明、アクセシビリティ、テストを扱います。
主な機能
- 01思いやりを重視した導入設計
- 02段階的な機能紹介
- 03状況に応じた権限リクエスト
- 04危機支援と安全計画
- 05アクセシビリティとテスト項目
活用シーン
- 01初回利用フローや機能ツアーの設計
- 02権限要求とプライバシー説明の計画
- 03回復プログラム選択と安全計画の導入
- 04オンボーディングのアクセシビリティ確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill recovery-app-onboardingインストール後、Claude Code で/recovery-app-onboardingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/example_asset.txtreferences/api_reference.mdscripts/example.py詳細
回復アプリの優れたオンボーディング
尊厳と実用性をもって、支援を必要とする人々に寄り添う、思いやりのある効果的な回復・ウェルネスアプリのオンボーディング体験を構築します。
使用する場面
✅ このスキルを使用する場面:
- 初回ユーザー向けのオンボーディングフロー
- 機能の紹介やアプリツアー
- 段階的開示の設計
- 権限リクエストのタイミングと伝え方
- ウェルカム画面と価値提案
- 回復プログラムの選択フロー
- 危機支援リソースの統合
- プライバシーと匿名性に関する説明
❌ 以下の場面では使用しないでください:
- 一般的なモバイルのレスポンシブデザイン →
mobile-ux-optimizerを使用 - マーケティング/コンバージョンの最適化 →
seo-visibility-expertを使用 - ネイティブ iOS/Android 開発 → プラットフォーム固有のスキルを使用
- データベーススキーマの設計 →
supabase-adminを使用
基本原則
1. 機能より思いやりを優先
回復に取り組むユーザーは、心身が不安定な状態にあることが少なくありません。オンボーディングに関するすべての判断で、以下を考慮する必要があります。
❌ 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
2. まず価値を示し、その後に情報を求める
個人情報を求める前に、意義のある価値を示します。
WRONG ORDER:
1. Create account
2. Enter personal details
3. Grant permissions
4. Finally see the app
RIGHT ORDER:
1. Show immediate value (meeting finder, crisis resources)
2. Demonstrate app benefits
3. Offer optional account for saved data
4. Request permissions contextually
3. 否定的な判断を含まない言葉遣い
羞恥心を与えたり、つらい記憶を呼び起こしたりする言葉は避けます。
❌ "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?"
モバイルオンボーディング UX(2025 年のベストプラクティス)
段階的開示
複雑なオンボーディングを、理解しやすい段階に分けます。
// ✅ 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)
権限リクエストの事前説明(許可率が 28% 向上)
状況を説明せずに権限をリクエストしないでください。
// ❌ 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>
);
}
「エバーボーディング」— 初回起動後も継続
オンボーディングは一度限りのイベントではありません。
// 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 */}
</>
);
}
スピナーではなくスケルトンローダーを使用
危機的な状況にある回復中のユーザーには、すぐに反応を返す必要があります。
// ❌ 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>
);
}
回復アプリの機能カテゴリー
必須機能(最初に紹介)
- ミーティング検索 — 中核的な実用機能で、すぐに価値を提供
- 危機支援リソース — 命を守るためのリソースで、常にアクセス可能
- 安全計画 — 個人的なサポートネットワーク
エンゲージメント機能(優先度 2)
- 毎日のチェックイン — HALT の意識づけ
- 回復ジャーナル — 振り返りと成長
- 感謝の実践 — 前向きな強化
高度な機能(時間をかけて紹介)
- 断酒カウンター — 節目の記録
- コミュニティフォーラム — 仲間からのサポート
- 回復プラン — AI の支援によるガイダンス
- オンラインミーティング — バーチャルな選択肢
機能カードのパターン
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
];
危機支援リソースの統合
重要:メンタルヘルスアプリで危機支援リソースを含むものは、わずか 45% です。回復アプリでは、これは必須です。
危機支援へのアクセスを常に表示
// 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>
);
}
オンボーディングの安全計画ステップ
// 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>
);
}
回復プログラムの選択
包括的な複数選択
判断を加えずに、複数の回復の道筋を選べるようにします。
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>
);
}
オンボーディングフローのアーキテクチャ
状態管理
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);
}
ステップの移動
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>
);
}
マイクロインタラクション
表示アニメーション
// 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>
);
}
カルーセルのナビゲーション
// 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 };
}
アクセシビリティ要件
WCAG AA 準拠
- コントラスト:18px 未満のテキストは 4.5:1、18px 以上のテキストは 3:1
- タッチターゲット:最小 44x44px
- フォーカスインジケーター:すべてのインタラクティブ要素に、視認可能なアウトラインを表示
- スクリーンリーダー:
aria-liveを使用してステップの変更を読み上げる
モーションの軽減
@media (prefers-reduced-motion: reduce) {
.animate-fade-in-up,
.animate-slide-in {
animation: none;
opacity: 1;
transform: none;
}
}
テストチェックリスト
機能テスト
- すべてのステップを完了できる
- すべてのステップをスキップできる
- 戻る操作が機能する
- セッションをまたいで進行状況が保持される
- モバイルでスワイプジェスチャーが機能する
- キーボード操作が機能する
アクセシビリティテスト
- スクリーンリーダーがステップの変更を読み上げる
- すべてのインタラクティブ要素にラベルがある
- フォーカスの順序が論理的である
- 色のコントラストが WCAG AA に準拠している
- タッチターゲットが最低 44x44px である
パフォーマンステスト
- 初回読み込みが 2 秒未満
- ステップの切り替えが 300ms 未満
- アニメーション中にレイアウトシフトが発生しない
- 初回読み込み後はオフラインで動作する
感情面のテスト
- 否定的な判断を含まない言葉遣いである
- スキップの選択肢が明確である
- 圧力をかける手法がない
- 危機支援リソースが表示される
- プライバシーに関する説明が明確である
参考資料
詳しいガイドについては、/references/ をご覧ください。
competitor-analysis.md- I Am Sober、Nomo、Sober Grid のパターンwellness-patterns.md- Headspace、Calm、Daylio から得られた知見crisis-integration.md- 緊急時リソースに関するベストプラクティス
コメント
Markdown 対応