Validation
Add validation logic to your onboarding tours to ensure users complete required actions before proceeding to the next step.
Overview
Validation allows you to create more interactive and guided onboarding experiences by ensuring users complete specific actions before moving to the next step. This is particularly useful for:
- Form validations during onboarding
- Ensuring users click specific buttons or interact with elements
- Verifying user input before proceeding
- Ensuring users have permissions to access certain features
- And many more! Creating more engaging step-by-step tutorials
How Validation Works
Validation in NextStep works through custom card components that can validate user actions before allowing progression to the next step. When validation fails, users receive feedback and cannot proceed until they complete the required action.
We chose this approach because it allows you to create your own validation logic, options are limitless.
Creating a Validation Card Component
To implement validation, you'll need to create a custom card component that includes validation logic. Here's an example:
'use client';
import { CardComponentProps, useNextStep } from 'nextstepjs';
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import validation from '@/lib/validation';
import { toast } from '@/hooks/use-toast';
const CardWithValidation: React.FC<CardComponentProps> = ({
step,
currentStep,
totalSteps,
nextStep,
prevStep,
skipTour,
arrow,
}) => {
const { currentTour } = useNextStep();
// Get the validation function for the current step
const validate =
currentTour && validation[currentTour] && validation[currentTour][currentStep]
? validation[currentTour][currentStep].validation
: () => true;
// Get the validation message for the current step
const validationMessage =
currentTour && validation[currentTour] && validation[currentTour][currentStep]
? validation[currentTour][currentStep].validationMessage
: '';
return (
<Card className="w-[350px]">
<CardHeader>
<CardTitle className="flex items-center justify-between">
<p>{step.title}</p>
{step.icon}
</CardTitle>
</CardHeader>
<CardContent>
<p>{step.content}</p>
{arrow}
</CardContent>
<CardFooter className="flex flex-col">
{step.showControls && (
<div className="flex justify-between w-full">
<Button onClick={prevStep} disabled={currentStep === 0} variant="outline">
Previous
</Button>
<Button
// Validate the current step and if it passes, go to the next step
// If it fails, show the validation message
onClick={async () => {
if (await validate()) {
nextStep();
} else {
toast({
title: validationMessage,
variant: 'destructive',
});
}
}}
>
{currentStep === totalSteps - 1 ? 'Finish' : 'Next'}
</Button>
</div>
)}
{step.showSkip && (
<Button onClick={skipTour} variant={'ghost'} className="w-full">
Skip Tour
</Button>
)}
</CardFooter>
</Card>
);
};
export default CardWithValidation;
Example Validations
This is how you can create your own validation logic.
interface ValidationStep {
validation: () => boolean | Promise<boolean>;
validationMessage: string;
}
interface ValidationTour {
[stepIndex: number]: ValidationStep;
}
interface ValidationConfig {
[tourName: string]: ValidationTour;
}
const validation: ValidationConfig = {
'validation-demo': {
0: {
validation: async () => {
console.log('Validating step 0');
await new Promise((resolve) => setTimeout(resolve, 1000));
return true;
},
validationMessage: 'Step 0 validation failed',
},
1: {
validation: () => {
console.log('Validating step 1');
// Check window width return true if width is greater than 768
return window.innerWidth > 768;
},
validationMessage: 'Window width must be greater than 768',
},
2: {
validation: () => {
console.log('Validating step 2');
// Check if window width is less than 768
return window.innerWidth < 768;
},
validationMessage: 'Window width must be less than 768',
},
},
};
export default validation;
Using Validation Card Component
To use your validation card component, pass it to the cardComponent
prop of the NextStep component:
import { NextStep, NextStepProvider } from 'nextstepjs';
import CardWithValidation from './CardWithValidation';
import steps from './steps';
function MyApp() {
return (
<NextStepProvider>
<NextStep
steps={steps}
cardComponent={CardWithValidation}
>
{/* Your app content */}
</NextStep>
</NextStepProvider>
);
}
Validation Examples
Form Field Validation
const formValidation = {
validation: () => {
const input = document.getElementById('username') as HTMLInputElement;
return input && input.value.length >= 3;
},
validationMessage: 'Please enter a username with at least 3 characters.',
};
Button Click Validation
const buttonClickValidation = {
validation: () => {
return localStorage.getItem('button-clicked') === 'true';
},
validationMessage: 'Please click the "Get Started" button to continue.',
};
// In your component, set the flag when button is clicked
const handleButtonClick = () => {
localStorage.setItem('button-clicked', 'true');
// Your button logic here
};
Async Validation
const asyncValidation = {
validation: async () => {
try {
const response = await fetch('/api/validate-user');
const data = await response.json();
return data.isValid;
} catch (error) {
return false;
}
},
validationMessage: 'Please complete the required verification.',
};