Page Types
The SDK includes pre-built renderers for common onboarding patterns.
Overview
Each page type is designed for a specific onboarding use case. All types share common properties and are customizable through theming, custom components, or custom renderers.
Available Page Types
Question
Interactive questions with single or multiple choice answers.
Use cases:
- User preferences
- Quiz-style onboarding
- Survey questions
- Profile building
Key features:
- Single or multiple selection
- Customizable answer buttons
- Theme-aware styling
- Support for custom components
Payload:
{
title: string;
subtitle?: string;
answers: Array<{
label: string;
value: string;
}>;
multipleAnswer: boolean;
}
Example:
{
id: "preferences",
type: "Question",
payload: {
title: "What are your goals?",
subtitle: "Select all that apply",
answers: [
{ label: "Build muscle", value: "muscle" },
{ label: "Lose weight", value: "weight" },
{ label: "Improve flexibility", value: "flexibility" },
],
multipleAnswer: true
}
}
Dependencies: None
MediaContent
Display images or videos with title and description.
Use cases:
- Welcome screens
- Feature explanations
- Visual onboarding
- Tutorial steps
Key features:
- Video, Image, Lottie, or Rive support
- Optional title and description
- Centered media display
- Responsive sizing
Payload:
{
title?: string;
description?: string;
media: {
type: "video" | "image" | "lottie" | "rive";
url?: string;
localPathId?: string;
};
}
Example:
{
id: "welcome",
type: "MediaContent",
payload: {
title: "Welcome to FitApp",
description: "Your personal fitness companion",
media: {
type: "image",
url: "https://example.com/welcome.png"
}
}
}
Dependencies: None
Carousel
Multi-slide horizontal pagination with page indicators.
Use cases:
- Feature tours
- Multi-step tutorials
- Swipeable content
- Progressive disclosure
Key features:
- Horizontal scrolling
- Page indicators (dots)
- Per-slide media and text
- Automatic "Next" / "Continue" button labels
Payload:
{
screens: Array<{
title?: string;
description?: string;
media?: MediaSource;
}>;
}
Example:
{
id: "tour",
type: "Carousel",
payload: {
screens: [
{
title: "Track Your Progress",
description: "Monitor your fitness journey",
media: { type: "image", url: "https://..." }
},
{
title: "Set Goals",
description: "Define and achieve your targets",
media: { type: "image", url: "https://..." }
}
]
}
}
Dependencies: None
Picker
Type-specific input pickers for structured data.
Use cases:
- Collect specific user data
- Profile information
- Preferences with constrained options
Key features:
- Native picker UI
- Type-specific implementations
- Unit conversions (weight, height)
- Range validation
Supported types:
weight- Weight with kg/lb unitsheight- Height with cm/ft+in unitsage- Age pickergender- Gender selectionname- Name inputdate- Date picker
Payload:
{
title: string;
subtitle?: string;
pickerType: "weight" | "height" | "age" | "gender" | "name" | "date";
defaultValue?: string;
}
Example:
{
id: "user-weight",
type: "Picker",
payload: {
title: "What's your weight?",
pickerType: "weight",
defaultValue: "70-kg"
}
}
Dependencies: @react-native-picker/picker
npx expo install @react-native-picker/picker
Loader
Sequential progress animation with optional carousel.
Use cases:
- Loading states
- Processing animations
- Wait screens with progress
- Educational "Did you know?" moments
Key features:
- Sequential step animations
- Progress bars (0% → 100%)
- Checkmarks on completion
- Optional carousel with images
- Configurable duration
Payload:
{
title: string;
steps: Array<{
label: string; // Text while loading
completed: string; // Text when done
}>;
didYouKnowImages?: Array<MediaSource>;
duration?: number; // Milliseconds per step (default: 2000)
}
Example:
{
id: "analyzing",
type: "Loader",
payload: {
title: "Analyzing your profile",
steps: [
{
label: "Processing data",
completed: "Data processed"
},
{
label: "Building recommendations",
completed: "Recommendations ready"
}
],
duration: 2000
}
}
Dependencies: None
Ratings
App store rating prompts with social proof.
Use cases:
- Request app reviews
- Show user testimonials
- Build trust with social proof
- Increase app ratings
Key features:
- Star rating UI
- Opens native App Store review
- Social proof cards (optional)
- Customizable testimonials
Payload:
{
title: string;
subtitle?: string;
socialProofs?: Array<{
name: string;
rating: number;
comment: string;
avatar?: string;
}>;
}
Example:
{
id: "rate-us",
type: "Ratings",
payload: {
title: "Enjoying FitApp?",
subtitle: "Your feedback helps us improve!",
socialProofs: [
{
name: "Sarah M.",
rating: 5,
comment: "This app changed my life!",
}
]
}
}
Dependencies: expo-store-review
npx expo install expo-store-review
Commitment
User commitment and agreement screens.
Use cases:
- Terms acceptance
- Commitment statements
- Agreements
- Signature collection
Key features:
- Checkbox for agreement
- Optional signature (requires Skia)
- Info boxes for important notes
- Required acknowledgment before continuing
Payload:
{
title: string;
subtitle?: string;
commitmentText: string;
requireSignature?: boolean;
infoBoxes?: Array<{
title: string;
description: string;
icon?: string;
}>;
}
Example:
{
id: "terms",
type: "Commitment",
payload: {
title: "Terms of Service",
commitmentText: "I agree to the Terms of Service and Privacy Policy",
requireSignature: false,
infoBoxes: [
{
title: "Privacy First",
description: "We never share your data without permission"
}
]
}
}
Dependencies (signature only): @shopify/react-native-skia
npx expo install @shopify/react-native-skia
ComposableScreen
Build arbitrary onboarding screens entirely from CMS data — no custom renderer required. You define a tree of layout and media elements that is rendered directly to native components.
Use cases:
- Fully custom layouts without shipping app updates
- Cards, feature highlights, or rich text screens
- Animation-heavy screens (Lottie or Rive)
- Any screen that doesn't fit a pre-built type
Key features:
- Recursive element tree (
YStack,XStack,ZStack,SafeAreaView,Text,Image,Lottie,Rive,Icon,Video,Input,RadioGroup,CheckboxGroup,Button,DatePicker,Carousel) - Full flexbox layout support
- Border, radius, overflow, and opacity control
- Dimension constraints (
width,height,min/max) - Spacing with both
paddingandmargin - Text color defaults to
theme.colors.text.primary - Lottie, Rive, Video, and DatePicker as optional peer deps — graceful fallback if not installed
- Variable context —
Input,RadioGroup,CheckboxGroup, andDatePickerelements write into shared state;Textelements interpolate those values with{{variableName}}expressions
Element types:
| Element | Renders as | Direction | Peer dep |
|---|---|---|---|
YStack | <View> | column | — |
XStack | <View> | row | — |
ZStack | <View> | depth (z-axis) | — |
SafeAreaView | <SafeAreaView> | column | react-native-safe-area-context |
Text | <Text> | — | — |
Image | <Image> | — | — |
Icon | Lucide icon | — | — (bundled) |
Input | <TextInput> | — | — |
RadioGroup | Radio item list | — | — |
CheckboxGroup | Checkbox item list | — | — |
Button | <TouchableOpacity> | — | — |
Carousel | Horizontal slides | — | react-native-reanimated-carousel |
DatePicker | Native date/time picker | — | @react-native-community/datetimepicker |
Lottie | LottieView | — | lottie-react-native |
Rive | <Rive> | — | rive-react-native |
Video | <VideoView> | — | expo-video |
Stack props (YStack / XStack):
| Prop | Type | Notes |
|---|---|---|
gap | number | Space between children |
padding / paddingHorizontal / paddingVertical | number | |
margin / marginHorizontal / marginVertical | number | |
flex / flexShrink / flexWrap | number | string | flexShrink defaults to 1 inside XStack |
alignItems | "flex-start" | "center" | "flex-end" | "stretch" | |
alignSelf | "auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline" | |
justifyContent | "flex-start" | "center" | "flex-end" | "space-between" | "space-around" | |
width / height / minWidth / maxWidth / minHeight / maxHeight | number | |
backgroundColor / borderColor | string | |
borderWidth / borderRadius | number | |
overflow | "hidden" | "visible" | "scroll" | Use "hidden" to clip rounded corners |
opacity | number |
ZStack props:
ZStack accepts only the BaseBoxProps subset listed below. Flex/flow props such as gap, alignItems, justifyContent, and flexWrap are not honored — children are absolutely positioned, so flex layout does not apply.
| Prop | Type | Notes |
|---|---|---|
width / height / minWidth / maxWidth / minHeight / maxHeight | number | Explicit height is recommended — absolute children do not size the parent |
padding / paddingHorizontal / paddingVertical | number | Applied to the ZStack container |
margin / marginHorizontal / marginVertical | number | |
flex / flexShrink / flexGrow | number | Apply to the ZStack itself within a parent flex container |
alignSelf | "auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline" | |
backgroundColor / backgroundGradient | string / GradientBackground | |
borderWidth / borderRadius / borderColor | number | string | |
overflow | "hidden" | "visible" | "scroll" | Use "hidden" to clip overflowing children to rounded corners |
opacity | number |
Children are layered in declaration order: first child = bottom, last child = top. Each child is wrapped in position: "absolute" filling the container, so flex props on ZStack itself (gap, alignItems, justifyContent) have no effect — instead control the layout of each layer's content via the layer's own props (e.g. wrap content in a YStack with justifyContent: "flex-end" to anchor it to the bottom).
The wrapper for each child uses pointerEvents="box-none", so touches pass through transparent regions to layers underneath.
SafeAreaView props:
SafeAreaView mirrors the props of SafeAreaView from react-native-safe-area-context. Use it to inset content from system UI (notch, home indicator) inside a ComposableScreen. Other renderers wrap themselves in SafeAreaView automatically; ComposableScreen does not, so opt in here when needed.
| Prop | Type | Notes |
|---|---|---|
mode | "padding" | "margin" | Defaults to "padding". "margin" is useful when the SafeAreaView itself has a background that should not extend into the inset region |
edges | Edge[] or { top?, right?, bottom?, left? } | Edge is "top" | "right" | "bottom" | "left". Object form maps each edge to "off" | "additive" | "maximum" for fine-grained control |
All BaseBoxProps | — | flex, padding, backgroundColor, borderRadius, etc. |
SafeAreaView example:
{
"id": "safe-root",
"type": "SafeAreaView",
"props": { "flex": 1, "edges": ["top", "bottom"] },
"children": [
{ "id": "content", "type": "YStack", "props": { "padding": 24, "gap": 16 }, "children": [ /* ... */ ] }
]
}
As of 1.15.0, OnboardingTemplate does not apply safe-area insets. Built-in page types (Question, MediaContent, Carousel, etc.) now wrap themselves with SafeAreaView edges={["top","bottom"]}. For ComposableScreen, place a SafeAreaView UIElement at the root of your tree (or omit it for full edge-to-edge rendering).
Image props:
| Prop | Type | Notes |
|---|---|---|
url | string | Required — remote image URI |
width | number | Defaults to "100%" |
height | number | When omitted, aspectRatio is used to size the image |
aspectRatio | number | Used when height is absent; defaults to 16/9 |
resizeMode | "cover" | "contain" | "stretch" | "center" | Defaults to "cover" |
borderRadius / borderWidth | number | |
borderColor | string | |
opacity | number | |
margin / marginHorizontal / marginVertical | number | |
padding / paddingHorizontal / paddingVertical | number |
Text props:
| Prop | Type | Notes |
|---|---|---|
content | string | Required |
mode | "plain" | "expression" | "plain" (default) renders content as-is; "expression" interpolates {{variableName}} patterns from the variable context |
fontSize / fontWeight / letterSpacing / lineHeight | number | string | |
fontFamily | string | Font family name; must be loaded by the host app (e.g. via expo-font) |
color | string | Defaults to theme.colors.text.primary |
textAlign | "left" | "center" | "right" | |
backgroundColor / borderColor | string | |
padding / paddingHorizontal / paddingVertical | number | |
margin / marginHorizontal / marginVertical | number | |
borderWidth / borderRadius / opacity | number |
Input props:
| Prop | Type | Notes |
|---|---|---|
variableName | string | Context key — value is written to shared composableVariables on every keystroke |
placeholder | string | Placeholder text |
placeholderColor | string | Defaults to theme.colors.text.tertiary |
defaultValue | string | Initial value; also written to context on mount if variableName is set |
keyboardType | "default" | "email-address" | "numeric" | "phone-pad" | "decimal-pad" | "url" | "number-pad" | "ascii-capable" | "numbers-and-punctuation" | "name-phone-pad" | "twitter" | "web-search" | "visible-password" | Defaults to "default" |
returnKeyType | "done" | "go" | "next" | "search" | "send" | "default" | "emergency-call" | "google" | "join" | "route" | "yahoo" | "none" | "previous" | Defaults to "done" |
autoCapitalize | "none" | "sentences" | "words" | "characters" | Defaults to "sentences" |
secureTextEntry | boolean | Mask input for passwords; defaults to false |
maxLength | number | Maximum character count |
multiline | boolean | Enable multi-line input; defaults to false |
numberOfLines | number | Visible line count when multiline is true |
editable | boolean | Defaults to true |
color | string | Text color; defaults to theme.colors.text.primary |
fontSize | number | Defaults to 16 |
textAlign | "left" | "center" | "right" | |
padding / paddingHorizontal / paddingVertical | number | Inner padding; defaults to 12 |
backgroundColor | string | Defaults to theme.colors.neutral.lowest |
borderWidth | number | Defaults to 1 |
borderRadius | number | Defaults to 8 |
borderColor | string | Defaults to theme.colors.neutral.low |
alignSelf | "auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline" | |
width / height | number | |
opacity | number | |
margin / marginHorizontal / marginVertical | number |
RadioGroup props:
| Prop | Type | Notes |
|---|---|---|
items | Array<{ label: string; value: string }> | Required — list of options |
variableName | string | Context key — selected entry (value + label) is written to shared composableVariables |
defaultValue | string | Pre-selects the matching item on mount; also written to context |
direction | "vertical" | "horizontal" | Layout direction; defaults to "vertical" |
gap | number | Space between items; defaults to 8 |
itemBackgroundColor | string | Unselected item background; defaults to "transparent" |
itemSelectedBackgroundColor | string | Selected item background; defaults to primary + 15% opacity |
itemBorderColor | string | Unselected item border; defaults to theme.colors.neutral.low |
itemSelectedBorderColor | string | Selected item border; defaults to theme.colors.primary |
itemBorderRadius | number | Defaults to 8 |
itemBorderWidth | number | Defaults to 1 |
itemColor | string | Unselected label color; defaults to theme.colors.text.primary |
itemSelectedColor | string | Selected label color; defaults to theme.colors.primary |
itemFontSize | number | |
itemFontWeight | string | |
itemFontFamily | string | |
itemPadding | number | Defaults to 12 when no itemPaddingHorizontal/Vertical is set |
itemPaddingHorizontal / itemPaddingVertical | number | |
alignSelf | "auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline" | |
width / height | number | Container dimensions |
margin / marginHorizontal / marginVertical | number | |
padding / paddingHorizontal / paddingVertical | number | |
borderWidth / borderRadius / borderColor | number | string | Container border |
opacity | number |
CheckboxGroup props:
| Prop | Type | Notes |
|---|---|---|
items | Array<{ label: string; value: string }> | Required — list of options; values must be unique |
variableName | string | Context key — selected values stored as JSON.stringify(string[]), label as comma-joined display names |
defaultValues | string[] | Pre-selects matching items on mount; must reference valid item values |
direction | "vertical" | "horizontal" | Layout direction; defaults to "vertical" |
gap | number | Space between items; defaults to 8 |
itemBackgroundColor | string | Unselected item background; defaults to "transparent" |
itemSelectedBackgroundColor | string | Selected item background; defaults to primary + 10% opacity |
itemBorderColor | string | Unselected item border; defaults to theme.colors.neutral.low |
itemSelectedBorderColor | string | Selected item border; defaults to theme.colors.primary |
itemBorderRadius | number | Defaults to 8 |
itemBorderWidth | number | Defaults to 1 |
itemColor | string | Unselected label color; defaults to theme.colors.text.primary |
itemSelectedColor | string | Selected label color; defaults to theme.colors.primary |
itemFontSize | number | |
itemFontWeight | string | |
itemFontFamily | string | |
itemPadding | number | Defaults to 12 when no itemPaddingHorizontal/Vertical is set |
itemPaddingHorizontal / itemPaddingVertical | number | |
alignSelf | "auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline" | |
width / height | number | Container dimensions |
margin / marginHorizontal / marginVertical | number | |
padding / paddingHorizontal / paddingVertical | number | |
borderWidth / borderRadius / borderColor | number | string | Container border |
opacity | number |
Button props:
| Prop | Type | Notes |
|---|---|---|
label | string | Required — button text |
variant | "filled" | "outlined" | "ghost" | Visual style; filled = solid primary background, outlined = border only, ghost = no background or border |
actions | ButtonAction[] | Ordered list run on press. See Button actions below. When omitted (and no legacy action), the press is a no-op |
action | "continue" | Deprecated — back-compat alias. When actions is absent and action === "continue", runtime treats it as actions: ["continue"] |
backgroundColor | string | Overrides variant background color |
color | string | Label text color |
fontSize | number | |
fontWeight | string | |
fontFamily | string | |
textAlign | "left" | "center" | "right" | |
alignSelf | "auto" | "flex-start" | "center" | "flex-end" | "stretch" | |
width / height | number | |
margin / marginHorizontal / marginVertical | number | |
padding / paddingHorizontal / paddingVertical | number | |
borderWidth / borderRadius / borderColor | number | string | |
opacity | number |
Button actions
Button.actions is an ordered array. Each entry is one of:
"continue"— advances to the next onboarding step (calls theonContinuecallback wired to the renderer).{ type: "custom", function: string, variables?: string[] }— invokes a developer-injected handler registered onOnboardingProvider.customActions.variableslists the keys to read from the live ComposableScreen variable map and forward to the handler.{ type: "setVariable", name: string, value: string, label?: string }— writes to the variable map ({ value, label }). Pair with"continue"to set a discriminator a downstreamnextSteprule can branch on; the value is visible to the same-tick branch evaluation.
Execution rules:
- Sequential. Each action awaits the previous before starting.
- Async-aware. If a custom handler returns a
Promise, the runtime awaits it. "continue"is terminal — any actions after it are ignored.- A thrown error in a custom handler is logged via
console.errorand the remaining chain is aborted. - An unknown
functionname (no matching key incustomActions) emitsconsole.warnand the chain skips to the next action.
{
"id": "primary-cta",
"type": "Button",
"props": {
"label": "Get Started",
"variant": "filled",
"actions": [
{ "type": "custom", "function": "trackCta", "variables": ["name", "plan"] },
"continue"
]
}
}
Register the matching handlers on OnboardingProvider:
import { OnboardingProvider } from "@rocapine/react-native-onboarding";
<OnboardingProvider
client={client}
customActions={{
trackCta: async ({ variables }) => {
// variables → { name?: { value, label? }, plan?: { value, label? } }
await analytics.track("cta_pressed", {
name: variables.name?.value,
plan: variables.plan?.value,
});
},
}}
>
{children}
</OnboardingProvider>
See Custom Actions for the full handler signature and patterns.
DatePicker props:
| Prop | Type | Notes |
|---|---|---|
variableName | string | Context key — selected date stored as ISO 8601 string (value) and locale-formatted string (label, e.g. "Apr 23, 2026") |
defaultValue | string | ISO date string; used as initial value if no persisted value exists |
minimumDate | string | ISO date string — earliest selectable date |
maximumDate | string | ISO date string — latest selectable date |
mode | "date" | "time" | "datetime" | Defaults to "date" |
display | "default" | "spinner" | "calendar" | "clock" | "compact" | "inline" | Platform-specific display style; iOS defaults to "spinner", Android defaults to "default" |
textColor | string | Picker text color; defaults to theme.colors.text.primary |
accentColor | string | Picker accent/highlight color; defaults to theme.colors.primary |
locale | string | BCP 47 locale tag (e.g. "fr-FR") |
alignSelf | "auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline" | |
width / height | number | Container dimensions |
margin / marginHorizontal / marginVertical | number | |
padding / paddingHorizontal / paddingVertical | number | |
borderWidth / borderRadius / borderColor | number | string | Container border |
opacity | number |
On Android, DatePicker renders as a Pressable showing the current value. Tapping opens the native modal picker. On iOS it renders inline.
Dependencies: @react-native-community/datetimepicker
npx expo install @react-native-community/datetimepicker
Carousel props:
| Prop | Type | Notes |
|---|---|---|
carouselType | "normal" | "left-align" | "parallax" | "stack" | Layout mode — see below; defaults to "normal" |
autoPlay | boolean | Auto-advance slides; defaults to false |
autoPlayInterval | number | Milliseconds between auto-advances; defaults to 3000 |
loop | boolean | Loop back to first slide after last; defaults to true |
showDots | boolean | Show Pagination.Basic pill dots below carousel; defaults to true |
height | number | Slide height in dp; defaults to 220 |
width | number | Slide width; defaults to useWindowDimensions().width (overridden for stack and left-align) |
borderRadius | number | Applied to each slide's inner View |
All BaseBoxProps | — | margin, padding, alignSelf, borderColor, borderWidth, opacity, etc. applied to outer container |
carouselType modes:
| Mode | Width | Library prop | Description |
|---|---|---|---|
"normal" | 100 % window | — | Full-width paged carousel |
"parallax" | 100 % window | mode="parallax" | Depth-zoom effect on adjacent slides |
"stack" | 75 % window | mode="horizontal-stack" | Stacked card effect; multiple slides visible |
"left-align" | 82 % window | — | Next slide peeks in from the right; overflow: "visible" on container |
Slides are arbitrary UIElement trees — place any renderable element (Image, Text, YStack, etc.) as children. Each child becomes one slide.
Dependencies: react-native-reanimated-carousel
npx expo install react-native-reanimated-carousel
Variable context
Input, RadioGroup, CheckboxGroup, and DatePicker elements write into a shared composableVariables map stored in OnboardingProgressContext. This state persists across navigation between ComposableScreen steps, so values collected on an early screen are available on later screens.
Each entry is a structured object { value: string; label?: string }:
| Element | value | label |
|---|---|---|
Input | Raw text string | — |
RadioGroup | Selected item value (e.g. "monthly") | Selected item label (e.g. "Monthly") |
CheckboxGroup | JSON.stringify(string[]) of selected values | Comma-joined display labels (e.g. "Health, Fitness") |
DatePicker | ISO 8601 string (e.g. "1990-01-01T00:00:00.000Z") | Locale-formatted date (e.g. "Jan 1, 1990") |
Text elements with mode: "expression" interpolate {{variableName}} patterns in their content string from that map. The renderer prefers label over value when both are present. If a key has no value yet, the pattern is replaced with an empty string.
[
{
"id": "name-input",
"type": "Input",
"props": {
"variableName": "name",
"placeholder": "Enter your name",
"autoCapitalize": "words",
"borderRadius": 12
}
},
{
"id": "greeting",
"type": "Text",
"props": {
"content": "Hello {{name}}!",
"mode": "expression",
"fontSize": 18,
"fontWeight": "600",
"textAlign": "center"
}
}
]
OnboardingProgressProvider must wrap your app for the variable context to work. If you use @rocapine/react-native-onboarding-ui, wrap your root layout with it:
import { OnboardingProgressProvider } from "@rocapine/react-native-onboarding-ui";
export default function RootLayout() {
return (
<OnboardingProgressProvider>
{/* ... */}
</OnboardingProgressProvider>
);
}
Without the provider the context falls back to its default (empty variables, no-op setter) — Input values will not be saved and expression Text will render blank for any {{variable}}.
Icon props:
| Prop | Type | Notes |
|---|---|---|
name | string | Required — Lucide icon name e.g. "Star", "Heart", "CheckCircle" |
size | number | Icon width & height in dp; defaults to 24 |
color | string | Defaults to theme.colors.text.primary |
strokeWidth | number | Line stroke weight; defaults to 2 |
width / height | number | Wrapper View dimensions |
margin / marginHorizontal / marginVertical | number | |
padding / paddingHorizontal / paddingVertical | number | |
borderWidth | number | |
borderRadius | number | |
borderColor | string | |
opacity | number |
Icon uses Lucide React Native, which is bundled with the UI package — no extra install needed. If an unknown icon name is passed the element renders nothing.
Lottie props:
| Prop | Type | Notes |
|---|---|---|
source | string | Required — remote .json URL |
width | number | Defaults to "100%" |
height | number | Defaults to 200 |
autoPlay | boolean | Defaults to true |
loop | boolean | Defaults to true |
speed | number | Playback speed multiplier |
opacity | number | |
margin / marginHorizontal / marginVertical | number | |
padding / paddingHorizontal / paddingVertical | number | |
borderWidth / borderRadius / borderColor | number | string | Applied via a wrapping View |
Dependencies: lottie-react-native
npx expo install lottie-react-native
If lottie-react-native is not installed, the element renders a placeholder view with an install hint instead of crashing.
Rive props:
| Prop | Type | Notes |
|---|---|---|
url | string | Required — remote .riv URL |
width | number | Defaults to "100%" |
height | number | Defaults to 200 |
autoplay | boolean | Defaults to true |
fit | "Contain" | "Cover" | "Fill" | "FitWidth" | "FitHeight" | "None" | "ScaleDown" | "Layout" | |
alignment | "TopLeft" | "TopCenter" | "TopRight" | "CenterLeft" | "Center" | "CenterRight" | "BottomLeft" | "BottomCenter" | "BottomRight" | |
artboardName | string | Target artboard in the .riv file |
stateMachineName | string | State machine to drive |
opacity | number | |
margin / marginHorizontal / marginVertical | number | |
padding / paddingHorizontal / paddingVertical | number | |
borderWidth / borderRadius / borderColor | number | string | Applied via a wrapping View |
Dependencies: rive-react-native
npx expo install rive-react-native
If rive-react-native is not installed, the element renders a placeholder view with an install hint instead of crashing.
Video props:
| Prop | Type | Notes |
|---|---|---|
url | string | Required — remote video URL |
width | number | Defaults to "100%" |
height | number | Defaults to 200 |
autoPlay | boolean | Start playing on mount; defaults to false |
loop | boolean | Loop playback; defaults to false |
muted | boolean | Mute audio; defaults to true (required for autoplay on iOS) |
controls | boolean | Show native playback controls; defaults to false |
opacity | number | |
margin / marginHorizontal / marginVertical | number | |
padding / paddingHorizontal / paddingVertical | number | |
borderWidth | number | Applied via a wrapping View |
borderRadius | number | Applied via a wrapping View |
borderColor | string | Applied via a wrapping View |
Dependencies: expo-video
npx expo install expo-video
If expo-video is not installed, the element renders a placeholder view with an install hint instead of crashing.
Payload:
{
elements: UIElement[]; // Recursive tree of layout and media elements
}
Example:
{
"id": "feature-highlight",
"type": "ComposableScreen",
"payload": {
"elements": [
{
"id": "card",
"type": "YStack",
"props": {
"padding": 24,
"gap": 12,
"borderWidth": 1,
"borderRadius": 16,
"borderColor": "#E0E0E0",
"overflow": "hidden"
},
"children": [
{
"id": "hero",
"type": "Image",
"props": {
"url": "https://example.com/hero.png",
"height": 180,
"resizeMode": "cover",
"borderRadius": 12
}
},
{
"id": "title",
"type": "Text",
"props": {
"content": "Welcome aboard",
"fontSize": 24,
"fontWeight": "700"
}
},
{
"id": "row",
"type": "XStack",
"props": { "gap": 8, "alignItems": "center" },
"children": [
{
"id": "badge",
"type": "YStack",
"props": {
"backgroundColor": "#E8F5E9",
"borderRadius": 8,
"padding": 8
},
"children": [
{
"id": "badge-text",
"type": "Text",
"props": { "content": "New", "fontSize": 12, "color": "#2E7D32" }
}
]
},
{
"id": "label",
"type": "Text",
"props": { "content": "Personalized for you", "fontSize": 14 }
}
]
}
]
}
]
}
}
ZStack example — image with text overlay:
{
"id": "hero-overlay",
"type": "ZStack",
"props": {
"height": 200,
"borderRadius": 16,
"overflow": "hidden",
"marginVertical": 8
},
"children": [
{
"id": "bg",
"type": "Image",
"props": { "url": "https://example.com/hero.jpg", "height": 200, "resizeMode": "cover" }
},
{
"id": "overlay",
"type": "YStack",
"props": { "backgroundColor": "rgba(0,0,0,0.45)", "padding": 20, "justifyContent": "flex-end" },
"children": [
{
"id": "label",
"type": "Text",
"props": { "content": "Your headline here", "fontSize": 18, "fontWeight": "700", "color": "#fff" }
}
]
}
]
}
Dependencies: None for YStack, XStack, ZStack, Text, Image, Icon, Input, RadioGroup, CheckboxGroup, Button. SafeAreaView requires react-native-safe-area-context (already a peer dep of @rocapine/react-native-onboarding-ui). See the Carousel, Lottie, Rive, Video, and DatePicker sections above for elements with peer deps.
Common Properties
All page types share these properties:
{
id: string; // Unique identifier
type: string; // Discriminated union field
name: string; // Display name (for debugging)
displayProgressHeader: boolean; // Show/hide progress bar
payload: object; // Type-specific data
customPayload: object | null; // Your custom fields
continueButtonLabel?: string; // Optional CTA text
figmaUrl?: string | null; // Design reference
}
Customization Levels
Each page type supports three levels of customization:
Level 1: Theming
Apply theme tokens to all screens:
<OnboardingProvider
theme={{
colors: { primary: "#FF5733" },
typography: { fontFamily: { title: "CustomFont" } }
}}
/>
Level 2: Custom Components
Replace specific UI components (currently available for Question type):
<OnboardingProvider
customComponents={{
QuestionAnswerButton: MyCustomButton
}}
/>
Learn more about custom components →
Level 3: Custom Renderers
Complete control over specific screens:
// In your routing file
if (step.id === "custom-screen") {
return <MyCustomRenderer step={step} onContinue={onContinue} />;
}
Learn more about custom renderers →
Media Support
Several page types support media with the MediaSourceSchema:
{
type: "video" | "image" | "lottie" | "rive";
url?: string; // Remote URL
localPathId?: string; // Local asset path
}
Supported media types:
- video: Video files (implementation pending)
- image: PNG, JPG, WebP via React Native Image
- lottie: Lottie animations via
lottie-react-native - rive: Rive animations via
rive-react-native
Using Individual Renderers
You can use renderers directly without OnboardingPage:
import { MediaContentRenderer, MediaContentStepType } from "@rocapine/react-native-onboarding";
const step: MediaContentStepType = {
id: "welcome",
type: "MediaContent",
name: "Welcome",
displayProgressHeader: true,
payload: {
title: "Welcome!",
description: "Let's get started",
media: { type: "image", url: "https://..." },
},
customPayload: null,
continueButtonLabel: "Get Started",
figmaUrl: null,
};
<MediaContentRenderer step={step} onContinue={handleContinue} />
Next Steps
- 🎨 Customization Overview - Learn about customization levels
- 📘 API Reference - Complete type definitions
- 🚀 Getting Started - Build your first onboarding flow