@neuralsea/react-wizard
v1.0.1
Published
Customizable wizard control for React 19+ with TypeScript 5+
Maintainers
Readme
React Wizard Control
A production-grade, fully customisable wizard/stepper component for React 19+ and TypeScript 5+. Designed for complex workflows with built-in support for VS Code extensions, theming, validation, and accessibility.
Features
- Type-safe: comprehensive TypeScript 5+ definitions
- Fully customisable: theme system with VS Code integration
- Accessible: keyboard navigation and ARIA-friendly components
- Framework-agnostic: suitable for web apps and VS Code webviews
- Dual format: ESM and CommonJS builds
- State persistence: optional localStorage integration
- Performant: optimised hook-based architecture
Installation
npm install @neuralsea/react-wizard
# or
yarn add @neuralsea/react-wizard
# or
pnpm add @neuralsea/react-wizardQuick start
import { Wizard } from '@neuralsea/react-wizard';
import type { StepComponentProps, WizardStep } from '@neuralsea/react-wizard';
import '@neuralsea/react-wizard/styles';
type WizardData = {
name?: string;
newsletter?: boolean;
};
const PersonalInfoStep: React.FC<StepComponentProps<WizardData>> = ({
data,
updateData,
}) => (
<div>
<h2>Personal information</h2>
<input
type="text"
value={data.name || ''}
onChange={(e) => void updateData({ name: e.target.value })}
placeholder="Your name"
/>
</div>
);
const PreferencesStep: React.FC<StepComponentProps<WizardData>> = ({
data,
updateData,
}) => (
<div>
<h2>Preferences</h2>
<label>
<input
type="checkbox"
checked={data.newsletter || false}
onChange={(e) => void updateData({ newsletter: e.target.checked })}
/>
Subscribe to newsletter
</label>
</div>
);
const steps: WizardStep[] = [
{
id: 'personal',
title: 'Personal info',
description: 'Tell us about yourself',
component: PersonalInfoStep,
validate: async () => true,
},
{
id: 'preferences',
title: 'Preferences',
component: PreferencesStep,
},
];
export function App() {
return (
<Wizard
steps={steps}
onComplete={(data) => {
console.log('Wizard completed with data:', data);
}}
/>
);
}Core concepts
Step definition
Each step is defined by WizardStep:
interface WizardStep {
id: string;
title: string;
description?: string;
component: ComponentType<StepComponentProps>;
validate?: () =>
| boolean
| { valid: boolean; error?: string }
| Promise<boolean | { valid: boolean; error?: string }>;
canSkip?: boolean;
icon?: ComponentType<{ size?: number }>;
optional?: boolean;
}Step components
Step components receive these props:
interface StepComponentProps<TData = any> {
data: TData;
updateData: (updates: Partial<TData>) => void | Promise<void>;
goToStep: (stepId: string) => void;
goNext?: () => Promise<void>;
goPrevious?: () => void;
}Theming
Default theme
<Wizard
steps={steps}
theme={{
primary: '#3b82f6',
primaryHover: '#2563eb',
success: '#10b981',
error: '#ef4444',
radius: '0.5rem',
}}
/>VS Code theme
For VS Code extensions:
import { vscodeTheme } from '@neuralsea/react-wizard';
<Wizard steps={steps} theme={vscodeTheme} />;Advanced usage
Custom layout
Take full control of layout:
import {
Wizard,
StepIndicator,
StepContent,
Navigation,
} from '@neuralsea/react-wizard';
export function CustomWizard() {
return (
<Wizard steps={steps}>
<div className="custom-layout">
<aside>
<StepIndicator />
</aside>
<main>
<StepContent />
<Navigation />
</main>
</div>
</Wizard>
);
}Using the hook
Access wizard context in nested components:
import { useWizard } from '@neuralsea/react-wizard';
type WizardData = { name: string };
export function CustomComponent() {
const { steps, currentStepIndex, data, goNext, isLastStep } =
useWizard<WizardData>();
return (
<div>
<p>
Step {currentStepIndex + 1} of {steps.length}
</p>
<p>Name: {data.name}</p>
<button onClick={() => void goNext()} disabled={isLastStep}>
Next
</button>
</div>
);
}Higher-order components
Wrap step components with wizard capabilities:
import { withWizardStep, withValidation } from '@neuralsea/react-wizard';
import type { StepComponentProps, ValidationResult } from '@neuralsea/react-wizard';
interface MyData {
email: string;
}
const MyStep: React.FC<StepComponentProps<MyData>> = ({ data, updateData }) => (
<input value={data.email} onChange={(e) => void updateData({ email: e.target.value })} />
);
const validate = (data: MyData): ValidationResult =>
data.email.includes('@') ? { valid: true } : { valid: false, error: 'Invalid email' };
export default withValidation(withWizardStep<MyData>(MyStep), validate);Validation
Validation supports booleans or structured error messages:
const steps: WizardStep[] = [
{
id: 'email',
title: 'Email',
component: EmailStep,
validate: () => {
if (!data.email) return { valid: false, error: 'Email is required' };
if (!data.email.includes('@')) return { valid: false, error: 'Invalid email format' };
return { valid: true };
},
},
];State persistence
<Wizard
steps={steps}
persistData
storageKey="my-wizard-data"
onComplete={(data) => {
localStorage.removeItem('my-wizard-data');
}}
/>;Keyboard navigation
Enabled by default:
- Alt + Right Arrow: next step
- Alt + Left Arrow: previous step
Disable keyboard navigation:
<Wizard steps={steps} keyboardNavigation={false} />Accessibility
The wizard supports:
- ARIA labels and roles
- Keyboard navigation
- Screen reader-friendly structure
- Reduced motion preferences
Licence
MIT. See LICENSE.
