A lightweight, zero-dependency coachmark/onboarding library for React Native. Guide your users through your app with beautiful spotlight walkthroughs β no native modules required.
- π― Spotlight overlay β highlights any component with a smooth animated cutout
- π Animated transitions β spotlight morphs fluidly between steps
- β Multiple shapes β rectangle, rounded rectangle, or circle
- π Rich tooltips β custom title, description, and progress indicator per step
- β Auto-stop β sequence ends automatically after the last step
- π¨ Fully customizable β overlay color, tooltip component, padding, radius
- π± Backdrop behavior β tap backdrop to skip, advance, or do nothing
- π Callback hooks β
onFinish,onSkip,onStepChange - π« Zero native dependencies β works in Expo Go out of the box
- π§© Custom tooltips β pass your own tooltip component via
renderTooltip
npm install react-native-coachmark-onboarding
# or
yarn add react-native-coachmark-onboardingimport { CoachmarkProvider } from 'react-native-coachmark-onboarding';
export default function App() {
return (
<CoachmarkProvider
onFinish={() => console.log('Tour complete!')}
onSkip={() => console.log('Tour skipped')}
backdropBehavior="next"
>
<YourApp />
</CoachmarkProvider>
);
}import { useCoachmark } from 'react-native-coachmark-onboarding';
function ProfileButton() {
const { ref, onLayout } = useCoachmark('profile', 0, {
title: 'Your Profile',
description: 'Tap here to view and edit your profile settings.',
shape: 'circle',
padding: 12,
});
return (
<View ref={ref} onLayout={onLayout}>
<ProfileIcon />
</View>
);
}function HomeScreen() {
const { startSequence } = useCoachmark('trigger', -1, { title: 'Trigger' });
return (
<Button title="Start Tour" onPress={startSequence} />
);
}Wrap your app to enable coachmarks.
| Prop | Type | Default | Description |
|---|---|---|---|
onFinish |
() => void |
β | Called when the user completes all steps |
onSkip |
() => void |
β | Called when the user taps "Skip" |
onStepChange |
(step, total) => void |
β | Called on every step change |
backdropBehavior |
'skip' | 'next' | 'none' |
'none' |
What happens when backdrop is tapped |
enabled |
boolean |
true |
Set to false to disable the tour |
overlayColor |
string |
'rgba(0,0,0,0.7)' |
Backdrop overlay color |
renderTooltip |
(props: TooltipRenderProps) => ReactNode |
β | Custom tooltip component |
Hook to register a component as a coachmark target.
| Parameter | Type | Description |
|---|---|---|
id |
string |
Unique identifier for this target |
step |
number |
Step number in the sequence (0-indexed) |
config |
CoachmarkStepConfig |
Step configuration (see below) |
Returns: { ref, onLayout, startSequence, stopSequence, nextStep, prevStep, isActive, currentStep, totalSteps }
| Property | Type | Default | Description |
|---|---|---|---|
title |
string |
'Step N' |
Tooltip title |
description |
string |
β | Tooltip description |
shape |
'rect' | 'circle' |
'rect' |
Spotlight shape |
radius |
number |
8 |
Border radius for rect shape |
padding |
number |
8 |
Breathing room around the spotlight |
Props passed to your custom tooltip component:
| Property | Type | Description |
|---|---|---|
step |
number |
Current step index |
totalSteps |
number |
Total number of steps |
title |
string |
Step title |
description |
string? |
Step description |
onNext |
() => void |
Advance to next step |
onPrev |
() => void |
Go back to previous step |
onSkip |
() => void |
Skip the tour |
isFirstStep |
boolean |
Whether this is the first step |
isLastStep |
boolean |
Whether this is the last step |
function MyTooltip({ title, description, onNext, onSkip, step, totalSteps, isLastStep }) {
return (
<View style={{ backgroundColor: '#1a1a2e', padding: 20, borderRadius: 16 }}>
<Text style={{ color: '#e94560', fontWeight: '700' }}>{title}</Text>
<Text style={{ color: '#eee', marginTop: 8 }}>{description}</Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 16 }}>
<Text onPress={onSkip} style={{ color: '#999' }}>Skip</Text>
<Text onPress={onNext} style={{ color: '#e94560' }}>
{isLastStep ? 'Finish' : 'Next β'}
</Text>
</View>
</View>
);
}
// Usage
<CoachmarkProvider renderTooltip={(props) => <MyTooltip {...props} />}>
<App />
</CoachmarkProvider>Use AsyncStorage with the onFinish callback to remember completed tours:
import AsyncStorage from '@react-native-async-storage/async-storage';
function App() {
const [tourDone, setTourDone] = useState(false);
useEffect(() => {
AsyncStorage.getItem('tour_complete').then((val) => {
if (val === 'true') setTourDone(true);
});
}, []);
return (
<CoachmarkProvider
enabled={!tourDone}
onFinish={() => {
setTourDone(true);
AsyncStorage.setItem('tour_complete', 'true');
}}
>
<MyApp />
</CoachmarkProvider>
);
}# Install dependencies
npm install
# Run the example app
cd example
npm install
npx expo start
# Run type checks
npm run typecheck
# Run linter
npm run lintMIT