Skip to main content

Theming (Level 1)

Customize colors, typography, and semantic text styles to match your brand.

Quick Start

import { ThemeProvider } from "@rocapine/react-native-onboarding-ui";

<ThemeProvider
customTheme={{
colors: { primary: "#FF5733" },
typography: {
fontFamily: { title: "Poppins-Bold" }
}
}}
>
<YourApp />
</ThemeProvider>

That's it! Your theme tokens are now applied across all onboarding screens.


Color Customization

Available Color Tokens

colors: {
// Main brand colors
primary: string, // Buttons, accents
secondary: string, // Secondary actions
disable: string, // Disabled states

// Tertiary/accent colors
tertiary: {
tertiary1: string,
tertiary2: string,
tertiary3: string,
},

// Neutral grays (highest = darkest, lowest = lightest)
neutral: {
highest: string,
higher: string,
high: string,
medium: string,
low: string,
lower: string,
lowest: string,
},

// Surface/background colors
surface: {
lowest: string, // Main background
lower: string,
low: string,
medium: string,
high: string,
higher: string,
highest: string,
opposite: string, // Opposite of main background
},

// Text colors
text: {
primary: string, // Main text
secondary: string, // Secondary text
tertiary: string, // Tertiary text
opposite: string, // Opposite of primary
disable: string, // Disabled text
}
}

Color Customization Examples

Override primary color:

<ThemeProvider
customTheme={{
colors: { primary: "#007AFF" }
}}
>
<YourApp />
</ThemeProvider>

Override multiple colors:

<ThemeProvider
customTheme={{
colors: {
primary: "#007AFF",
secondary: "#5856D6",
text: {
primary: "#1A1A1A",
secondary: "#666666"
}
}
}}
>
<YourApp />
</ThemeProvider>

Nested overrides:

<ThemeProvider
customTheme={{
colors: {
neutral: {
lowest: "#F5F5F5",
highest: "#1A1A1A"
},
surface: {
lowest: "#FFFFFF",
opposite: "#000000"
}
}
}}
>
<YourApp />
</ThemeProvider>

Light & Dark Mode

Customize themes for each mode separately:

<ThemeProvider
customLightTheme={{
colors: {
primary: "#007AFF",
surface: { lowest: "#FFFFFF" },
text: { primary: "#000000" }
}
}}
customDarkTheme={{
colors: {
primary: "#0A84FF",
surface: { lowest: "#000000" },
text: { primary: "#FFFFFF" }
}
}}
>
<YourApp />
</ThemeProvider>

Or use a single theme for both:

<ThemeProvider
customTheme={{
colors: { primary: "#FF5733" }
}}
>
<YourApp />
</ThemeProvider>

Switch modes programmatically:

import { useTheme } from "@rocapine/react-native-onboarding-ui";

function ThemeSwitcher() {
const { colorScheme, toggleTheme } = useTheme();

return (
<Button
title={`Switch to ${colorScheme === "light" ? "dark" : "light"}`}
onPress={toggleTheme}
/>
);
}

Typography Customization

Font Families

The SDK uses three font categories:

  • title: Used for headings (heading1, heading2)
  • text: Used for body text, buttons, labels
  • tagline: Used for special emphasis text

There are two ways to load custom fonts:

  1. Bundle locally with expo-font — for fonts shipped with the app binary.
  2. Download at runtime via the Onboarding.fonts manifest — for fonts the CMS declares per onboarding (no native rebuild required).

Option A — Local fonts via expo-font

import { useFonts } from 'expo-font';
import { ThemeProvider } from "@rocapine/react-native-onboarding-ui";

export default function RootLayout() {
const [fontsLoaded] = useFonts({
'Poppins-Bold': require('./assets/fonts/Poppins-Bold.ttf'),
'Roboto-Regular': require('./assets/fonts/Roboto-Regular.ttf'),
});

if (!fontsLoaded) return null;

return (
<ThemeProvider
customTheme={{
typography: {
fontFamily: {
title: "Poppins-Bold",
text: "Roboto-Regular",
}
}
}}
>
<YourApp />
</ThemeProvider>
);
}

Option B — Runtime fonts via the Onboarding payload (since 1.17.0)

The backend can ship a font manifest in the onboarding response. The SDK downloads each variant via expo-font (optional peer dep) and blocks render until they are ready. ComposableScreen TextElement, ButtonElement, and InputElement automatically resolve fontFamily + fontWeight to the right registered variant — CMS authors keep using the family name they declared.

Backend Onboarding response shape:

{
"metadata": { /* ... */ },
"steps": [ /* ... */ ],
"configuration": { /* ... */ },
"fonts": {
"Inter": {
"regular": "https://cdn/.../Inter-Regular.ttf",
"medium": "https://cdn/.../Inter-Medium.ttf",
"bold": "https://cdn/.../Inter-Bold.ttf"
},
"Lobster": {
"regular": "https://cdn/.../Lobster-Regular.ttf"
}
}
}

Weight keys may be named (regular, medium, semibold, bold, extrabold) or numeric (100900); both are normalized internally. When the requested weight is not registered, the resolver falls back to the closest available weight (CSS-style font matching).

Show a fallback while fonts download:

import { OnboardingProvider } from "@rocapine/react-native-onboarding";
import { ActivityIndicator } from "react-native";

<OnboardingProvider
client={client}
fontsFallback={<ActivityIndicator />}
>
<YourApp />
</OnboardingProvider>

For non-OnboardingProvider hosts, mount fonts directly with FontLoaderGate:

import { FontLoaderGate, type FontsManifest } from "@rocapine/react-native-onboarding";
import { ActivityIndicator } from "react-native";

const fonts: FontsManifest = {
Inter: {
regular: "https://cdn/.../Inter-Regular.ttf",
bold: "https://cdn/.../Inter-Bold.ttf",
},
};

<FontLoaderGate fonts={fonts} fallback={<ActivityIndicator />}>
<YourScreen />
</FontLoaderGate>

If expo-font is not installed in the host app, registerFonts resolves to an empty registry and logs a single warning — text falls back to system fonts.

Default Font Family (since 1.19.0)

typography.defaultFontFamily is the inherit target for ComposableScreen Text, Button, and Input elements. When an element omits fontFamily or sets it to the literal "inherit", the renderer falls back to this token.

Set it once at the theme level to brand every onboarding text element with a single family — no need to patch each textStyles.*.fontFamily entry or sprinkle fontFamily on every CMS payload element.

<ThemeProvider
customTheme={{
typography: {
defaultFontFamily: "Lobster",
},
}}
>
<YourApp />
</ThemeProvider>

CMS payload examples:

{ "type": "Text", "props": { "content": "Inherits Lobster" } }
{ "type": "Text", "props": { "content": "Also Lobster", "fontFamily": "inherit" } }
{ "type": "Text", "props": { "content": "Stays Inter", "fontFamily": "Inter" } }

Default value: "Inter". Set to undefined to fall back to the platform system font when an element does not declare its own fontFamily.

Font Sizes

Override font size tokens:

<ThemeProvider
customTheme={{
typography: {
fontSize: {
xs: 10,
sm: 12,
md: 14,
lg: 18,
xl: 28, // heading2
"2xl": 36, // heading1
"3xl": 48,
"4xl": 72,
}
}
}}
>
<YourApp />
</ThemeProvider>

Default sizes:

  • xs: 12px
  • sm: 14px
  • md: 16px
  • lg: 20px
  • xl: 24px
  • 2xl: 32px
  • 3xl: 40px
  • 4xl: 72px

Font Weights

Override font weight tokens:

<ThemeProvider
customTheme={{
typography: {
fontWeight: {
regular: "400",
medium: "500",
semibold: "700", // Make semibold bolder
bold: "700",
extrabold: "800",
}
}
}}
>
<YourApp />
</ThemeProvider>

Line Heights

Override line height tokens:

<ThemeProvider
customTheme={{
typography: {
lineHeight: {
tight: 1.2,
normal: 1.4,
relaxed: 1.6,
}
}
}}
>
<YourApp />
</ThemeProvider>

Semantic Text Styles

The SDK provides semantic text styles that match Figma specifications:

Default Semantic Styles

heading1: {
fontSize: 32, // 2xl token
fontWeight: "600", // semibold token
lineHeight: 1.25, // tight token
fontFamily: "title" // title font
}

heading2: {
fontSize: 24, // xl token
fontWeight: "600", // semibold token
lineHeight: 1.3, // normal token
fontFamily: "title" // title font
}

heading3: {
fontSize: 18, // lg token
fontWeight: "500", // medium token
lineHeight: 1.3, // normal token
fontFamily: "text" // text font
}

body: {
fontSize: 16, // md token
fontWeight: "400", // regular token
lineHeight: 1.3, // normal token
fontFamily: "text" // text font
}

bodyMedium: {
fontSize: 16, // md token
fontWeight: "500", // medium token
lineHeight: 1.3, // normal token
fontFamily: "text" // text font
}

label: {
fontSize: 14, // sm token
fontWeight: "500", // medium token
lineHeight: 1.3, // normal token
fontFamily: "text" // text font
}

caption: {
fontSize: 12, // xs token
fontWeight: "400", // regular token
lineHeight: 1.3, // normal token
fontFamily: "text" // text font
}

button: {
fontSize: 16, // md token
fontWeight: "500", // medium token
lineHeight: 1.5, // relaxed token
fontFamily: "text" // text font
}

Override Semantic Styles

<ThemeProvider
customTheme={{
typography: {
textStyles: {
heading1: {
fontSize: 40,
fontWeight: "700",
lineHeight: 1.2,
fontFamily: "title"
},
body: {
fontSize: 18,
fontWeight: "400",
lineHeight: 1.4,
fontFamily: "text"
}
}
}
}}
>
<YourApp />
</ThemeProvider>

Using Theme in Custom Code

Access Theme Tokens

import { useTheme } from "@rocapine/react-native-onboarding-ui";

function MyComponent() {
const { theme } = useTheme();

return (
<View style={{ backgroundColor: theme.colors.surface.lowest }}>
<Text style={{
color: theme.colors.text.primary,
fontSize: theme.typography.textStyles.heading3.fontSize,
}}>
Hello
</Text>
</View>
);
}

Use Semantic Text Styles

import { useTheme, getTextStyle } from "@rocapine/react-native-onboarding-ui";

function MyComponent() {
const { theme } = useTheme();

return (
<View>
<Text style={[
getTextStyle(theme, "heading1"),
{ color: theme.colors.text.primary }
]}>
Title
</Text>
<Text style={[
getTextStyle(theme, "body"),
{ color: theme.colors.text.secondary }
]}>
Body text
</Text>
</View>
);
}

Complete Example

import { useFonts } from 'expo-font';
import {
OnboardingProvider,
OnboardingStudioClient,
} from "@rocapine/react-native-onboarding";
import {
ThemeProvider,
ProgressBar,
} from "@rocapine/react-native-onboarding-ui";

const client = new OnboardingStudioClient("your-project-id", {
appVersion: "1.0.0",
});

export default function RootLayout() {
// Load custom fonts
const [fontsLoaded] = useFonts({
'CustomFont-Bold': require('./assets/fonts/CustomFont-Bold.ttf'),
'CustomFont-Regular': require('./assets/fonts/CustomFont-Regular.ttf'),
});

if (!fontsLoaded) return null;

return (
<OnboardingProvider
client={client}
locale="en"
customAudienceParams={{ onboardingId: "your-onboarding-id" }}
>
<ThemeProvider
customTheme={{
colors: {
primary: "#FF5733",
secondary: "#5856D6",
text: {
primary: "#1A1A1A",
secondary: "#666666"
},
surface: {
lowest: "#FFFFFF"
}
},
typography: {
fontFamily: {
title: "CustomFont-Bold",
text: "CustomFont-Regular",
},
fontSize: {
"2xl": 36,
xl: 26,
},
textStyles: {
heading1: {
fontSize: 36,
fontWeight: "700",
lineHeight: 1.2,
fontFamily: "title"
}
}
}
}}
>
<ProgressBar />
<YourApp />
</ThemeProvider>
</OnboardingProvider>
);
}

Default Theme Reference

Want to extend the default theme instead of overriding? Import default tokens:

import {
lightTokens,
darkTokens,
typography,
} from "@rocapine/react-native-onboarding-ui";

const myTheme = {
colors: {
...lightTokens.colors,
primary: "#FF5733",
},
typography: {
...typography,
fontFamily: {
...typography.fontFamily,
title: "CustomFont-Bold"
}
},
};

<ThemeProvider customTheme={myTheme}>
<YourApp />
</ThemeProvider>

Deep Merge Behavior

The SDK uses deep merging for theme tokens, so you only need to specify what changes:

// This merges with default tokens
<ThemeProvider
customTheme={{
colors: { primary: "#FF5733" } // Other colors remain default
}}
>
<YourApp />
</ThemeProvider>
tip

You don't need to provide all tokens. Only specify the ones you want to override.


Next Steps