claude-skill-app-onboarding-questionnaire

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

App Onboarding Questionnaire

应用新手引导问卷

Skill by ara.so — Daily 2026 Skills collection.
A Claude Code skill that analyses your existing app codebase and generates a complete, high-converting questionnaire-style onboarding flow — including all copy, screen designs, and production-ready code — modelled on proven patterns from top subscription apps.
本技能由ara.so开发 — 属于2026年度技能合集。
这是一款Claude Code技能,可分析你现有的应用代码库,参考顶级订阅应用的成熟转化模式,生成完整的高转化率问卷式新手引导流程,包含所有文案、页面设计以及可直接投产的代码。

What It Does

功能说明

When you run
/app-onboarding-questionnaire
in your project, the skill:
  1. Analyses your codebase — reads your app's source, manifest/plist files, and existing screens to understand your app's purpose, target users, and required permissions
  2. Defines the user transformation — constructs a before/after narrative that drives the onboarding story
  3. Designs a screen-by-screen blueprint — using a 14-screen psychological conversion framework
  4. Drafts all copy — headlines, questions, answer options, CTAs, testimonials, social proof
  5. Builds the screens — in your app's native framework (SwiftUI, React Native, Flutter, Jetpack Compose, etc.)
当你在项目中运行
/app-onboarding-questionnaire
命令时,该技能会执行以下操作:
  1. 分析你的代码库 — 读取应用源码、manifest/plist配置文件以及现有页面,理解应用的定位、目标用户和所需权限
  2. 定义用户价值转变路径 — 搭建「使用前/使用后」的叙事框架,支撑新手引导的故事逻辑
  3. 设计逐页的实现蓝图 — 采用14屏心理学转化框架
  4. 撰写所有文案 — 包含标题、问题、选项、CTA按钮、用户评价、社交证明等内容
  5. 生成页面代码 — 支持你应用所用的原生框架(SwiftUI、React Native、Flutter、Jetpack Compose等)

Installation

安装

Option 1: Global skills directory

方案1:全局技能目录安装

bash
cd ~/.claude/skills
git clone https://github.com/adamlyttleapps/claude-skill-app-onboarding-questionnaire.git app-onboarding-questionnaire
bash
cd ~/.claude/skills
git clone https://github.com/adamlyttleapps/claude-skill-app-onboarding-questionnaire.git app-onboarding-questionnaire

Option 2: Project-level dependency

方案2:项目级依赖安装

Add to your project's
.claude/settings.json
:
json
{
  "skills": [
    "github:adamlyttleapps/claude-skill-app-onboarding-questionnaire"
  ]
}
添加到项目的
.claude/settings.json
文件中:
json
{
  "skills": [
    "github:adamlyttleapps/claude-skill-app-onboarding-questionnaire"
  ]
}

Usage

使用方法

Navigate to your app project directory and run:
/app-onboarding-questionnaire
The skill is interactive — it asks clarifying questions and builds incrementally. Progress is saved to Claude Code's memory system so you can resume across sessions.
进入你的应用项目目录,运行以下命令:
/app-onboarding-questionnaire
该技能为交互式工具,会询问澄清问题并逐步构建引导流程。进度会保存到Claude Code的内存系统中,你可以跨会话恢复工作。

The 14-Screen Framework

14屏转化框架

#ScreenConversion Purpose
1WelcomeHook — show the end state, create desire
2Goal Question"What are you trying to achieve?" — psychological investment
3Pain Points"What prevents you?" — builds empathy
4Social ProofPersona-matched testimonials
5Tinder CardsSwipe agree/disagree on pain statements
6Personalised SolutionMirror pains back with app solution stats
7Comparison TableLife with vs without the app (optional)
8PreferencesFunctional personalisation for the demo
9Permission PrimingBenefit-framed pre-sell before system dialogs
10Processing Moment"Building X just for you..." anticipation builder
11App DemoUser actually uses the core app mechanic
12Value DeliveryTangible output + share/viral moment
13Account GateOptional sign-in to save what they created
14PaywallHard paywall with trial, social proof, pricing
Not every app needs every screen — the skill adapts based on your app's complexity and type.
#页面名称转化目标
1欢迎页吸引用户 — 展示最终价值,激发使用意愿
2目标问卷「你想要实现什么目标?」 — 提升用户心理投入度
3痛点调研「什么问题阻碍了你?」 — 建立情感共鸣
4社交证明匹配用户画像的真实评价
5Tinder滑动卡片让用户滑动选择同意/不同意痛点描述
6个性化解决方案结合应用解决方案的数据回应用户痛点
7对比表格使用应用vs不使用应用的生活对比(可选)
8偏好设置为演示环节做功能个性化配置
9权限预申请在系统弹窗出现前,基于价值说明提前铺垫权限申请
10处理等待页「正在为你专属定制X...」 提升用户期待感
11应用演示让用户实际体验应用的核心功能
12价值传递产出可感知的结果 + 分享/传播节点
13账号门槛可选的登录环节,用于保存用户生成的内容
14付费墙硬付费墙,包含试用、社交证明、定价信息
不是所有应用都需要全部页面,该技能会根据你的应用复杂度和类型自适应调整。

Key Differentiators

核心优势

App Demo Screen

应用演示页面

Instead of a tour, users do something — pick recipes, complete an exercise, categorise a transaction — and receive a tangible result. This is Screen 11 and is the highest-impact screen for conversion.
不同于普通的功能导览,用户会实际完成操作 — 挑选菜谱、完成练习、分类交易等,并获得可感知的结果。这是第11页,也是对转化率影响最高的页面。

Permission Priming (Screen 9)

权限预申请(第9页)

The skill auto-detects required permissions from your codebase:
  • iOS: reads
    Info.plist
    for
    NSCameraUsageDescription
    ,
    NSLocationWhenInUseUsageDescription
    , etc.
  • Android: reads
    AndroidManifest.xml
    for
    uses-permission
    entries
  • React Native / Flutter: checks both
For each permission found, it generates a benefit-framed priming screen shown before the system dialog. This converts at 70–80%+ vs ~40% for cold prompts.
该技能会自动从你的代码库中检测所需权限:
  • iOS:读取
    Info.plist
    中的
    NSCameraUsageDescription
    NSLocationWhenInUseUsageDescription
    等配置
  • Android:读取
    AndroidManifest.xml
    中的
    uses-permission
    条目
  • React Native / Flutter:同时检查上述两个位置
针对每一个检测到的权限,它会生成一个基于价值说明的预申请页面,在系统弹窗之前展示。这种模式的授权转化率可达70-80%以上,而冷弹窗的转化率仅约40%。

Viral / Share Moment (Screen 12)

传播/分享节点(第12页)

The demo output is designed to be shareable — a meal plan, a workout, a savings projection. This is where organic growth originates.
演示环节的产出被设计为可分享的内容 — 饮食计划、健身方案、储蓄预测等,这是自然增长的来源。

Code Examples

代码示例

SwiftUI — Goal Question Screen

SwiftUI — 目标问卷页面

swift
// Generated by /app-onboarding-questionnaire
import SwiftUI

struct GoalQuestionView: View {
    @EnvironmentObject var onboardingState: OnboardingState
    
    let goals = [
        OnboardingOption(id: "lose_weight", emoji: "⚖️", title: "Lose weight", subtitle: "Reach a healthier body"),
        OnboardingOption(id: "build_muscle", emoji: "💪", title: "Build muscle", subtitle: "Get stronger and leaner"),
        OnboardingOption(id: "eat_healthier", emoji: "🥗", title: "Eat healthier", subtitle: "Improve my nutrition"),
        OnboardingOption(id: "save_time", emoji: "⏱️", title: "Save time cooking", subtitle: "Quick, easy meals")
    ]
    
    var body: some View {
        VStack(spacing: 24) {
            OnboardingHeader(
                title: "What's your main goal?",
                subtitle: "We'll personalise everything around this"
            )
            
            VStack(spacing: 12) {
                ForEach(goals) { goal in
                    OnboardingOptionRow(
                        option: goal,
                        isSelected: onboardingState.selectedGoal == goal.id
                    ) {
                        onboardingState.selectedGoal = goal.id
                    }
                }
            }
            
            Spacer()
            
            PrimaryButton(title: "Continue", isEnabled: onboardingState.selectedGoal != nil) {
                onboardingState.advance()
            }
        }
        .padding()
    }
}
swift
// Generated by /app-onboarding-questionnaire
import SwiftUI

struct GoalQuestionView: View {
    @EnvironmentObject var onboardingState: OnboardingState
    
    let goals = [
        OnboardingOption(id: "lose_weight", emoji: "⚖️", title: "Lose weight", subtitle: "Reach a healthier body"),
        OnboardingOption(id: "build_muscle", emoji: "💪", title: "Build muscle", subtitle: "Get stronger and leaner"),
        OnboardingOption(id: "eat_healthier", emoji: "🥗", title: "Eat healthier", subtitle: "Improve my nutrition"),
        OnboardingOption(id: "save_time", emoji: "⏱️", title: "Save time cooking", subtitle: "Quick, easy meals")
    ]
    
    var body: some View {
        VStack(spacing: 24) {
            OnboardingHeader(
                title: "What's your main goal?",
                subtitle: "We'll personalise everything around this"
            )
            
            VStack(spacing: 12) {
                ForEach(goals) { goal in
                    OnboardingOptionRow(
                        option: goal,
                        isSelected: onboardingState.selectedGoal == goal.id
                    ) {
                        onboardingState.selectedGoal = goal.id
                    }
                }
            }
            
            Spacer()
            
            PrimaryButton(title: "Continue", isEnabled: onboardingState.selectedGoal != nil) {
                onboardingState.advance()
            }
        }
        .padding()
    }
}

React Native — Tinder Swipe Cards Screen

React Native — Tinder滑动卡片页面

tsx
// Generated by /app-onboarding-questionnaire
import React, { useState } from 'react';
import { View, Text, StyleSheet, Animated, PanResponder } from 'react-native';
import { useOnboarding } from '../context/OnboardingContext';

const PAIN_STATEMENTS = [
  "I don't know what to cook each week",
  "I end up wasting food I've bought",
  "Healthy eating feels too complicated",
  "I spend too long deciding what to make",
];

export function TinderCardsScreen() {
  const { addAgreedPain, advance } = useOnboarding();
  const [currentIndex, setCurrentIndex] = useState(0);
  const position = new Animated.ValueXY();

  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: (_, gesture) => {
      position.setValue({ x: gesture.dx, y: gesture.dy });
    },
    onPanResponderRelease: (_, gesture) => {
      if (gesture.dx > 120) {
        swipe('agree');
      } else if (gesture.dx < -120) {
        swipe('disagree');
      } else {
        Animated.spring(position, { toValue: { x: 0, y: 0 }, useNativeDriver: true }).start();
      }
    },
  });

  const swipe = (direction: 'agree' | 'disagree') => {
    if (direction === 'agree') {
      addAgreedPain(PAIN_STATEMENTS[currentIndex]);
    }
    Animated.timing(position, {
      toValue: { x: direction === 'agree' ? 500 : -500, y: 0 },
      duration: 250,
      useNativeDriver: true,
    }).start(() => {
      position.setValue({ x: 0, y: 0 });
      if (currentIndex + 1 >= PAIN_STATEMENTS.length) {
        advance();
      } else {
        setCurrentIndex(i => i + 1);
      }
    });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Do these sound familiar?</Text>
      <Text style={styles.subtitle}>Swipe right if yes, left if no</Text>
      <Animated.View
        style={[styles.card, { transform: position.getTranslateTransform() }]}
        {...panResponder.panHandlers}
      >
        <Text style={styles.cardText}>{PAIN_STATEMENTS[currentIndex]}</Text>
      </Animated.View>
    </View>
  );
}
tsx
// Generated by /app-onboarding-questionnaire
import React, { useState } from 'react';
import { View, Text, StyleSheet, Animated, PanResponder } from 'react-native';
import { useOnboarding } from '../context/OnboardingContext';

const PAIN_STATEMENTS = [
  "I don't know what to cook each week",
  "I end up wasting food I've bought",
  "Healthy eating feels too complicated",
  "I spend too long deciding what to make",
];

export function TinderCardsScreen() {
  const { addAgreedPain, advance } = useOnboarding();
  const [currentIndex, setCurrentIndex] = useState(0);
  const position = new Animated.ValueXY();

  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: (_, gesture) => {
      position.setValue({ x: gesture.dx, y: gesture.dy });
    },
    onPanResponderRelease: (_, gesture) => {
      if (gesture.dx > 120) {
        swipe('agree');
      } else if (gesture.dx < -120) {
        swipe('disagree');
      } else {
        Animated.spring(position, { toValue: { x: 0, y: 0 }, useNativeDriver: true }).start();
      }
    },
  });

  const swipe = (direction: 'agree' | 'disagree') => {
    if (direction === 'agree') {
      addAgreedPain(PAIN_STATEMENTS[currentIndex]);
    }
    Animated.timing(position, {
      toValue: { x: direction === 'agree' ? 500 : -500, y: 0 },
      duration: 250,
      useNativeDriver: true,
    }).start(() => {
      position.setValue({ x: 0, y: 0 });
      if (currentIndex + 1 >= PAIN_STATEMENTS.length) {
        advance();
      } else {
        setCurrentIndex(i => i + 1);
      }
    });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Do these sound familiar?</Text>
      <Text style={styles.subtitle}>Swipe right if yes, left if no</Text>
      <Animated.View
        style={[styles.card, { transform: position.getTranslateTransform() }]}
        {...panResponder.panHandlers}
      >
        <Text style={styles.cardText}>{PAIN_STATEMENTS[currentIndex]}</Text>
      </Animated.View>
    </View>
  );
}

Flutter — Processing / Loading Screen

Flutter — 处理/加载页面

dart
// Generated by /app-onboarding-questionnaire
import 'package:flutter/material.dart';

class ProcessingScreen extends StatefulWidget {
  final VoidCallback onComplete;
  const ProcessingScreen({required this.onComplete, super.key});

  
  State<ProcessingScreen> createState() => _ProcessingScreenState();
}

class _ProcessingScreenState extends State<ProcessingScreen>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  int _stepIndex = 0;

  final List<String> _steps = [
    'Analysing your goals...',
    'Matching your preferences...',
    'Crafting your personal plan...',
    'Almost ready!',
  ];

  
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(seconds: 4))
      ..addListener(() {
        final newIndex = (_controller.value * _steps.length).floor().clamp(0, _steps.length - 1);
        if (newIndex != _stepIndex) {
          setState(() => _stepIndex = newIndex);
        }
      })
      ..forward().whenComplete(widget.onComplete);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CircularProgressIndicator(value: _controller.value),
            const SizedBox(height: 32),
            AnimatedSwitcher(
              duration: const Duration(milliseconds: 400),
              child: Text(
                _steps[_stepIndex],
                key: ValueKey(_stepIndex),
                style: Theme.of(context).textTheme.titleMedium,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
dart
// Generated by /app-onboarding-questionnaire
import 'package:flutter/material.dart';

class ProcessingScreen extends StatefulWidget {
  final VoidCallback onComplete;
  const ProcessingScreen({required this.onComplete, super.key});

  
  State<ProcessingScreen> createState() => _ProcessingScreenState;
}

class _ProcessingScreenState extends State<ProcessingScreen>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  int _stepIndex = 0;

  final List<String> _steps = [
    'Analysing your goals...',
    'Matching your preferences...',
    'Crafting your personal plan...',
    'Almost ready!',
  ];

  
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(seconds: 4))
      ..addListener(() {
        final newIndex = (_controller.value * _steps.length).floor().clamp(0, _steps.length - 1);
        if (newIndex != _stepIndex) {
          setState(() => _stepIndex = newIndex);
        }
      })
      ..forward().whenComplete(widget.onComplete);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CircularProgressIndicator(value: _controller.value),
            const SizedBox(height: 32),
            AnimatedSwitcher(
              duration: const Duration(milliseconds: 400),
              child: Text(
                _steps[_stepIndex],
                key: ValueKey(_stepIndex),
                style: Theme.of(context).textTheme.titleMedium,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

SwiftUI — Permission Priming Screen (auto-generated from Info.plist)

SwiftUI — 权限预申请页面(从Info.plist自动生成)

swift
// Generated from detected NSCameraUsageDescription in Info.plist
struct CameraPermissionPrimingView: View {
    @EnvironmentObject var onboardingState: OnboardingState
    
    var body: some View {
        VStack(spacing: 32) {
            Image(systemName: "camera.fill")
                .font(.system(size: 64))
                .foregroundColor(.accentColor)
            
            VStack(spacing: 12) {
                Text("Scan ingredients instantly")
                    .font(.title2.bold())
                Text("Point your camera at any ingredient or barcode and we'll find matching recipes in seconds — no typing needed.")
                    .multilineTextAlignment(.center)
                    .foregroundColor(.secondary)
            }
            
            VStack(spacing: 8) {
                Label("Identify 10,000+ ingredients", systemImage: "checkmark.circle.fill")
                Label("Scan barcodes for nutrition info", systemImage: "checkmark.circle.fill")
                Label("Works offline for pantry items", systemImage: "checkmark.circle.fill")
            }
            .foregroundColor(.primary)
            
            Spacer()
            
            PrimaryButton(title: "Enable Camera Access") {
                // System prompt shown AFTER this priming screen
                onboardingState.requestCameraPermission()
            }
            
            Button("Not now") {
                onboardingState.skipPermission(.camera)
            }
            .foregroundColor(.secondary)
        }
        .padding(32)
    }
}
swift
// Generated from detected NSCameraUsageDescription in Info.plist
struct CameraPermissionPrimingView: View {
    @EnvironmentObject var onboardingState: OnboardingState
    
    var body: some View {
        VStack(spacing: 32) {
            Image(systemName: "camera.fill")
                .font(.system(size: 64))
                .foregroundColor(.accentColor)
            
            VStack(spacing: 12) {
                Text("Scan ingredients instantly")
                    .font(.title2.bold())
                Text("Point your camera at any ingredient or barcode and we'll find matching recipes in seconds — no typing needed.")
                    .multilineTextAlignment(.center)
                    .foregroundColor(.secondary)
            }
            
            VStack(spacing: 8) {
                Label("Identify 10,000+ ingredients", systemImage: "checkmark.circle.fill")
                Label("Scan barcodes for nutrition info", systemImage: "checkmark.circle.fill")
                Label("Works offline for pantry items", systemImage: "checkmark.circle.fill")
            }
            .foregroundColor(.primary)
            
            Spacer()
            
            PrimaryButton(title: "Enable Camera Access") {
                // System prompt shown AFTER this priming screen
                onboardingState.requestCameraPermission()
            }
            
            Button("Not now") {
                onboardingState.skipPermission(.camera)
            }
            .foregroundColor(.secondary)
        }
        .padding(32)
    }
}

Onboarding State Management Pattern

新手引导状态管理模式

The skill generates a central state object to track progress across all screens:
swift
// SwiftUI example
class OnboardingState: ObservableObject {
    @Published var currentScreen: OnboardingScreen = .welcome
    @Published var selectedGoal: String?
    @Published var agreedPains: [String] = []
    @Published var preferences: UserPreferences = .default
    @Published var demoResult: DemoOutput?
    
    // Persisted to UserDefaults so onboarding survives app restarts
    func advance() {
        let next = currentScreen.next(given: self)
        withAnimation { currentScreen = next }
        save()
    }
    
    func save() {
        // Skill generates serialisation code appropriate to your stack
    }
}
该技能会生成一个中心化的状态对象,用于跟踪所有页面的进度:
swift
// SwiftUI example
class OnboardingState: ObservableObject {
    @Published var currentScreen: OnboardingScreen = .welcome
    @Published var selectedGoal: String?
    @Published var agreedPains: [String] = []
    @Published var preferences: UserPreferences = .default
    @Published var demoResult: DemoOutput?
    
    // Persisted to UserDefaults so onboarding survives app restarts
    func advance() {
        let next = currentScreen.next(given: self)
        withAnimation { currentScreen = next }
        save()
    }
    
    func save() {
        // Skill generates serialisation code appropriate to your stack
    }
}

Configuration Options

配置选项

When you run
/app-onboarding-questionnaire
, the skill asks about:
OptionDescription
App typeSubscription, freemium, one-time purchase
Core loopThe single thing users do in your app
Target audienceWho the app is for (used for copy tone)
Paywall timingWhether to show paywall before or after account creation
Screens to skipComparison table, account gate, etc.
Brand coloursUsed in generated SwiftUI/CSS/Flutter theme code
当你运行
/app-onboarding-questionnaire
时,该技能会询问以下配置项:
配置项说明
应用类型订阅制、免费增值、一次性付费
核心使用流程用户在应用中的核心操作
目标受众应用的目标用户群体(用于调整文案风格)
付费墙时机在账号创建之前还是之后展示付费墙
要跳过的页面对比表格、账号门槛等
品牌配色用于生成SwiftUI/CSS/Flutter主题代码

Resuming a Session

会话恢复

Progress is saved to Claude Code's memory. To resume:
/app-onboarding-questionnaire resume
To restart from a specific screen:
/app-onboarding-questionnaire --from=paywall
进度会保存到Claude Code的内存中,恢复会话请运行:
/app-onboarding-questionnaire resume
要从指定页面重新开始,请运行:
/app-onboarding-questionnaire --from=paywall

Troubleshooting

问题排查

Skill doesn't detect my permissions correctly
  • iOS: ensure
    Info.plist
    is at the project root or
    <AppName>/Info.plist
  • Android: ensure
    AndroidManifest.xml
    is at
    app/src/main/AndroidManifest.xml
  • React Native: the skill checks both locations automatically
Generated code uses wrong framework
  • The skill infers your framework from file extensions (
    .swift
    ,
    .tsx
    ,
    .dart
    ,
    .kt
    )
  • If detection fails, specify explicitly:
    /app-onboarding-questionnaire --framework=swiftui
Paywall screen doesn't match my payment provider
  • The skill generates a UI shell; wire up your payment provider (RevenueCat, StoreKit 2, stripe-react-native) separately
  • RevenueCat is the recommended integration — the skill generates compatible purchase call sites
Want fewer screens for a simpler app
  • The skill asks about complexity during setup
  • You can also specify:
    /app-onboarding-questionnaire --screens=welcome,goal,processing,paywall
技能无法正确检测我的权限
  • iOS:请确保
    Info.plist
    位于项目根目录或
    <AppName>/Info.plist
    路径下
  • Android:请确保
    AndroidManifest.xml
    位于
    app/src/main/AndroidManifest.xml
    路径下
  • React Native:技能会自动检查上述两个位置
生成的代码使用了错误的框架
  • 技能会根据文件扩展名(
    .swift
    .tsx
    .dart
    .kt
    )推断你使用的框架
  • 如果检测失败,可以显式指定:
    /app-onboarding-questionnaire --framework=swiftui
付费墙页面与你的支付服务商不匹配
  • 技能会生成UI外壳,你需要单独接入支付服务商(RevenueCat、StoreKit 2、stripe-react-native等)
  • 推荐使用RevenueCat集成,技能会生成兼容的购买调用代码
希望为简单应用减少页面数量
  • 技能会在初始化时询问应用复杂度
  • 你也可以显式指定:
    /app-onboarding-questionnaire --screens=welcome,goal,processing,paywall

Reference

参考来源

The framework is based on analysis of the Mob recipe app's 19-screen onboarding flow, widely regarded as one of the highest-converting onboarding experiences on the App Store, combined with patterns from Noom, Headspace, and Duolingo.
该框架基于对Mob菜谱应用19屏新手引导流程的分析(该流程被普遍认为是App Store上转化率最高的新手引导体验之一),同时结合了Noom、Headspace和Duolingo的成熟模式。