Getting Started
Get up and running with Rocapine Onboarding Studio SDK in minutes.
Installation
Core Package (Required)
The core package provides data fetching, caching, and hooks - no UI included:
npm install @rocapine/react-native-onboarding
UI Package (Optional)
The UI package provides pre-built components. Install it if you want to use the ready-made screens:
npm install @rocapine/react-native-onboarding-ui
Onboarding Studio is headless-first. The core package (@rocapine/react-native-onboarding) provides all the data management you need, while the UI package (@rocapine/react-native-onboarding-ui) offers optional pre-built components. You can use the pre-built components, build your own UI, or mix both approaches.
Required Peer Dependency
The SDK requires expo-router for navigation:
npm install expo-router
# or with Expo
npx expo install expo-router
The UI SDK automatically includes react-native-reanimated, react-native-gesture-handler, and react-native-svg as dependencies, so you don't need to install them separately.
Optional Dependencies by Screen Type
Install these only if you're using the specific screen types:
| Screen Type | Required Package | Install Command |
|---|---|---|
| Picker (weight, height, age, name, date) | @react-native-picker/picker | npx expo install @react-native-picker/picker |
| Ratings (app store reviews) | expo-store-review | npx expo install expo-store-review |
| Commitment (signature variant only) | @shopify/react-native-skia | npx expo install @shopify/react-native-skia |
ComposableScreen — Lottie elements | lottie-react-native | npx expo install lottie-react-native |
ComposableScreen — Rive elements | rive-react-native | npx expo install rive-react-native |
If you try to use a screen type without its required dependency, you'll see a clear error message telling you exactly what to install.
Create Your Project in the Onboarding Studio (CMS)
After installing the SDK, you'll need to create your project in the Onboarding Studio CMS to get your project ID.
- Visit the Onboarding Studio CMS: Go to https://onboarding-studio.rocapine.io/
- Sign up or log in to your account
- Create a new project - This will give you a unique project ID
- Copy your project ID - You'll need this to initialize the
OnboardingStudioClientin your app
Your project ID is required to connect your app to the CMS and fetch onboarding flows. Keep it secure and don't commit it to public repositories.
Quick Start
Step 1: Set Up the Provider
Wrap your app with OnboardingProvider in your root layout (e.g., app/_layout.tsx):
import {
OnboardingProvider,
OnboardingStudioClient,
onboardingExample,
} from "@rocapine/react-native-onboarding";
import { Stack } from "expo-router";
// Initialize the client with your project ID
// Get your project ID from https://onboarding-studio.rocapine.io/
const client = new OnboardingStudioClient("your-project-id", {
appVersion: "1.0.0",
isSandbox: true, // Set to true for development
fallbackOnboarding: onboardingExample,
});
export default function RootLayout() {
return (
<OnboardingProvider
client={client}
>
<Stack screenOptions={{ headerShown: false }} >
<Stack.Screen name="onboarding" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
{/* other screens... */}
</Stack>
</OnboardingProvider>
);
}
Step 2: Create the Onboarding layout
Create the onboarding layout using the ProgressBar component:
// app/onboarding/_layout.tsx
import { useOnboarding } from "@rocapine/react-native-onboarding";
import { ProgressBar } from "@rocapine/react-native-onboarding-ui";
import { Stack } from "expo-router";
export default function OnboardingLayout() {
const { isProgressBarVisible, progressPercentage } = useOnboarding()
return (<>
<ProgressBar isProgressBarVisible={isProgressBarVisible} progressPercentage={progressPercentage} />
<Stack screenOptions={{ headerShown: false }} />
</>);
}
Step 3: Create an Onboarding Screen
Create your onboarding screen using the useOnboardingQuestions hook:
// app/onboarding/[stepNumber].tsx
import { useOnboardingStep } from "@rocapine/react-native-onboarding";
import { OnboardingPage } from "@rocapine/react-native-onboarding-ui";
import { useLocalSearchParams, useRouter } from "expo-router";
export default function OnboardingScreen() {
const { stepNumber } = useLocalSearchParams();
const router = useRouter();
// Fetch the current step
const { step, isLastStep } = useOnboardingStep({
stepNumber: parseInt(stepNumber as string, 10),
});
const handleContinue = () => {
if (isLastStep) {
// Navigate to your main app
router.push("/(tabs)"
} else {
// Go to next step
router.push(`/onboarding/${parseInt(stepNumber as string, 10) + 1}`);
}
};
return <OnboardingPage step={step} onContinue={handleContinue} />;
}
Step 3: Start Your Onboarding Flow
Navigate to the first step from anywhere in your app:
import { useRouter } from "expo-router";
export default function WelcomeScreen() {
const router = useRouter();
const startOnboarding = () => {
router.push("/onboarding/1");
};
return (
<Button title="Start Onboarding" onPress={startOnboarding} />
);
}
That's It! 🎉
Your onboarding flow is now ready to use. The SDK will:
- Fetch steps from your CMS
- Cache them for offline access
- Track progress automatically
- Render the appropriate screen for each step type
Next Steps
- 📖 Learn about Core Concepts like caching and progress tracking
- 🎨 Explore Customization Options to match your brand
- 📘 Check the API Reference for advanced configuration
- 🎭 Review available Page Types for your onboarding flow
Configuration Options
OnboardingStudioClient
const client = new OnboardingStudioClient(projectId, {
appVersion: "1.0.0", // Your app version
isSandbox: true, // Enable sandbox mode
platform: "ios", // Platform (ios/android)
});
OnboardingProvider
| Prop | Type | Default | Description |
|---|---|---|---|
isSandbox | boolean | false | Enable sandbox mode |
appVersion | string | - | Your app version |
fallbackOnboarding | Onboarding | - | Fallback onboarding if the onboarding is not found |
Paywalls
PaywallProvider presents standalone paywalls (a "Settings → Upgrade" button,
an out-of-onboarding upsell) using the same element-based rendering engine as
a ComposableScreen onboarding step. Mount it once, above
OnboardingProvider, not beside or inside it:
import {
PaywallProvider,
OnboardingProvider,
OnboardingStudioClient,
revenueCatProductProvider,
} from "@rocapine/react-native-onboarding";
import { PaywallHost } from "@rocapine/react-native-onboarding-ui";
import Purchases from "react-native-purchases";
const client = new OnboardingStudioClient("your-project-id", { appVersion: "1.0.0" });
export default function RootLayout() {
return (
<PaywallProvider client={client} productProvider={revenueCatProductProvider(Purchases)}>
<OnboardingProvider client={client} productProvider={revenueCatProductProvider(Purchases)}>
{/* the rest of your app */}
</OnboardingProvider>
<PaywallHost />
</PaywallProvider>
);
}
Why above, not beside: React context only flows to descendants, so an
app-level PaywallProvider reaches OnboardingProvider wherever it mounts
underneath — including a screen with no onboarding flow active at all (e.g.
Settings). It's also what gives both providers one shared product
runtime: pass the same productProvider to each and OnboardingProvider
picks up the ancestor PaywallProvider's already-resolved products instead of
resolving its own — one store round-trip, one purchasing flag, visible to
both an onboarding step's purchase action and a standalone paywall's. If
OnboardingProvider also declares its own productRefs for slots the paywall
catalog doesn't cover, those are still resolved (a second, smaller round-trip
for just that slot set) and merged into the same runtime — productRefs is
never silently dropped just because a PaywallProvider is mounted above.
Present a paywall by placement from anywhere inside the tree:
import { usePaywall } from "@rocapine/react-native-onboarding";
function UpgradeButton() {
const { present, isReady } = usePaywall();
return (
<Button
title="Upgrade"
disabled={!isReady}
onPress={async () => {
const result = await present("settings_upgrade");
// result.status: "purchased" | "dismissed" | "cancelled" | "error"
// This says how the presentation ENDED, not what the user owns.
// Read entitlement from the store via your ProductProvider — a
// purchase followed by a render crash resolves "error", so keying
// entitlement off "purchased" would under-grant a paying user.
}}
/>
);
}
present() makes no network call — the whole paywall catalog (every
placement) is fetched once at PaywallProvider mount, and every placement's
products are resolved as one deduplicated union up front, so tapping "Upgrade"
renders instantly. An unknown placement, or calling present() while another
paywall is already showing, resolves { status: "error" } rather than
throwing.
A paywall's own content is authored the same way a ComposableScreen step is
(see Page Types) — same elements, same
Button.actions, plus two actions specific to this flow: dismiss (closes
the paywall) and presentPaywall (opens another placement, from either an
onboarding step or a paywall). See the Button actions section of
Page Types for both.
Example Project
Check out the example/ directory in the repository for a complete working example with all screen types and customization options.
cd example/
npm install
npm start