--- name: claude-skill-app-onboarding-questionnaire description: Claude Code skill that designs and builds high-converting questionnaire-style app onboarding flows modelled on proven conversion patterns from top subscription apps like Noom, Headspace, and Duolingo. triggers: - "build onboarding flow for my app" - "create app onboarding questionnaire" - "design subscription app onboarding" - "add onboarding screens to my app" - "build high converting onboarding" - "create paywall onboarding flow" - "generate onboarding questionnaire screens" - "design app welcome flow with questions" --- # App Onboarding Questionnaire > Skill by [ara.so](https://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. ## 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.) ## Installation ### Option 1: Global skills directory ```bash cd ~/.claude/skills git clone https://github.com/adamlyttleapps/claude-skill-app-onboarding-questionnaire.git app-onboarding-questionnaire ``` ### Option 2: Project-level dependency Add to your project's `.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. ## The 14-Screen Framework | # | Screen | Conversion Purpose | |---|--------|--------------------| | 1 | Welcome | Hook — show the end state, create desire | | 2 | Goal Question | "What are you trying to achieve?" — psychological investment | | 3 | Pain Points | "What prevents you?" — builds empathy | | 4 | Social Proof | Persona-matched testimonials | | 5 | Tinder Cards | Swipe agree/disagree on pain statements | | 6 | Personalised Solution | Mirror pains back with app solution stats | | 7 | Comparison Table | Life with vs without the app *(optional)* | | 8 | Preferences | Functional personalisation for the demo | | 9 | Permission Priming | Benefit-framed pre-sell before system dialogs | | 10 | Processing Moment | "Building X just for you..." anticipation builder | | 11 | App Demo | User actually uses the core app mechanic | | 12 | Value Delivery | Tangible output + share/viral moment | | 13 | Account Gate | Optional sign-in to save what they created | | 14 | Paywall | Hard paywall with trial, social proof, pricing | Not every app needs every screen — the skill adapts based on your app's complexity and type. ## 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. ### Permission Priming (Screen 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. ### Viral / Share Moment (Screen 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 ```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 ```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 ( Do these sound familiar? Swipe right if yes, left if no {PAIN_STATEMENTS[currentIndex]} ); } ``` ### Flutter — Processing / Loading Screen ```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}); @override State createState() => _ProcessingScreenState(); } class _ProcessingScreenState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; int _stepIndex = 0; final List _steps = [ 'Analysing your goals...', 'Matching your preferences...', 'Crafting your personal plan...', 'Almost ready!', ]; @override 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); } @override 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) ```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 } } ``` ## Configuration Options When you run `/app-onboarding-questionnaire`, the skill asks about: | Option | Description | |--------|-------------| | App type | Subscription, freemium, one-time purchase | | Core loop | The single thing users do in your app | | Target audience | Who the app is for (used for copy tone) | | Paywall timing | Whether to show paywall before or after account creation | | Screens to skip | Comparison table, account gate, etc. | | Brand colours | Used in generated SwiftUI/CSS/Flutter theme code | ## 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 ``` ## Troubleshooting **Skill doesn't detect my permissions correctly** - iOS: ensure `Info.plist` is at the project root or `/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` ## Reference The framework is based on analysis of the [Mob](https://www.mob.co.uk/) 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.