@tour-kit/reactStyling
Custom Components
Build fully custom tour card, overlay, and navigation components using Tour Kit hooks and headless primitives
Custom Components
Use headless exports for full customization.
Headless Components
import {
TourCard as HeadlessTourCard,
TourNavigation as HeadlessNav,
} from '@tour-kit/react/headless';
function CustomCard() {
return (
<HeadlessTourCard>
{({ currentStep, next, prev }) => (
<div className="my-custom-card">
<h3>{currentStep?.title}</h3>
<p>{currentStep?.content}</p>
<button onClick={prev}>Back</button>
<button onClick={next}>Next</button>
</div>
)}
</HeadlessTourCard>
);
}Using with useTour
import { useTour } from '@tour-kit/react';
function FullyCustomTour() {
const { isActive, currentStep, next, prev, skip } = useTour('my-tour');
if (!isActive) return null;
return (
<div className="fixed inset-0 z-50">
<div className="absolute bg-white p-6 rounded-xl shadow-2xl">
<h2>{currentStep?.title}</h2>
<p>{currentStep?.content}</p>
<div className="flex gap-2 mt-4">
<button onClick={prev}>Previous</button>
<button onClick={next}>Next</button>
<button onClick={skip}>Skip</button>
</div>
</div>
</div>
);
}Custom Overlay
import { useSpotlight } from '@tour-kit/core';
function CustomOverlay() {
const { isVisible, targetRect, padding } = useSpotlight();
if (!isVisible || !targetRect) return null;
return (
<div className="fixed inset-0 pointer-events-none">
{/* Backdrop */}
<div className="absolute inset-0 bg-black/50" />
{/* Cutout */}
<div
className="absolute bg-transparent ring-[9999px] ring-black/50"
style={{
top: targetRect.top - padding,
left: targetRect.left - padding,
width: targetRect.width + padding * 2,
height: targetRect.height + padding * 2,
borderRadius: 8,
}}
/>
</div>
);
}Custom Progress
import { useTour } from '@tour-kit/react';
function StepIndicator() {
const { currentStepIndex, totalSteps } = useTour('my-tour');
return (
<div className="flex gap-1">
{Array.from({ length: totalSteps }, (_, i) => (
<div
key={i}
className={cn(
'w-2 h-2 rounded-full transition-colors',
i === currentStepIndex ? 'bg-blue-500' : 'bg-gray-300',
i < currentStepIndex && 'bg-green-500'
)}
/>
))}
</div>
);
}Custom Navigation
import { useTour } from '@tour-kit/react';
import { Button } from '@/components/ui/button';
function TourButtons() {
const {
next,
prev,
skip,
complete,
isFirstStep,
isLastStep,
} = useTour('my-tour');
return (
<div className="flex items-center gap-2">
<Button
variant="ghost"
onClick={skip}
size="sm"
>
Skip tour
</Button>
<div className="flex gap-2 ml-auto">
{!isFirstStep && (
<Button variant="outline" onClick={prev}>
Back
</Button>
)}
{isLastStep ? (
<Button onClick={complete}>
Get Started
</Button>
) : (
<Button onClick={next}>
Continue
</Button>
)}
</div>
</div>
);
}Complete Custom Implementation
import { Tour, TourStep, useTour, useSpotlight } from '@tour-kit/react';
import { AnimatePresence, motion } from 'framer-motion';
function CustomTourUI() {
const { isActive, currentStep, currentStepIndex, totalSteps, next, prev, skip } = useTour('custom');
const { targetRect } = useSpotlight();
if (!isActive || !currentStep) return null;
return (
<AnimatePresence>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="fixed z-50 bg-white rounded-2xl shadow-2xl p-6 w-80"
style={{
top: targetRect ? targetRect.bottom + 16 : '50%',
left: targetRect ? targetRect.left : '50%',
}}
>
<div className="flex items-start justify-between mb-4">
<span className="text-xs font-medium text-blue-600 bg-blue-50 px-2 py-1 rounded">
Step {currentStepIndex + 1} of {totalSteps}
</span>
<button onClick={skip} className="text-gray-400 hover:text-gray-600">
×
</button>
</div>
<h3 className="text-lg font-semibold mb-2">{currentStep.title}</h3>
<p className="text-gray-600 mb-6">{currentStep.content}</p>
<div className="flex justify-between">
<button
onClick={prev}
disabled={currentStepIndex === 0}
className="text-gray-500 hover:text-gray-700 disabled:opacity-50"
>
Previous
</button>
<button
onClick={next}
className="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600"
>
{currentStepIndex === totalSteps - 1 ? 'Finish' : 'Next'}
</button>
</div>
</motion.div>
</AnimatePresence>
);
}
// Usage
function App() {
return (
<Tour id="custom">
<TourStep target="#feature" title="New Feature" content="Check this out!" />
<CustomTourUI />
<YourApp />
</Tour>
);
}