
Syncfusion React Stepper
- 386 installs
- 3 repo stars
- Updated July 28, 2026
- syncfusion/react-ui-components-skills
Use syncfusion-react-stepper for development tasks
About
syncfusion-react-stepper: A skill for development. This provides functionality for development workflows.
- syncfusion-react-stepper
Syncfusion React Stepper by the numbers
- 386 all-time installs (skills.sh)
- +52 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,125 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/react-ui-components-skills --skill syncfusion-react-stepperAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 386 |
|---|---|
| repo stars | ★ 3 |
| Last updated | July 28, 2026 |
| Repository | syncfusion/react-ui-components-skills ↗ |
What it does
Use syncfusion-react-stepper for development tasks
Files
Implementing Syncfusion React Stepper
The Stepper component guides users through a multi-step workflow or process with visual indicators, step labels, and flexible configuration. It's ideal for wizards, checkout flows, onboarding processes, and any guided user experience requiring sequential navigation.
When to Use This Skill
Use the Stepper component when you need to:
- Guide users through multi-step processes (checkout, registration, setup wizards)
- Display step-by-step workflows with progress indication
- Validate user input before advancing to the next step
- Support linear or non-linear navigation patterns
- Customize appearance with icons, labels, and templates
- Localize content for different languages/regions
Component Overview
Key Capabilities:
- Step Navigation: Horizontal and vertical orientations, sequential or free navigation
- Step Types: Default (icons + labels), label-only, or indicator-only modes
- Events: Track step changes, validations, and interactions
- Styling: Animations, templates, custom CSS, and tooltips
- Accessibility: Full keyboard navigation and ARIA support
- Globalization: Multi-language support and RTL compatibility
Documentation and Navigation Guide
Getting Started & Installation
📄 Read: references/getting-started.md
- Package installation and dependencies
- CSS imports and theme setup
- Creating your first stepper
- Initial configuration and rendering
Core Configuration: Steps and Properties
📄 Read: references/steps-and-configuration.md
- Adding and defining steps with StepDirective
- Icon CSS, text, and label properties
- Active step management
- Disabled states and customization
- CSS class configuration
Layout & Appearance: Orientations and Types
📄 Read: references/orientations-and-types.md
- Horizontal and vertical orientations
- Step type modes (Default, Label, Indicator)
- Label positioning (Top, Bottom, Start, End)
- RTL support and responsive design
Interaction & Behavior: Events
📄 Read: references/events-and-interactions.md
- Lifecycle events: created, stepChanged, stepChanging
- User interaction events: stepClick, beforeStepRender
- Event arguments and handling patterns
- Preventing unwanted transitions
Workflow Control: Linear Flow and Validation
📄 Read: references/linear-flow-and-validation.md
- Linear stepper configuration for sequential navigation
- Step validation and status management
- Preventing invalid transitions
- Resetting stepper state
Advanced Styling & Customization
📄 Read: references/animation-template-tooltip.md
- Animation configuration and timing
- Template customization for steps
- Tooltip integration and display
- Custom content rendering
Methods and Advanced Patterns
📄 Read: references/methods-and-advanced.md
- Component methods (reset, etc.)
- Both API patterns (component-based vs property-based)
- Advanced use cases and patterns
- Performance optimization tips
Best Practices: Accessibility & Localization
📄 Read: references/accessibility-globalization.md
- WCAG compliance and ARIA attributes
- Keyboard navigation guidelines
- Globalization and localization
- RTL support implementation
Quick Start Examples
Pattern 1: Component-Based (StepsDirective)
import React from 'react';
import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
import '@syncfusion/ej2-base/styles/tailwind3.css';
import '@syncfusion/ej2-navigations/styles/tailwind3.css';
function App() {
return (
<div>
<StepperComponent>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>
</div>
);
}
export default App;Pattern 2: Property-Based (steps Array)
import React from 'react';
import { StepperComponent } from '@syncfusion/ej2-react-navigations';
import '@syncfusion/ej2-base/styles/tailwind3.css';
import '@syncfusion/ej2-navigations/styles/tailwind3.css';
function App() {
const steps = [
{ iconCss: 'sf-icon-cart', label: 'Cart' },
{ iconCss: 'sf-icon-transport', label: 'Delivery' },
{ iconCss: 'sf-icon-payment', label: 'Payment' },
{ iconCss: 'sf-icon-success', label: 'Confirmation' }
];
return (
<div>
<StepperComponent steps={steps} />
</div>
);
}
export default App;Common Patterns
Pattern 1: Wizard with Validation
const [activeStep, setActiveStep] = React.useState(0);
const stepperRef = React.useRef(null);
const handleStepChanging = (args) => {
// Validate current step before advancing
if (!validateStep(activeStep)) {
args.cancel = true; // Prevent transition
}
};
<StepperComponent
ref={stepperRef}
stepChanging={handleStepChanging}
>
{/* steps */}
</StepperComponent>Pattern 2: Linear vs Non-Linear Navigation
// Linear: Users must complete steps sequentially
<StepperComponent linear={true}>
// Non-linear: Users can skip to any step
<StepperComponent linear={false}>Pattern 3: Responsive Orientation
// Auto-switch orientation based on screen size
const [orientation, setOrientation] = React.useState('horizontal');
React.useEffect(() => {
const handleResize = () => {
setOrientation(window.innerWidth < 768 ? 'vertical' : 'horizontal');
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
<StepperComponent orientation={orientation}>Key Props and Configuration
Component Properties
| Prop | Type | Default | Purpose |
|---|---|---|---|
activeStep | number | 0 | Currently active step index |
animation | StepperAnimationSettingsModel | undefined | Animation configuration (enable, duration, delay) |
cssClass | string | '' | CSS class for custom styling |
enablePersistence | boolean | false | Persist component state between page reloads |
enableRtl | boolean | false | Enable right-to-left layout |
labelPosition | string | 'Bottom' | Label placement: 'Top', 'Bottom', 'Start', 'End' |
linear | boolean | false | Enforce sequential step navigation |
locale | string | 'en-US' | Localization culture code |
orientation | string | 'horizontal' | Layout direction: 'horizontal' or 'vertical' |
readOnly | boolean | false | Disable user interaction |
showTooltip | boolean | true | Show tooltips on hover |
stepType | string | 'Default' | Visual mode: 'Default', 'Label', 'Indicator' |
steps | StepModel[] | [] | Array of step objects (property-based pattern) |
template | string \ | function | undefined |
tooltipTemplate | string \ | function | undefined |
Step Properties (StepModel)
| Property | Type | Purpose |
|---|---|---|
cssClass | string | CSS class for individual step styling |
disabled | boolean | Disable the step |
iconCss | string | Icon CSS class for the step |
isValid | boolean | Validation status of the step |
label | string | Step label text |
optional | boolean | Mark step as optional |
status | string | Step status: 'NotStarted', 'InProgress', 'Completed' |
text | string | Text content (usually number) |
Animation Settings
| Property | Type | Default | Purpose |
|---|---|---|---|
enable | boolean | true | Enable animations |
duration | number | 400 | Animation duration in milliseconds |
delay | number | 0 | Delay before animation starts |
Events
| Event | Fires | Use For | Arguments |
|---|---|---|---|
created | After component initialization | Setup, initialization | Event |
stepChanged | After step changes | Update UI, load content | StepperChangedEventArgs |
stepChanging | Before step changes | Validate, prevent transitions | StepperChangingEventArgs |
stepClick | User clicks step | Track interactions | StepperClickEventArgs |
beforeStepRender | Before rendering each step | Customize step appearance | StepperRenderingEventArgs |
Methods
| Method | Parameters | Returns | Purpose |
|---|---|---|---|
reset() | none | void | Reset stepper to initial state (activeStep: 0) |
nextStep() | none | void | Move to next step programmatically |
previousStep() | none | void | Move to previous step programmatically |
refreshProgressbar() | none | void | Refresh progress bar on container resize |
destroy() | none | void | Destroy component and release resources |
Event Arguments Reference:
- StepperChangedEventArgs: activeStep, previousStep, isInteracted, name, event, element
- StepperChangingEventArgs: activeStep, previousStep, cancel, isInteracted, name, event, element
- StepperClickEventArgs: activeStep, name, event, element
- StepperRenderingEventArgs: activeStep, name, element
Common Use Cases
- E-Commerce Checkout: Multi-step checkout flow with order review, shipping, payment
- User Registration: Multi-step signup with email, profile, verification
- Setup Wizards: Software onboarding with configuration steps
- Survey Forms: Step-by-step questionnaire with progress indication
- Installation Guides: Installation steps with instructions and validation
Accessibility and Globalization
Table of Contents
- Accessibility Overview
- WAI-ARIA Attributes
- Keyboard Navigation
- Localization
- RTL Support
- Testing Accessibility
Accessibility Overview
The Stepper component meets comprehensive accessibility standards including WCAG 2.2, Section 508, and ADA compliance.
Accessibility Compliance
| Standard | Support |
|---|---|
| WCAG 2.2 | ✓ Full Support |
| Section 508 | ✓ Full Support |
| Screen Readers | ✓ Supported |
| Keyboard Navigation | ✓ Supported |
| Color Contrast | ✓ Compliant |
| Right-to-Left | ✓ Supported |
| Mobile Devices | ✓ Supported |
WAI-ARIA Attributes
The Stepper component automatically includes ARIA attributes for screen readers:
ARIA Labels
<StepperComponent>
<StepsDirective>
{/* Each step automatically gets aria-label */}
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
</StepsDirective>
</StepperComponent>Rendered ARIA:
<div role="tablist">
<div role="tab" aria-label="Step 1" aria-current="true">...</div>
<div role="tab" aria-label="Step 2" aria-current="false">...</div>
</div>ARIA Current
The aria-current="page" attribute identifies the active step:
// Automatically applied by component
<div role="tab" aria-current="page" aria-label="Step 2">...</div>ARIA Disabled
Disabled steps have the aria-disabled attribute:
<StepperComponent>
<StepsDirective>
<StepDirective label="Available" />
<StepDirective label="Disabled" disabled={true} />
</StepsDirective>
</StepperComponent>Rendered:
<div role="tab" aria-label="Available">...</div>
<div role="tab" aria-label="Disabled" aria-disabled="true">...</div>Keyboard Navigation
Users can navigate the Stepper entirely with keyboard:
Keyboard Shortcuts
Horizontal Stepper
| Key | Action |
|---|---|
| Left Arrow | Move to previous step |
| Right Arrow | Move to next step |
| Home | Jump to first step |
| End | Jump to last step |
| Enter / Space | Activate focused step |
| Tab | Move focus to next interactive element |
| Shift+Tab | Move focus to previous interactive element |
Vertical Stepper
| Key | Action |
|---|---|
| Up Arrow | Move to previous step |
| Down Arrow | Move to next step |
| Home | Jump to first step |
| End | Jump to last step |
| Enter / Space | Activate focused step |
Example: Keyboard Navigation
function App() {
return (
<div>
<p>Use arrow keys to navigate</p>
<StepperComponent orientation="horizontal">
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>
</div>
);
}User Journey: 1. Tab → Focus on first step 2. Right Arrow → Move to next step 3. Right Arrow → Move to next step 4. Home → Jump back to first step 5. End → Jump to last step
Localization
Support multiple languages by defining locale translations:
Basic Localization
import { L10n } from '@syncfusion/ej2-base';
// Define locale translations
L10n.load({
'fr-FR': {
'stepper': {
'optional': 'Facultatif'
}
},
'de-DE': {
'stepper': {
'optional': 'Optional'
}
},
'es-ES': {
'stepper': {
'optional': 'Opcional'
}
}
});
function App() {
return (
<StepperComponent locale="fr-FR">
<StepsDirective>
<StepDirective label="Étape 1" />
<StepDirective label="Étape 2" optional={true} />
<StepDirective label="Étape 3" />
</StepsDirective>
</StepperComponent>
);
}Locale Switching
function App() {
const [locale, setLocale] = React.useState('en-US');
const locales = {
'en-US': 'English',
'fr-FR': 'Français',
'de-DE': 'Deutsch',
'es-ES': 'Español'
};
// Define translations
React.useEffect(() => {
L10n.load({
'fr-FR': { 'stepper': { 'optional': 'Facultatif' } },
'de-DE': { 'stepper': { 'optional': 'Optional' } },
'es-ES': { 'stepper': { 'optional': 'Opcional' } }
});
}, []);
return (
<div>
<select value={locale} onChange={(e) => setLocale(e.target.value)}>
{Object.entries(locales).map(([code, name]) => (
<option key={code} value={code}>{name}</option>
))}
</select>
<StepperComponent locale={locale}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" optional={true} />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>
</div>
);
}Supported Locales
Default English locale: en-US
You can extend with any locale:
fr-FR(French)de-DE(German)es-ES(Spanish)ar-AE(Arabic)ja-JP(Japanese)zh-CN(Chinese)- And many others...
RTL Support
Enable right-to-left layout for RTL languages:
Basic RTL
<StepperComponent enableRtl={true}>
<StepsDirective>
<StepDirective label="الخطوة 1" />
<StepDirective label="الخطوة 2" />
<StepDirective label="الخطوة 3" />
</StepsDirective>
</StepperComponent>RTL with Localization
import { L10n } from '@syncfusion/ej2-base';
function App() {
L10n.load({
'ar-AE': {
'stepper': {
'optional': 'اختياري'
}
}
});
return (
<StepperComponent enableRtl={true} locale="ar-AE">
<StepsDirective>
<StepDirective label="الإشارة الأولى" />
<StepDirective label="الإشارة الثانية" optional={true} />
<StepDirective label="الإشارة الثالثة" />
</StepsDirective>
</StepperComponent>
);
}Global RTL Configuration
Enable RTL for entire application:
import { enableRtl } from '@syncfusion/ej2-base';
// Enable RTL globally
enableRtl(true);
function App() {
return (
<StepperComponent>
{/* All Syncfusion components will be RTL */}
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
</StepsDirective>
</StepperComponent>
);
}RTL Effects
When RTL is enabled:
- Steps flow right-to-left
- Navigation arrows reverse
- Labels position on opposite side
- Text direction automatically adjusted
- Layout mirrors appropriately
Testing Accessibility
Automated Testing
Use tools to validate accessibility:
# Install accessibility checker
npm install accessibility-checker
# Install axe-core
npm install axe-coreManual Testing Checklist
- [ ] Keyboard Navigation: Test all arrow keys, Home, End, Enter/Space
- [ ] Screen Reader: Test with NVDA, JAWS, or VoiceOver
- [ ] Color Contrast: Use contrast analyzer (WCAG AA or AAA)
- [ ] Focus Indicators: Verify visible focus rings
- [ ] Labels: Confirm aria-labels are descriptive
- [ ] RTL: Test with RTL language
- [ ] Mobile: Test with mobile screen readers
Testing with Screen Readers
Using NVDA (Windows)
1. Download NVDA 2. Start NVDA 3. Open browser and navigate to stepper 4. Use NVDA shortcuts to explore component
Using JAWS (Windows)
1. Open JAWS 2. Open application with stepper 3. Use JAWS navigation keys (arrow keys, etc.)
Using VoiceOver (Mac)
1. Enable VoiceOver: Cmd + F5 2. Use VO shortcuts to navigate 3. Test keyboard navigation
Code Example: Testing
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
describe('Stepper Accessibility', () => {
test('should have proper ARIA labels', () => {
render(
<StepperComponent>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
</StepsDirective>
</StepperComponent>
);
const steps = screen.getAllByRole('tab');
expect(steps[0]).toHaveAttribute('aria-label', 'Step 1');
expect(steps[1]).toHaveAttribute('aria-label', 'Step 2');
});
test('should navigate with keyboard arrows', async () => {
const user = userEvent.setup();
const { container } = render(
<StepperComponent>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>
);
const firstStep = screen.getByRole('tab', { name: 'Step 1' });
await user.click(firstStep);
await user.keyboard('{ArrowRight}');
const secondStep = screen.getByRole('tab', { name: 'Step 2' });
expect(secondStep).toHaveFocus();
});
});Best Practices
Accessibility:
- ✅ Always use descriptive labels
- ✅ Test with keyboard navigation
- ✅ Verify with screen readers
- ✅ Maintain color contrast ratios (WCAG AA)
- ✅ Provide meaningful aria-labels for complex steps
Localization:
- ✅ Define all locale translations upfront
- ✅ Test RTL layouts thoroughly
- ✅ Provide localized error messages
- ✅ Consider text length in different languages
RTL:
- ✅ Test navigation in RTL mode
- ✅ Verify icon positioning
- ✅ Check label alignment
- ✅ Ensure proper text direction (auto-set by component)
Advanced Patterns and Use Cases
Table of Contents
- Multi-Step Form Wizard
- E-Commerce Checkout
- Progress Tracker
- Setup Wizard
- Performance Optimization
- Common Gotchas
Multi-Step Form Wizard
A complete form spread across multiple steps with validation:
import React, { useState } from 'react';
import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function FormWizard() {
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
email: '',
phone: '',
address: '',
city: '',
country: '',
terms: false
});
const [stepValidation, setStepValidation] = useState([
{ isValid: null },
{ isValid: null },
{ isValid: null },
{ isValid: null }
]);
const validateStep = (stepIndex) => {
let isValid = false;
if (stepIndex === 0) {
isValid = formData.firstName && formData.lastName;
} else if (stepIndex === 1) {
isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email) && formData.phone;
} else if (stepIndex === 2) {
isValid = formData.address && formData.city && formData.country;
} else if (stepIndex === 3) {
isValid = formData.terms === true;
}
return isValid;
};
const handleStepChanging = (args) => {
const isValid = validateStep(args.previousStep);
if (!isValid) {
alert('Please fill all required fields');
args.cancel = true;
return;
}
// Update validation state
const newValidation = [...stepValidation];
newValidation[args.previousStep].isValid = true;
setStepValidation(newValidation);
};
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData({
...formData,
[name]: type === 'checkbox' ? checked : value
});
};
const handleSubmit = () => {
if (validateStep(3)) {
console.log('Form submitted:', formData);
alert('Form successfully submitted!');
}
};
return (
<div className="form-wizard">
<StepperComponent linear={true} stepChanging={handleStepChanging}>
<StepsDirective>
<StepDirective
label="Personal Info"
isValid={stepValidation[0].isValid}
iconCss="sf-icon-user"
/>
<StepDirective
label="Contact"
isValid={stepValidation[1].isValid}
iconCss="sf-icon-phone"
/>
<StepDirective
label="Address"
isValid={stepValidation[2].isValid}
iconCss="sf-icon-building"
/>
<StepDirective
label="Confirm"
isValid={stepValidation[3].isValid}
iconCss="sf-icon-check"
/>
</StepsDirective>
</StepperComponent>
<div className="form-content" style={{ marginTop: '20px' }}>
{/* Step 1: Personal Info */}
{/* Step 2: Contact */}
{/* Step 3: Address */}
{/* Step 4: Confirmation */}
</div>
<button onClick={handleSubmit} style={{ marginTop: '20px' }}>
Submit
</button>
</div>
);
}E-Commerce Checkout
Realistic checkout flow with order review and payment:
function CheckoutFlow() {
const [cart, setCart] = useState([
{ id: 1, name: 'Product 1', price: 29.99 },
{ id: 2, name: 'Product 2', price: 49.99 }
]);
const [checkout, setCheckout] = useState({
email: '',
shippingAddress: '',
shippingMethod: '',
cardNumber: '',
expiryDate: '',
cvv: ''
});
const total = cart.reduce((sum, item) => sum + item.price, 0);
const handleStepChanged = (args) => {
if (args.activeStep === 2) {
// Pre-fill shipping methods on step 3
console.log('Loading shipping options...');
}
};
return (
<div className="checkout">
<div className="checkout-header">
<h2>Checkout Process</h2>
<p>Total: ${total.toFixed(2)}</p>
</div>
<StepperComponent stepChanged={handleStepChanged} linear={true}>
<StepsDirective>
<StepDirective
label="Cart Review"
iconCss="sf-icon-cart"
/>
<StepDirective
label="Shipping"
iconCss="sf-icon-truck"
/>
<StepDirective
label="Payment"
iconCss="sf-icon-credit-card"
/>
<StepDirective
label="Confirmation"
iconCss="sf-icon-success"
/>
</StepsDirective>
</StepperComponent>
<div className="cart-summary">
<h3>Order Summary</h3>
{cart.map(item => (
<div key={item.id} className="cart-item">
<span>{item.name}</span>
<span>${item.price.toFixed(2)}</span>
</div>
))}
<div className="cart-total">
<strong>Total: ${total.toFixed(2)}</strong>
</div>
</div>
</div>
);
}Progress Tracker
Display progress without allowing backward navigation:
function ProgressTracker() {
const [progress, setProgress] = useState(0);
const [isProcessing, setIsProcessing] = useState(false);
const steps = [
{ label: 'Initializing', duration: 2000 },
{ label: 'Processing', duration: 3000 },
{ label: 'Finalizing', duration: 1000 },
{ label: 'Complete', duration: 0 }
];
React.useEffect(() => {
if (progress < steps.length - 1) {
setIsProcessing(true);
const timer = setTimeout(() => {
setProgress(progress + 1);
setIsProcessing(false);
}, steps[progress].duration);
return () => clearTimeout(timer);
}
}, [progress]);
return (
<div>
<StepperComponent
activeStep={progress}
readOnly={true}
orientation="vertical"
>
<StepsDirective>
{steps.map((step, index) => (
<StepDirective
key={index}
label={step.label}
isValid={progress > index ? true : progress === index ? null : false}
/>
))}
</StepsDirective>
</StepperComponent>
{isProcessing && <p>Processing... {steps[progress].label}</p>}
{progress === steps.length - 1 && <p>✓ All done!</p>}
</div>
);
}Setup Wizard
Guided application setup with optional steps:
function SetupWizard() {
const [setupConfig, setSetupConfig] = useState({
basicSetup: false,
advancedSetup: false,
integrations: false,
customization: false
});
const handleStepClick = (args) => {
console.log(`User viewing step: ${args.activeStep}`);
};
return (
<div className="setup-wizard">
<h2>Application Setup</h2>
<StepperComponent stepClick={handleStepClick} linear={false}>
<StepsDirective>
<StepDirective
label="Basic Configuration"
iconCss="sf-icon-settings"
text="1"
/>
<StepDirective
label="Advanced Settings"
iconCss="sf-icon-sliders"
text="2"
optional={true}
/>
<StepDirective
label="Integrations"
iconCss="sf-icon-connection"
text="3"
optional={true}
/>
<StepDirective
label="Customization"
iconCss="sf-icon-palette"
text="4"
optional={true}
/>
<StepDirective
label="Complete"
iconCss="sf-icon-check"
text="5"
/>
</StepsDirective>
</StepperComponent>
<div style={{ marginTop: '20px' }}>
<p>You can skip optional steps and come back later.</p>
<p>Completed steps: {Object.values(setupConfig).filter(v => v).length}</p>
</div>
</div>
);
}Performance Optimization
Lazy Load Content
function LazyLoadingStepper() {
const [loadedSteps, setLoadedSteps] = useState([0]); // Initially load first step
const handleStepChanged = (args) => {
// Load step content on demand
if (!loadedSteps.includes(args.activeStep)) {
setLoadedSteps([...loadedSteps, args.activeStep]);
console.log(`Loading content for step ${args.activeStep}`);
}
};
const loadStepContent = (stepIndex) => {
if (!loadedSteps.includes(stepIndex)) {
return <p>Loading...</p>;
}
return <p>Content for step {stepIndex + 1}</p>;
};
return (
<>
<StepperComponent stepChanged={handleStepChanged}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>
<div>
{loadStepContent(0)}
</div>
</>
);
}Memoize Template
import React, { useMemo } from 'react';
function StepperWithMemoTemplate() {
const memoTemplate = useMemo(() => {
return ({ step, currentStep }) => (
<div className="step-template">
<span className={step.iconCss}></span>
<span>{step.label}</span>
</div>
);
}, []);
return (
<StepperComponent template={memoTemplate}>
<StepsDirective>
{/* steps */}
</StepsDirective>
</StepperComponent>
);
}Common Gotchas
Gotcha 1: Linear Mode Still Allows Backward Navigation
Problem: Linear mode doesn't prevent going back
// This still allows backward movement
<StepperComponent linear={true}>Solution: Use stepChanging event to prevent backward:
const handleStepChanging = (args) => {
if (args.activeStep < args.previousStep) {
args.cancel = true; // Prevent going back
}
};
<StepperComponent linear={true} stepChanging={handleStepChanging}>Gotcha 2: State Not Syncing with Active Step
Problem: Component shows step 1 but state says step 3
// DON'T: State and component out of sync
const [activeStep, setActiveStep] = useState(3);
<StepperComponent activeStep={activeStep}>Solution: Use ref for direct access or keep synchronized:
// DO: Use ref for stepper
const stepperRef = useRef(null);
const handleNext = () => {
stepperRef.current.activeStep += 1;
};Gotcha 3: Validation State Not Visual
Problem: isValid set but no visual change
// isValid state doesn't auto-update visually
isValid={formData.email ? true : false}Solution: Update within stepChanging event:
const handleStepChanging = (args) => {
const newValidation = [...validation];
newValidation[args.previousStep].isValid = isFieldValid(args.previousStep);
setValidation(newValidation);
};Gotcha 4: Template Not Updating
Problem: Template shows old data
// Template captures data at render time
const getTemplate = ({ step }) => {
// This might be stale
return <div>{externalData.value}</div>;
};Solution: Include dependencies in template function:
const getTemplate = ({ step }) => {
// This will update when externalData changes
return <div>{externalData.value}</div>;
};
// Ensure component re-renders: pass dependencies
<StepperComponent key={externalData.id} template={getTemplate}>Gotcha 5: Events Firing Multiple Times
Problem: Event handlers called unexpectedly
// Event might fire multiple times during renders
const handleStepChanged = (args) => {
fetchData(); // Fires too often
};Solution: Use useCallback and refs:
const handleStepChanged = useCallback((args) => {
if (lastStepRef.current !== args.activeStep) {
fetchData();
lastStepRef.current = args.activeStep;
}
}, []);Best Practices Summary
Performance:
- ✅ Use ref access for frequent updates
- ✅ Lazy load step content
- ✅ Memoize template functions
- ✅ Debounce event handlers if needed
UX:
- ✅ Provide clear validation feedback
- ✅ Show progress indication
- ✅ Allow reviewing completed steps
- ✅ Mark optional steps clearly
Code:
- ✅ Keep validation logic DRY
- ✅ Test keyboard navigation
- ✅ Handle edge cases (empty steps, rapid clicks)
- ✅ Provide meaningful error messages
Animation, Template, and Tooltip
Table of Contents
Animation
Animate the stepper progress state with smooth transitions between steps.
Enabling Animation
Animation is enabled by default. Control it with the animation property:
<StepperComponent animation={{ enable: true, duration: 2000, delay: 0 }}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>Animation Properties
| Property | Type | Default | Description |
|---|---|---|---|
enable | boolean | true | Enable/disable animations |
duration | number | 2000 | Animation duration in milliseconds |
delay | number | 0 | Delay before animation starts (milliseconds) |
Disabling Animation
<StepperComponent animation={{ enable: false }}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>Custom Animation Configuration
// Fast animation: 500ms
<StepperComponent animation={{ enable: true, duration: 500, delay: 0 }}>
{/* steps */}
</StepperComponent>
// Slow animation: 3000ms with 200ms delay
<StepperComponent animation={{ enable: true, duration: 3000, delay: 200 }}>
{/* steps */}
</StepperComponent>
// No animation, instant transition
<StepperComponent animation={{ enable: false }}>
{/* steps */}
</StepperComponent>Practical Example: Progressive Animation
function App() {
const [animationSpeed, setAnimationSpeed] = useState(2000);
return (
<div>
<div>
<label>Animation Speed (ms):</label>
<input
type="number"
value={animationSpeed}
onChange={(e) => setAnimationSpeed(parseInt(e.target.value))}
min="0"
step="100"
/>
</div>
<StepperComponent
animation={{
enable: true,
duration: animationSpeed,
delay: 0
}}
>
<StepsDirective>
<StepDirective label="Cart" />
<StepDirective label="Delivery" />
<StepDirective label="Payment" />
</StepsDirective>
</StepperComponent>
</div>
);
}Template Customization
Customize the appearance of steps using custom templates.
Basic Template
Define a template function that receives the step object:
function App() {
const getTemplate = ({ step, currentStep }) => {
return (
<div className="custom-step">
<span className={step.iconCss}></span>
<span className="label">{step.label}</span>
</div>
);
};
return (
<StepperComponent template={getTemplate}>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
</StepsDirective>
</StepperComponent>
);
}Template Context
The template function receives:
step: Current step object with properties (label, iconCss, text, etc.)currentStep: Index of the current step in the loop
Advanced Template with Status
function App() {
const getTemplate = ({ step, currentStep }) => {
let statusClass = 'pending';
if (step.isValid === true) {
statusClass = 'completed';
} else if (step.isValid === false) {
statusClass = 'error';
}
return (
<div className={`template-step ${statusClass}`}>
<div className="step-header">
<span className={step.iconCss}></span>
<span className="step-number">{currentStep + 1}</span>
</div>
<div className="step-content">
<p className="step-label">{step.label}</p>
<p className="step-description">
{statusClass === 'completed' && '✓ Completed'}
{statusClass === 'error' && '✗ Error'}
{statusClass === 'pending' && '○ Pending'}
</p>
</div>
</div>
);
};
return (
<StepperComponent template={getTemplate}>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" isValid={true} />
<StepDirective iconCss="sf-icon-transport" label="Delivery" isValid={false} />
<StepDirective iconCss="sf-icon-payment" label="Payment" isValid={null} />
</StepsDirective>
</StepperComponent>
);
}CSS for Template
.template-step {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
border-radius: 8px;
transition: all 0.3s ease;
}
.template-step.completed {
background-color: #e8f5e9;
color: #2e7d32;
}
.template-step.error {
background-color: #ffebee;
color: #c62828;
}
.template-step.pending {
background-color: #f5f5f5;
color: #666;
}
.step-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: bold;
}
.step-content {
margin-top: 8px;
text-align: center;
font-size: 12px;
}
.step-label {
margin: 0;
font-weight: 600;
}
.step-description {
margin: 4px 0 0 0;
font-size: 11px;
opacity: 0.8;
}Tooltip Integration
Add tooltips to steps for additional information:
Using Title Attribute
<StepperComponent>
<StepsDirective>
<StepDirective
iconCss="sf-icon-cart"
label="Cart"
title="Review and modify your shopping cart"
/>
<StepDirective
iconCss="sf-icon-transport"
label="Delivery"
title="Enter your shipping address"
/>
<StepDirective
iconCss="sf-icon-payment"
label="Payment"
title="Choose your payment method"
/>
</StepsDirective>
</StepperComponent>Custom Tooltip with Syncfusion Tooltip Component
import { TooltipComponent } from '@syncfusion/ej2-react-popups';
function App() {
return (
<StepperComponent>
<StepsDirective>
<StepDirective
iconCss="sf-icon-cart"
label="Cart"
/>
<StepDirective
iconCss="sf-icon-transport"
label="Delivery"
/>
</StepsDirective>
</StepperComponent>
);
}Tooltip via Template
function App() {
const getTemplate = ({ step, currentStep }) => {
const tooltips = [
'Review items in your cart',
'Enter shipping address',
'Select payment method',
'Confirm your order'
];
return (
<div className="step-with-tooltip">
<span className={step.iconCss}></span>
<span className="step-label">{step.label}</span>
<div className="tooltip-hint" title={tooltips[currentStep]}>
?
</div>
</div>
);
};
return (
<StepperComponent template={getTemplate}>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirm" />
</StepsDirective>
</StepperComponent>
);
}CSS for Tooltip
.step-with-tooltip {
display: flex;
align-items: center;
gap: 8px;
position: relative;
}
.tooltip-hint {
width: 18px;
height: 18px;
border-radius: 50%;
background-color: #2196F3;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
cursor: help;
}
.tooltip-hint:hover::after {
content: attr(title);
position: absolute;
bottom: -30px;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: white;
padding: 5px 10px;
border-radius: 4px;
white-space: nowrap;
z-index: 1000;
font-size: 12px;
}Advanced Customization
Combining Animation, Template, and Tooltip
function App() {
const [activeStep, setActiveStep] = useState(0);
const getTemplate = ({ step, currentStep }) => {
const helpText = [
'Add items to your cart',
'Provide delivery details',
'Choose payment option',
'Review and submit'
];
return (
<div className="advanced-step-template">
<div className="step-indicator">
<span className={step.iconCss}></span>
<span className="step-number">{currentStep + 1}</span>
</div>
<div className="step-info">
<p className="step-title">{step.label}</p>
<p className="step-help">{helpText[currentStep]}</p>
</div>
</div>
);
};
return (
<StepperComponent
activeStep={activeStep}
template={getTemplate}
animation={{ enable: true, duration: 1500, delay: 100 }}
stepChanged={(args) => setActiveStep(args.activeStep)}
>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>
);
}Advanced CSS
.advanced-step-template {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-radius: 6px;
transition: all 0.3s ease;
}
.advanced-step-template:hover {
background-color: #f0f0f0;
}
.step-indicator {
position: relative;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
border-radius: 50%;
}
.step-number {
position: absolute;
font-weight: bold;
font-size: 14px;
}
.step-info {
flex: 1;
}
.step-title {
margin: 0;
font-weight: 600;
font-size: 14px;
}
.step-help {
margin: 4px 0 0 0;
font-size: 12px;
color: #999;
}Troubleshooting
Issue: Animation too slow/fast
- ✅ Adjust
durationproperty (in milliseconds) - ✅ Typical range: 500-3000ms
Issue: Template not rendering
- ✅ Verify template function returns JSX
- ✅ Check that step properties are accessible
- ✅ Ensure no console errors
Issue: Tooltip not showing
- ✅ Verify
titleattribute is set on step - ✅ Check CSS z-index if tooltip is hidden behind other elements
- ✅ Ensure parent container overflow is not hidden
Events and Interactions
Table of Contents
- Event Types
- Created Event
- Step Changed Event
- Step Changing Event
- Step Click Event
- Before Step Render Event
- Event Handling Patterns
Event Types
The Stepper component triggers five main events during its lifecycle and user interactions:
| Event | When It Fires | Purpose |
|---|---|---|
created | Component initialization complete | Setup, initialization logic |
stepChanged | After step has changed | Update UI, load content |
stepChanging | Before step is about to change | Validate, prevent transitions |
stepClick | User clicks a step | Track interactions, analytics |
beforeStepRender | Before rendering each step | Customize step appearance |
Created Event
Fires when the Stepper component has finished rendering and is ready for interaction.
Basic Usage
function App() {
const handleCreated = () => {
console.log('Stepper component created and ready');
};
return (
<StepperComponent created={handleCreated}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
</StepsDirective>
</StepperComponent>
);
}Practical Example: Initialization
function App() {
const stepperRef = React.useRef(null);
const handleCreated = () => {
// Set initial properties after creation
if (stepperRef.current) {
console.log('Stepper initialized with', stepperRef.current.steps.length, 'steps');
}
};
return (
<StepperComponent ref={stepperRef} created={handleCreated}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>
);
}Step Changed Event
Fires after the active step has successfully changed.
Event Arguments
interface StepperChangedEventArgs {
activeStep: number; // Index of the new active step
previousStep: number; // Index of the previous step
isInteracted: boolean; // true if user interacted, false if programmatic
name: string; // Event name: "stepChanged"
event: Event; // DOM event object
element?: HTMLElement; // The changed step element
}Basic Usage
function App() {
const handleStepChanged = (args) => {
console.log(`Step changed from ${args.previousStep} to ${args.activeStep}`);
};
return (
<StepperComponent stepChanged={handleStepChanged}>
<StepsDirective>
<StepDirective label="Cart" />
<StepDirective label="Delivery" />
<StepDirective label="Payment" />
</StepsDirective>
</StepperComponent>
);
}Practical Example: Load Content Based on Step
function App() {
const [stepContent, setStepContent] = React.useState('');
const handleStepChanged = (args) => {
const contents = [
'Review your shopping cart',
'Enter shipping address',
'Select payment method'
];
setStepContent(contents[args.activeStep] || '');
};
return (
<div>
<StepperComponent stepChanged={handleStepChanged}>
<StepsDirective>
<StepDirective label="Cart" />
<StepDirective label="Delivery" />
<StepDirective label="Payment" />
</StepsDirective>
</StepperComponent>
<div className="content-panel">
{stepContent}
</div>
</div>
);
}Step Changing Event
Fires before the step is about to change. Cancel the navigation to prevent the change.
Event Arguments
interface StepperChangingEventArgs {
activeStep: number; // Index of the step being changed to
previousStep: number; // Index of the current step
cancel: boolean; // Set to true to prevent the change
isInteracted: boolean; // true if user interacted, false if programmatic
name: string; // Event name: "stepChanging"
event: Event; // DOM event object
element?: HTMLElement; // The step element being changed to
}Basic Usage
function App() {
const handleStepChanging = (args) => {
console.log(`Attempting to change from ${args.previousStep} to ${args.activeStep}`);
};
return (
<StepperComponent stepChanging={handleStepChanging}>
<StepsDirective>
<StepDirective label="Cart" />
<StepDirective label="Delivery" />
<StepDirective label="Payment" />
</StepsDirective>
</StepperComponent>
);
}Practical Example: Validation Before Transition
function App() {
const [formData, setFormData] = React.useState({
email: '',
address: '',
payment: ''
});
const handleStepChanging = (args) => {
if (args.previousStep === 0 && !formData.email) {
alert('Please enter your email address');
args.cancel = true; // Prevent transition
}
if (args.previousStep === 1 && !formData.address) {
alert('Please enter your shipping address');
args.cancel = true;
}
if (args.previousStep === 2 && !formData.payment) {
alert('Please select a payment method');
args.cancel = true;
}
};
return (
<StepperComponent stepChanging={handleStepChanging}>
<StepsDirective>
<StepDirective label="Contact Info" />
<StepDirective label="Shipping" />
<StepDirective label="Payment" />
<StepDirective label="Review" />
</StepsDirective>
</StepperComponent>
);
}Complex Validation Pattern
function validateStep(stepIndex, formData) {
const validations = [
{ field: 'email', errorMsg: 'Email is required' },
{ field: 'address', errorMsg: 'Address is required' },
{ field: 'payment', errorMsg: 'Payment method is required' }
];
if (stepIndex < validations.length) {
const validation = validations[stepIndex];
if (!formData[validation.field]) {
return { valid: false, message: validation.errorMsg };
}
}
return { valid: true };
}
const handleStepChanging = (args) => {
const validation = validateStep(args.previousStep, formData);
if (!validation.valid) {
alert(validation.message);
args.cancel = true;
}
};Step Click Event
Fires when the user clicks on a step.
Event Arguments
interface StepperClickEventArgs {
activeStep: number; // Index of the clicked step
name: string; // Event name: "stepClick"
event: Event; // DOM event object
element?: HTMLElement; // The clicked step element
}Basic Usage
function App() {
const handleStepClick = (args) => {
console.log(`User clicked on step ${args.activeStep}`);
};
return (
<StepperComponent stepClick={handleStepClick}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>
);
}Practical Example: Analytics Tracking
function App() {
const handleStepClick = (args) => {
// Track user interaction for analytics
analytics.track('step_clicked', {
stepIndex: args.activeStep,
timestamp: new Date().toISOString()
});
};
return (
<StepperComponent stepClick={handleStepClick}>
<StepsDirective>
<StepDirective label="Cart" />
<StepDirective label="Delivery" />
<StepDirective label="Payment" />
</StepsDirective>
</StepperComponent>
);
}Before Step Render Event
Fires before each step is rendered, allowing you to customize the step appearance.
Event Arguments
interface StepperRenderingEventArgs {
activeStep: number; // Index of the step being rendered
name: string; // Event name: "beforeStepRender"
element?: HTMLElement; // The step element being rendered
}Basic Usage
function App() {
const handleBeforeStepRender = (args) => {
console.log(`Rendering step ${args.currentStep}`);
};
return (
<StepperComponent beforeStepRender={handleBeforeStepRender}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
</StepsDirective>
</StepperComponent>
);
}Practical Example: Dynamic Styling
function App() {
const handleBeforeStepRender = (args) => {
if (args.element) {
// Add custom classes based on step index
if (args.activeStep === 0) {
args.element.classList.add('first-step');
} else if (args.activeStep === 3) {
args.element.classList.add('last-step');
}
}
};
return (
<StepperComponent beforeStepRender={handleBeforeStepRender}>
<StepsDirective>
<StepDirective label="Start" />
<StepDirective label="Middle 1" />
<StepDirective label="Middle 2" />
<StepDirective label="Complete" />
</StepsDirective>
</StepperComponent>
);
}Event Handling Patterns
Pattern 1: Multiple Events Combined
function App() {
const [status, setStatus] = React.useState('');
const handleCreated = () => {
setStatus('Stepper ready');
};
const handleStepChanging = (args) => {
setStatus(`Validating transition from step ${args.previousStep}...`);
};
const handleStepChanged = (args) => {
setStatus(`Now on step ${args.activeStep}`);
};
return (
<div>
<StepperComponent
created={handleCreated}
stepChanging={handleStepChanging}
stepChanged={handleStepChanged}
>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>
<p>Status: {status}</p>
</div>
);
}Pattern 2: Conditional Event Handling
function App() {
const stepperRef = React.useRef(null);
const handleStepClick = (args) => {
// Only allow clicking completed steps
if (args.activeStep > currentStep) {
stepperRef.current.activeStep = currentStep;
}
};
return (
<StepperComponent ref={stepperRef} stepClick={handleStepClick}>
{/* steps */}
</StepperComponent>
);
}Pattern 3: Preventing Backward Navigation
function App() {
const handleStepChanging = (args) => {
// Prevent users from going back
if (args.activeStep < args.previousStep) {
args.cancel = true;
alert('You cannot go back in this wizard');
}
};
return (
<StepperComponent stepChanging={handleStepChanging}>
{/* steps */}
</StepperComponent>
);
}Troubleshooting
Issue: Event handler not firing
- ✅ Verify event name is spelled correctly (e.g.,
stepChanged, notonStepChanged) - ✅ Ensure handler function is properly defined
- ✅ Check that the event trigger condition is met
Issue: Cancel not working in stepChanging
- ✅ Verify
args.cancel = trueis set before event completes - ✅ Check that step is not disabled
- ✅ Ensure linear mode isn't preventing the behavior
Getting Started with Syncfusion React Stepper
Table of Contents
Installation
Step 1: Install via npm
The Stepper component is part of the @syncfusion/ej2-react-navigations package. Install it using npm:
npm install @syncfusion/ej2-react-navigations --saveStep 2: Verify Dependencies
The Stepper component depends on the following packages, which are installed automatically:
@syncfusion/ej2-base@syncfusion/ej2-popups@syncfusion/ej2-navigations@syncfusion/ej2-react-base
You can verify the installation by checking your package.json:
{
"dependencies": {
"@syncfusion/ej2-react-navigations": "^latest"
}
}CSS Setup
Import Required Styles
Import the necessary CSS files in your main application file (typically src/App.css or src/App.jsx):
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css";Available Themes:
tailwind3.css(default, modern minimal design)bootstrap5.3.css(Bootstrap 5 styling)fluent2.css(Fluent Design System)material3.css(Material Design 3)
Choose one theme file based on your design preference. Replace tailwind3.css with your chosen theme.
Alternative: CSS Modules
If using CSS modules, import in your component:
import '@syncfusion/ej2-base/styles/tailwind3.css';
import '@syncfusion/ej2-navigations/styles/tailwind3.css';Creating Your First Stepper
Basic Example: Simple Checkout Flow
import React from 'react';
import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
import './App.css';
function App() {
return (
<div className="app-container">
<h2>Checkout Process</h2>
<StepperComponent>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>
</div>
);
}
export default App;Example with Text Instead of Icons
<StepperComponent>
<StepsDirective>
<StepDirective text="1" label="Account" />
<StepDirective text="2" label="Profile" />
<StepDirective text="3" label="Verification" />
<StepDirective text="4" label="Complete" />
</StepsDirective>
</StepperComponent>Example with Active Step
Set the initial active step using the activeStep property:
<StepperComponent activeStep={1}>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>Property-Based Pattern: Using steps Array
You can also use the steps property array instead of StepsDirective and StepDirective:
import React from 'react';
import { StepperComponent } from '@syncfusion/ej2-react-navigations';
import './App.css';
function App() {
const steps = [
{ iconCss: 'sf-icon-cart', label: 'Cart' },
{ iconCss: 'sf-icon-transport', label: 'Delivery' },
{ iconCss: 'sf-icon-payment', label: 'Payment' },
{ iconCss: 'sf-icon-success', label: 'Confirmation' }
];
return (
<div className="app-container">
<h2>Checkout Process</h2>
<StepperComponent steps={steps} activeStep={0} />
</div>
);
}
export default App;Running the Application
With Vite (Recommended)
npm create vite@latest my-app -- --template react
cd my-app
npm install @syncfusion/ej2-react-navigations --save
npm run devOpen url in your browser.
With Create React App
npx create-react-app my-app
cd my-app
npm install @syncfusion/ej2-react-navigations --save
npm startOpen url in your browser.
Quick Configuration
Horizontal Stepper (Default)
<StepperComponent orientation="horizontal">
{/* steps */}
</StepperComponent>Vertical Stepper
<StepperComponent orientation="vertical">
{/* steps */}
</StepperComponent>Linear Navigation (Step-by-Step)
<StepperComponent linear={true}>
{/* Users must complete each step sequentially */}
</StepperComponent>Readonly Stepper (View-Only)
<StepperComponent readOnly={true}>
{/* Users cannot interact with the stepper */}
</StepperComponent>Troubleshooting
Issue: Stepper is not displaying
- ✅ Verify CSS imports are included in your app
- ✅ Check that
@syncfusion/ej2-react-navigationsis installed - ✅ Ensure
StepperComponent,StepsDirective, andStepDirectiveare imported
Issue: Styles look different than expected
- ✅ Verify you're using the correct theme CSS file
- ✅ Check browser DevTools for CSS conflicts
- ✅ Ensure CSS imports are in the correct order
Issue: Step icons not showing
- ✅ Install Syncfusion icon fonts or use custom CSS classes
- ✅ Verify
iconCssproperty values are valid CSS class names - ✅ Add icon CSS files to your imports if using custom icon sets
Linear Flow and Validation
Table of Contents
- Linear Stepper
- Non-Linear Navigation
- Step Validation States
- Combining Linear Flow with Validation
- Resetting Stepper
Linear Stepper
Linear steppers enforce sequential navigation, requiring users to complete each step before advancing to the next one. This is ideal for guided workflows like wizards.
Enabling Linear Mode
<StepperComponent linear={true}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
<StepDirective label="Step 4" />
</StepsDirective>
</StepperComponent>Behavior:
- Users can only advance to the next step
- Users cannot skip steps
- Users cannot navigate to completed steps unless they go backward sequentially
- Each step must be marked as valid to proceed
Linear Checkout Flow Example
import React, { useState } from 'react';
import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function CheckoutWizard() {
const [formData, setFormData] = useState({
email: '',
address: '',
payment: ''
});
const handleStepChanging = (args) => {
// Validate current step before allowing transition
if (args.previousStep === 0 && !formData.email) {
alert('Please enter your email');
args.cancel = true;
}
if (args.previousStep === 1 && !formData.address) {
alert('Please enter your address');
args.cancel = true;
}
if (args.previousStep === 2 && !formData.payment) {
alert('Please select a payment method');
args.cancel = true;
}
};
return (
<StepperComponent linear={true} stepChanging={handleStepChanging}>
<StepsDirective>
<StepDirective label="Email" />
<StepDirective label="Address" />
<StepDirective label="Payment" />
<StepDirective label="Review" />
</StepsDirective>
</StepperComponent>
);
}Non-Linear Navigation
Non-linear steppers allow users to navigate freely between steps without enforcing order.
Enabling Non-Linear Mode
<StepperComponent linear={false}>
<StepsDirective>
<StepDirective label="Contact" />
<StepDirective label="Shipping" />
<StepDirective label="Payment" />
<StepDirective label="Review" />
</StepsDirective>
</StepperComponent>Behavior (Default):
- Users can click any step to jump to it
- Users can go forward and backward freely
- No forced step order
- Ideal for forms where all steps are independent
Non-Linear Example: Multi-Tab Form
function MultiTabForm() {
return (
<StepperComponent linear={false}>
<StepsDirective>
<StepDirective iconCss="sf-icon-user" label="Personal Info" />
<StepDirective iconCss="sf-icon-building" label="Company Details" />
<StepDirective iconCss="sf-icon-settings" label="Preferences" />
<StepDirective iconCss="sf-icon-save" label="Review & Submit" />
</StepsDirective>
</StepperComponent>
);
}Switching Between Modes Dynamically
function App() {
const [isLinear, setIsLinear] = useState(true);
const stepperRef = React.useRef(null);
const toggleLinearMode = () => {
setIsLinear(!isLinear);
// Reset stepper when switching modes
if (stepperRef.current) {
stepperRef.current.activeStep = 0;
}
};
return (
<div>
<label>
<input
type="checkbox"
checked={isLinear}
onChange={toggleLinearMode}
/>
Linear Mode
</label>
<StepperComponent ref={stepperRef} linear={isLinear}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>
</div>
);
}Step Validation States
Mark steps as valid or invalid to indicate completion status:
Validation Properties
<StepperComponent>
<StepsDirective>
<StepDirective
label="Completed"
isValid={true}
/>
<StepDirective
label="Error"
isValid={false}
/>
<StepDirective
label="Pending"
isValid={null}
/>
</StepsDirective>
</StepperComponent>Values:
true- Step is valid, shows checkmark/success iconfalse- Step has error, shows cross/error iconnull- Step is pending, shows default indicator
Visual Indicators
The validation state displays differently based on step type:
// Default type: Validation icon appears in indicator
<StepperComponent stepType="Default">
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" isValid={true} />
<StepDirective iconCss="sf-icon-transport" label="Delivery" isValid={false} />
</StepsDirective>
</StepperComponent>
// Label type: Validation icon appears with label
<StepperComponent stepType="Label">
<StepsDirective>
<StepDirective label="Cart" isValid={true} />
<StepDirective label="Delivery" isValid={false} />
</StepsDirective>
</StepperComponent>
// Indicator type: Only validation icon shows
<StepperComponent stepType="Indicator">
<StepsDirective>
<StepDirective text="1" isValid={true} />
<StepDirective text="2" isValid={false} />
</StepsDirective>
</StepperComponent>Dynamic Validation Based on User Input
function App() {
const [steps, setSteps] = useState([
{ label: 'Email', isValid: null },
{ label: 'Address', isValid: null },
{ label: 'Payment', isValid: null }
]);
const [formData, setFormData] = useState({
email: '',
address: '',
payment: ''
});
const validateEmail = (value) => {
const newSteps = [...steps];
newSteps[0].isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? true : false;
setSteps(newSteps);
};
const handleEmailChange = (e) => {
const value = e.target.value;
setFormData({ ...formData, email: value });
validateEmail(value);
};
return (
<div>
<StepperComponent>
<StepsDirective>
{steps.map((step, index) => (
<StepDirective key={index} label={step.label} isValid={step.isValid} />
))}
</StepsDirective>
</StepperComponent>
<input
type="email"
value={formData.email}
onChange={handleEmailChange}
placeholder="Enter email"
/>
</div>
);
}Combining Linear Flow with Validation
Enforce sequential navigation only for valid steps:
function App() {
const stepperRef = React.useRef(null);
const [stepValidation, setStepValidation] = useState([
{ label: 'Step 1', isValid: null },
{ label: 'Step 2', isValid: null },
{ label: 'Step 3', isValid: null }
]);
const handleStepChanging = (args) => {
// Only allow transition if current step is valid
const currentValidation = stepValidation[args.previousStep];
if (currentValidation.isValid === false) {
alert('Please fix the errors in the current step');
args.cancel = true;
}
};
const validateStep = (stepIndex) => {
const newValidation = [...stepValidation];
newValidation[stepIndex].isValid = true;
setStepValidation(newValidation);
};
const invalidateStep = (stepIndex) => {
const newValidation = [...stepValidation];
newValidation[stepIndex].isValid = false;
setStepValidation(newValidation);
};
return (
<div>
<StepperComponent
ref={stepperRef}
linear={true}
stepChanging={handleStepChanging}
>
<StepsDirective>
{stepValidation.map((step, index) => (
<StepDirective
key={index}
label={step.label}
isValid={step.isValid}
/>
))}
</StepsDirective>
</StepperComponent>
<button onClick={() => validateStep(stepperRef.current.activeStep)}>
Complete Step
</button>
<button onClick={() => invalidateStep(stepperRef.current.activeStep)}>
Mark Error
</button>
</div>
);
}Resetting Stepper
Use the reset() method to return the stepper to its initial state:
Basic Reset
function App() {
const stepperRef = React.useRef(null);
const handleReset = () => {
stepperRef.current.reset();
};
return (
<div>
<StepperComponent ref={stepperRef}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>
<button onClick={handleReset}>Reset Stepper</button>
</div>
);
}Reset with Form Clear
function App() {
const stepperRef = React.useRef(null);
const [formData, setFormData] = useState({
field1: '',
field2: '',
field3: ''
});
const handleResetWizard = () => {
// Reset stepper
stepperRef.current.reset();
// Clear form data
setFormData({
field1: '',
field2: '',
field3: ''
});
// Show confirmation
alert('Wizard has been reset');
};
return (
<div>
<StepperComponent ref={stepperRef} linear={true}>
<StepsDirective>
<StepDirective label="Information" />
<StepDirective label="Details" />
<StepDirective label="Confirm" />
</StepsDirective>
</StepperComponent>
<input
value={formData.field1}
onChange={(e) => setFormData({ ...formData, field1: e.target.value })}
/>
<button onClick={handleResetWizard}>Reset All</button>
</div>
);
}Best Practices
Linear Mode:
- ✅ Use for guided workflows and wizards
- ✅ Combine with validation on
stepChangingevent - ✅ Provide clear validation feedback
- ✅ Allow optional steps with
optional={true}
Non-Linear Mode:
- ✅ Use for independent form sections
- ✅ Ideal for settings or configuration panels
- ✅ Allows users to review and edit any section
Validation:
- ✅ Use
isValid={true}for completed steps - ✅ Use
isValid={false}for steps with errors - ✅ Use
isValid={null}for pending steps - ✅ Update validation as user fills form
# Methods and Advanced Patterns
## Table of Contents
- [Component Methods](#component-methods)
- [reset()](#reset)
- [nextStep()](#nextstep)
- [previousStep()](#previousstep)
- [refreshProgressbar()](#refreshprogressbar)
- [destroy()](#destroy)
- [Both API Patterns - Complete Guide](#both-api-patterns---complete-guide)
- [Advanced Pattern Examples](#advanced-pattern-examples)
- [Performance Optimization](#performance-optimization)
- [Common Gotchas](#common-gotchas)
## Component Methods
### reset()
Reset the Stepper component to its initial state. This method sets the active step to 0 and clears any step states.
**Syntax:**stepperRef.current.reset(): void
**Example - Component Pattern:**import React, { useRef } from 'react'; import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function App() { const stepperRef = useRef(null);
const handleReset = () => { stepperRef.current.reset(); };
return ( <div> <StepperComponent ref={stepperRef}> <StepsDirective> <StepDirective label="Step 1" /> <StepDirective label="Step 2" /> <StepDirective label="Step 3" /> </StepsDirective> </StepperComponent> <button onClick={handleReset}>Reset Stepper</button> </div> ); }
export default App;
**Example - Property Pattern:**import React, { useRef } from 'react'; import { StepperComponent } from '@syncfusion/ej2-react-navigations';
function App() { const stepperRef = useRef(null); const steps = [ { label: 'Step 1' }, { label: 'Step 2' }, { label: 'Step 3' } ];
const handleReset = () => { stepperRef.current.reset(); };
return ( <div> <StepperComponent ref={stepperRef} steps={steps} /> <button onClick={handleReset}>Reset Stepper</button> </div> ); }
export default App;
**Use Cases:**
- Reset multi-step form after submission
- Clear wizard state when user clicks "Start Over"
- Reset state on navigation or modal close
### nextStep()
Move to the next step from the current step in the Stepper.
**Syntax:**stepperRef.current.nextStep(): void
**Example:**import React, { useRef } from 'react'; import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function App() { const stepperRef = useRef(null);
const handleNextStep = () => { stepperRef.current.nextStep(); };
return ( <div> <StepperComponent ref={stepperRef}> <StepsDirective> <StepDirective label="Step 1" /> <StepDirective label="Step 2" /> <StepDirective label="Step 3" /> </StepsDirective> </StepperComponent> <button onClick={handleNextStep}>Next Step</button> </div> ); }
export default App;
**Use Cases:**
- Navigate forward programmatically
- "Next" button functionality
- Automatic step progression after validation
### previousStep()
Move to the previous step from the current step in the Stepper.
**Syntax:**stepperRef.current.previousStep(): void
**Example:**import React, { useRef } from 'react'; import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function App() { const stepperRef = useRef(null);
const handlePreviousStep = () => { stepperRef.current.previousStep(); };
return ( <div> <StepperComponent ref={stepperRef}> <StepsDirective> <StepDirective label="Step 1" /> <StepDirective label="Step 2" /> <StepDirective label="Step 3" /> </StepsDirective> </StepperComponent> <button onClick={handlePreviousStep}>Previous Step</button> </div> ); }
export default App;
**Use Cases:**
- Navigate backward programmatically
- "Back" button functionality
- Allow users to edit previous steps
### refreshProgressbar()
Refreshes the position of the progress bar programmatically when the dimensions of the parent container are changed (e.g., on window resize).
**Syntax:**stepperRef.current.refreshProgressbar(): void
**Example:**import React, { useRef, useEffect } from 'react'; import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function App() { const stepperRef = useRef(null);
useEffect(() => { // Refresh progress bar on window resize const handleResize = () => { if (stepperRef.current) { stepperRef.current.refreshProgressbar(); } };
window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []);
return ( <StepperComponent ref={stepperRef}> <StepsDirective> <StepDirective label="Step 1" /> <StepDirective label="Step 2" /> <StepDirective label="Step 3" /> </StepsDirective> </StepperComponent> ); }
export default App;
**Example - Responsive Container:**import React, { useRef, useState } from 'react'; import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function App() { const stepperRef = useRef(null); const [containerWidth, setContainerWidth] = useState(100);
const handleWidthChange = (newWidth) => { setContainerWidth(newWidth); // Refresh progress bar after container width changes setTimeout(() => { if (stepperRef.current) { stepperRef.current.refreshProgressbar(); } }, 0); };
return ( <div> <div style={{ width: ${containerWidth}%, overflow: 'auto' }}> <StepperComponent ref={stepperRef}> <StepsDirective> <StepDirective label="Step 1" /> <StepDirective label="Step 2" /> <StepDirective label="Step 3" /> </StepsDirective> </StepperComponent> </div> <button onClick={() => handleWidthChange(50)}>50%</button> <button onClick={() => handleWidthChange(100)}>100%</button> </div> ); }
export default App;
**Use Cases:**
- Adjust progress bar on responsive layout changes
- Update layout after modal resize
- Recalculate position after DOM reflow
### destroy()
Destroy the Stepper control and release all resources.
**Syntax:**stepperRef.current.destroy(): void
**Example:**import React, { useRef, useState } from 'react'; import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function App() { const stepperRef = useRef(null); const [isDestroyed, setIsDestroyed] = useState(false);
const handleDestroy = () => { if (stepperRef.current) { stepperRef.current.destroy(); setIsDestroyed(true); } };
if (isDestroyed) { return <p>Stepper has been destroyed</p>; }
return ( <div> <StepperComponent ref={stepperRef}> <StepsDirective> <StepDirective label="Step 1" /> <StepDirective label="Step 2" /> <StepDirective label="Step 3" /> </StepsDirective> </StepperComponent> <button onClick={handleDestroy}>Destroy Stepper</button> </div> ); }
export default App;
**Example - Cleanup on Unmount:**import React, { useRef, useEffect } from 'react'; import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function StepperModal({ isOpen, onClose }) { const stepperRef = useRef(null);
useEffect(() => { // Cleanup when modal closes return () => { if (stepperRef.current && isOpen) { stepperRef.current.destroy(); } }; }, [isOpen]);
if (!isOpen) return null;
return ( <div className="modal"> <StepperComponent ref={stepperRef}> <StepsDirective> <StepDirective label="Step 1" /> <StepDirective label="Step 2" /> </StepsDirective> </StepperComponent> <button onClick={onClose}>Close</button> </div> ); }
export default StepperModal;
**Use Cases:**
- Clean up resources when component unmounts
- Remove event listeners
- Prepare for component disposal
- Free memory in Single Page Applications (SPAs)
### Methods Summary Table
| Method | Parameters | Returns | Purpose |
|--------|-----------|---------|---------|
| `reset()` | none | void | Reset to first step and clear state |
| `nextStep()` | none | void | Move to next step programmatically |
| `previousStep()` | none | void | Move to previous step programmatically |
| `refreshProgressbar()` | none | void | Update progress bar on container resize |
| `destroy()` | none | void | Destroy component and release resources |
## Both API Patterns - Complete Guide
### Pattern 1: Component-Based (StepsDirective)
**Advantages:**
- More flexible JSX control
- Easier to add conditional rendering of steps
- Better for complex workflows with dynamic steps
- Mix React logic directly in JSX
**Full Example:**import React, { useState } from 'react'; import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function CheckoutWizard() { const [formData, setFormData] = useState({ email: '', address: '', payment: '' });
const [completedSteps, setCompletedSteps] = useState({ 0: false, 1: false, 2: false });
const handleStepChanging = (args) => { // Validate before allowing transition if (args.activeStep === 0 && !formData.email) { args.cancel = true; alert('Please enter email'); } };
const handleStepChanged = (args) => { setCompletedSteps({ ...completedSteps, [args.previousStep]: true }); };
return ( <div> <StepperComponent stepChanging={handleStepChanging} stepChanged={handleStepChanged} > <StepsDirective> <StepDirective iconCss="sf-icon-mail" label="Email" status={completedSteps[0] ? 'Completed' : 'NotStarted'} /> <StepDirective iconCss="sf-icon-home" label="Address" status={completedSteps[1] ? 'Completed' : 'NotStarted'} /> <StepDirective iconCss="sf-icon-payment" label="Payment" status={completedSteps[2] ? 'Completed' : 'NotStarted'} /> </StepsDirective> </StepperComponent> </div> ); }
export default CheckoutWizard;
### Pattern 2: Property-Based (steps Array)
**Advantages:**
- Cleaner, more declarative code
- Better for data-driven steppers
- Easier to manage large step lists
- Better performance with many steps
**Full Example:**import React, { useState } from 'react'; import { StepperComponent } from '@syncfusion/ej2-react-navigations';
function CheckoutWizard() { const [activeStep, setActiveStep] = useState(0); const [stepStatus, setStepStatus] = useState(['NotStarted', 'NotStarted', 'NotStarted']);
const steps = [ { label: 'Email', iconCss: 'sf-icon-mail', status: stepStatus[0] }, { label: 'Address', iconCss: 'sf-icon-home', status: stepStatus[1] }, { label: 'Payment', iconCss: 'sf-icon-payment', status: stepStatus[2] } ];
const handleStepChanging = (args) => { if (args.activeStep === 0) { args.cancel = true; alert('Validate email first'); } };
const handleStepChanged = (args) => { // Update status const newStatus = [...stepStatus]; newStatus[args.previousStep] = 'Completed'; setStepStatus(newStatus); setActiveStep(args.activeStep); };
return ( <StepperComponent steps={steps} activeStep={activeStep} stepChanged={handleStepChanged} stepChanging={handleStepChanging} /> ); }
export default CheckoutWizard;
### Pattern Comparison in Real Code
| Scenario | Component Pattern | Property Pattern |
|----------|-------------------|------------------|
| **Conditional steps** | Easy: `{showOptional && <StepDirective ... />}` | Harder: Manage array filtering |
| **Many steps (50+)** | Less efficient | More efficient |
| **Data-driven steps** | Requires mapping logic | Natural fit |
| **Inline styling** | Mixed with JSX | Separated concerns |
## Advanced Pattern Examples
### Example 1: Linear Wizard with Validation
Using component pattern with linear flow:
import React, { useState, useRef } from 'react'; import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function LinearWizard() { const stepperRef = useRef(null); const [formErrors, setFormErrors] = useState({});
const validateStep = (stepIndex) => { const errors = {}; if (stepIndex === 0 && !formData.email) { errors.email = 'Email required'; } if (stepIndex === 1 && !formData.phone) { errors.phone = 'Phone required'; } setFormErrors(errors); return Object.keys(errors).length === 0; };
const handleStepChanging = (args) => { if (!validateStep(args.activeStep)) { args.cancel = true; } };
return ( <StepperComponent ref={stepperRef} linear={true} stepChanging={handleStepChanging} > <StepsDirective> <StepDirective label="Contact" iconCss="sf-icon-mail" /> <StepDirective label="Address" iconCss="sf-icon-home" /> <StepDirective label="Review" iconCss="sf-icon-check" /> </StepsDirective> </StepperComponent> ); }
export default LinearWizard;
### Example 2: Dynamic Steps from API
Using property pattern with fetched data:
import React, { useState, useEffect } from 'react'; import { StepperComponent } from '@syncfusion/ej2-react-navigations';
function DynamicStepper() { const [steps, setSteps] = useState([]); const [activeStep, setActiveStep] = useState(0);
useEffect(() => { // Fetch steps from API fetch('/api/workflow-steps') .then(res => res.json()) .then(data => setSteps(data)) .catch(err => console.error(err)); }, []);
const handleStepChanged = (args) => { setActiveStep(args.activeStep); // Update step status based on business logic };
if (steps.length === 0) { return <div>Loading...</div>; }
return ( <StepperComponent steps={steps} activeStep={activeStep} stepChanged={handleStepChanged} /> ); }
export default DynamicStepper;
### Example 3: Conditional Steps with Component Pattern
import React, { useState } from 'react'; import { StepperComponent, StepsDirective, StepDirective } from '@syncfusion/ej2-react-navigations';
function ConditionalStepper() { const [isExpress, setIsExpress] = useState(false);
return ( <div> <label> <input type="checkbox" checked={isExpress} onChange={(e) => setIsExpress(e.target.checked)} /> Express Checkout </label>
<StepperComponent> <StepsDirective> <StepDirective label="Cart" iconCss="sf-icon-cart" /> {isExpress && ( <StepDirective label="Express" iconCss="sf-icon-bolt" /> )} <StepDirective label="Payment" iconCss="sf-icon-payment" /> {!isExpress && ( <StepDirective label="Delivery" iconCss="sf-icon-transport" /> )} <StepDirective label="Confirm" iconCss="sf-icon-check" /> </StepsDirective> </StepperComponent> </div> ); }
export default ConditionalStepper;
## Performance Optimization
### Tip 1: Use Property Pattern for Large Lists
For 50+ steps, use the property-based pattern:
// ✅ Better performance const largeStepList = Array.from({ length: 100 }, (_, i) => ({ label: Step ${i + 1}, text: String(i + 1) }));
<StepperComponent steps={largeStepList} />
### Tip 2: Memoize Callbacks
const handleStepChanging = useCallback((args) => { // Validation logic }, [dependencies]);
<StepperComponent stepChanging={handleStepChanging} />
### Tip 3: Use enablePersistence for UX
<StepperComponent enablePersistence={true} steps={steps} />
## Common Gotchas
### Gotcha 1: activeStep Index Out of Range
❌ **Wrong:**<StepperComponent activeStep={999}> <StepsDirective> <StepDirective label="Step 1" /> <StepDirective label="Step 2" /> </StepsDirective> </StepperComponent>
✅ **Correct:**const activeStep = Math.min(userStep, totalSteps - 1); <StepperComponent activeStep={activeStep}>
### Gotcha 2: Changing steps Array Without Key
❌ **Wrong:**const steps = [ { label: 'Step 1' }, { label: 'Step 2' } ];
// Later modifying array... steps[0].label = 'Updated'; setSteps(steps); // Component won't update!
✅ **Correct:**const newSteps = [...steps]; newSteps[0].label = 'Updated'; setSteps(newSteps); // Triggers re-render
### Gotcha 3: Event Arguments Structure
❌ **Wrong:**const handleStepChanged = (args) => { console.log(args.step); // ❌ undefined };
✅ **Correct:**const handleStepChanged = (args) => { console.log(args.activeStep); // ✅ current step console.log(args.previousStep); // ✅ previous step console.log(args.isInteracted); // ✅ user interaction };
### Gotcha 4: Ref Access Before Mount
❌ **Wrong:**const stepperRef = useRef(null); stepperRef.current.reset(); // ❌ Error: ref is null
✅ **Correct:**const handleReset = () => { if (stepperRef.current) { stepperRef.current.reset(); // ✅ Check ref exists } };
## Troubleshooting
| Problem | Solution |
|---------|----------|
| Steps not rendering | Import `StepsDirective` and `StepDirective`; check `steps` prop exists |
| Events not firing | Verify event names (camelCase); check callback function signature |
| activeStep not changing | Verify index is 0-based and within range; check re-render triggers |
| reset() not working | Ensure using ref with `useRef`; call within event handler, not render |
| Performance issues | Switch to property pattern; memoize callbacks; check re-render count |
Orientations and Step Types
Table of Contents
Orientations
The Stepper supports two layout orientations: horizontal and vertical.
Horizontal Orientation (Default)
Steps are displayed in a left-to-right (or right-to-left) linear arrangement:
<StepperComponent orientation="horizontal">
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>Use Case: Wide screens, simple linear workflows, checkout flows
Vertical Orientation
Steps are displayed from top to bottom in a vertical stack:
<StepperComponent orientation="vertical">
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>Use Case: Mobile screens, complex workflows, space-constrained layouts
Step Types
The Stepper supports three different visual representations for steps:
Default Type (Icon + Label)
Displays both the icon and label for each step:
<StepperComponent stepType="Default">
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>Features:
- Most informative visual representation
- Clear labeling and iconography
- Best for complex workflows
Label Type (Label Only)
Displays only the step label, hiding icons:
<StepperComponent stepType="Label">
<StepsDirective>
<StepDirective label="Cart" />
<StepDirective label="Delivery" />
<StepDirective label="Payment" />
<StepDirective label="Confirmation" />
</StepsDirective>
</StepperComponent>Features:
- Minimal, text-focused design
- Good for text-heavy workflows
- Cleaner appearance without icons
Indicator Type (Icon Only)
Displays only the step indicator (icon or number), hiding labels:
<StepperComponent stepType="Indicator">
<StepsDirective>
<StepDirective text="1" />
<StepDirective text="2" />
<StepDirective text="3" />
<StepDirective text="4" />
</StepsDirective>
</StepperComponent>Features:
- Compact, space-efficient design
- Icons or numbered indicators
- Best for mobile screens or compact spaces
Label Positioning
Control where labels appear relative to the step indicator using the labelPosition property (only applies to Default and Label step types):
Top Position
<StepperComponent labelPosition="Top">
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
</StepsDirective>
</StepperComponent>Bottom Position (Default)
<StepperComponent labelPosition="Bottom">
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
</StepsDirective>
</StepperComponent>Start Position (Left/Right in RTL)
<StepperComponent labelPosition="Start">
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
</StepsDirective>
</StepperComponent>End Position (Right/Left in RTL)
<StepperComponent labelPosition="End">
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
</StepsDirective>
</StepperComponent>RTL Support
Enable right-to-left layout for Arabic, Hebrew, and other RTL languages using the enableRtl property:
<StepperComponent enableRtl={true}>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="السلة" />
<StepDirective iconCss="sf-icon-transport" label="التوصيل" />
<StepDirective iconCss="sf-icon-payment" label="الدفع" />
<StepDirective iconCss="sf-icon-success" label="التأكيد" />
</StepsDirective>
</StepperComponent>Effects:
- Steps flow right-to-left
- Labels positioned on opposite sides
- Navigation reversed
- Text direction automatically adjusted
Global RTL Configuration
Set RTL for your entire application:
import { enableRtl } from '@syncfusion/ej2-base';
enableRtl(true);Responsive Design
Responsive Orientation Switching
Automatically switch orientation based on screen size:
import React, { useState, useEffect } from 'react';
function App() {
const [orientation, setOrientation] = useState('horizontal');
useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 768) {
setOrientation('vertical');
} else {
setOrientation('horizontal');
}
};
window.addEventListener('resize', handleResize);
handleResize(); // Set initial orientation
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<StepperComponent orientation={orientation}>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>
);
}Responsive Step Type Switching
Change step type for mobile:
function App() {
const [stepType, setStepType] = useState('Default');
useEffect(() => {
const handleResize = () => {
setStepType(window.innerWidth < 640 ? 'Indicator' : 'Default');
};
window.addEventListener('resize', handleResize);
handleResize();
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<StepperComponent stepType={stepType}>
<StepsDirective>
<StepDirective text="1" label="Cart" />
<StepDirective text="2" label="Delivery" />
<StepDirective text="3" label="Payment" />
</StepsDirective>
</StepperComponent>
);
}CSS Media Queries for Custom Styling
/* Desktop */
@media (min-width: 768px) {
.e-stepper {
padding: 20px;
}
}
/* Tablet */
@media (min-width: 600px) and (max-width: 767px) {
.e-stepper {
padding: 15px;
}
}
/* Mobile */
@media (max-width: 599px) {
.e-stepper {
padding: 10px;
}
}Common Combinations
Mobile-Optimized Checkout
<StepperComponent
orientation="vertical"
stepType="Indicator"
labelPosition="End"
>
{/* steps */}
</StepperComponent>Desktop-Optimized Progress
<StepperComponent
orientation="horizontal"
stepType="Default"
labelPosition="Bottom"
>
{/* steps */}
</StepperComponent>Minimal Space-Efficient
<StepperComponent
orientation="horizontal"
stepType="Indicator"
labelPosition="Top"
>
{/* steps */}
</StepperComponent>Troubleshooting
Issue: Orientation not changing
- ✅ Verify
orientationproperty is set correctly ("horizontal" or "vertical") - ✅ Check CSS isn't forcing a specific layout
- ✅ Ensure component re-renders after state change
Issue: Labels overlapping
- ✅ Try different
labelPositionvalues - ✅ Use shorter label text
- ✅ Switch to
Indicatorstep type to hide labels
Issue: RTL not working
- ✅ Verify
enableRtl={true}is set - ✅ Check language/text is actually RTL
- ✅ Inspect with DevTools to confirm direction property
Steps and Configuration
Table of Contents
- API Patterns
- Adding Steps - Component Pattern
- Adding Steps - Property Pattern
- Step Properties
- Setting Active Step
- Step Status
- Disabled Steps
- Optional Steps
- Read-Only Mode
- CSS Class Customization
API Patterns
Syncfusion React Stepper supports two implementation patterns. Choose based on your preference:
Pattern Comparison
| Aspect | Component-Based | Property-Based |
|---|---|---|
| Syntax | Uses StepsDirective and StepDirective | Uses steps array property |
| Flexibility | More JSX control, easier to mix with React logic | Cleaner for simple lists |
| Performance | Better for dynamic step additions | Better for large step lists |
| Recommended | Complex wizards with conditional steps | Static or data-driven steppers |
Adding Steps - Component Pattern
Use the StepsDirective container and StepDirective components to add steps to your Stepper. Each StepDirective represents a single step in the workflow.
<StepperComponent>
<StepsDirective>
<StepDirective />
<StepDirective />
<StepDirective />
<StepDirective />
</StepsDirective>
</StepperComponent>Adding Steps - Property Pattern
Define steps as an array and pass to the steps property:
import React from 'react';
import { StepperComponent } from '@syncfusion/ej2-react-navigations';
function App() {
const steps = [
{ label: 'Step 1' },
{ label: 'Step 2' },
{ label: 'Step 3' },
{ label: 'Step 4' }
];
return <StepperComponent steps={steps} />;
}
export default App;Step Properties
Each StepDirective supports the following properties for customization:
Icon CSS
Display an icon for each step using the iconCss property:
<StepperComponent>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" />
<StepDirective iconCss="sf-icon-transport" />
<StepDirective iconCss="sf-icon-payment" />
<StepDirective iconCss="sf-icon-success" />
</StepsDirective>
</StepperComponent>Label
Display descriptive text below or beside the step indicator using the label property:
<StepperComponent>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>Text Content
Display text instead of icons using the text property (useful for numbered steps or indicators):
<StepperComponent>
<StepsDirective>
<StepDirective text="1" label="Account" />
<StepDirective text="2" label="Profile" />
<StepDirective text="3" label="Verification" />
<StepDirective text="4" label="Complete" />
</StepsDirective>
</StepperComponent>Note: When both label and text are defined, the label takes priority for display.
Combined Example
<StepperComponent>
<StepsDirective>
<StepDirective
iconCss="sf-icon-home"
label="Shipping Address"
text="1"
/>
<StepDirective
iconCss="sf-icon-creditcard"
label="Payment Method"
text="2"
/>
<StepDirective
iconCss="sf-icon-check"
label="Review Order"
text="3"
/>
</StepsDirective>
</StepperComponent>Setting Active Step
Control which step is currently active using the activeStep property:
<StepperComponent activeStep={1}>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>Usage: The activeStep is zero-indexed. In the example above, "Delivery" (index 1) is the active step.
Dynamic Active Step
import React, { useState } from 'react';
function App() {
const [activeStep, setActiveStep] = useState(0);
return (
<div>
<StepperComponent activeStep={activeStep}>
<StepsDirective>
<StepDirective label="Step 1" />
<StepDirective label="Step 2" />
<StepDirective label="Step 3" />
</StepsDirective>
</StepperComponent>
<button onClick={() => setActiveStep(activeStep + 1)}>
Next
</button>
</div>
);
}Step Status
Define the completion status of each step using the status property. Valid values: 'NotStarted', 'InProgress', 'Completed'.
Component Pattern
<StepperComponent>
<StepsDirective>
<StepDirective
iconCss="sf-icon-cart"
label="Cart"
status="Completed"
/>
<StepDirective
iconCss="sf-icon-transport"
label="Delivery"
status="InProgress"
/>
<StepDirective
iconCss="sf-icon-payment"
label="Payment"
status="NotStarted"
/>
</StepsDirective>
</StepperComponent>Property Pattern
const steps = [
{ label: 'Cart', status: 'Completed' },
{ label: 'Delivery', status: 'InProgress' },
{ label: 'Payment', status: 'NotStarted' }
];
<StepperComponent steps={steps} />Dynamic Status Update
const [stepStatus, setStepStatus] = React.useState([
'Completed', 'InProgress', 'NotStarted'
]);
const steps = [
{ label: 'Step 1', status: stepStatus[0] },
{ label: 'Step 2', status: stepStatus[1] },
{ label: 'Step 3', status: stepStatus[2] }
];
return <StepperComponent steps={steps} />;Disabled Steps
Prevent user interaction with specific steps using the disabled property:
<StepperComponent>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective
iconCss="sf-icon-payment"
label="Payment"
disabled={true}
/>
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>Use Case: Disable payment step until shipping address is confirmed.
Disable Multiple Steps
const disabledSteps = [2, 3]; // Disable steps at index 2 and 3
<StepsDirective>
{steps.map((step, index) => (
<StepDirective
key={index}
label={step.label}
disabled={disabledSteps.includes(index)}
/>
))}
</StepsDirective>Optional Steps
Mark steps as optional to indicate they don't need to be completed:
<StepperComponent>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective
iconCss="sf-icon-gift"
label="Gift Wrap"
optional={true}
/>
<StepDirective iconCss="sf-icon-payment" label="Payment" />
</StepsDirective>
</StepperComponent>Visual Indicator: Optional steps display an "optional" label or indicator depending on the step type.
Read-Only Mode
Disable all user interactions with the Stepper using the readOnly property:
<StepperComponent readOnly={true}>
<StepsDirective>
<StepDirective iconCss="sf-icon-cart" label="Cart" />
<StepDirective iconCss="sf-icon-transport" label="Delivery" />
<StepDirective iconCss="sf-icon-payment" label="Payment" />
<StepDirective iconCss="sf-icon-success" label="Confirmation" />
</StepsDirective>
</StepperComponent>Use Case: Display stepper as a progress indicator without allowing step changes.
CSS Class Customization
Apply custom CSS classes to steps for additional styling:
<StepperComponent>
<StepsDirective>
<StepDirective
iconCss="sf-icon-cart"
label="Cart"
cssClass="step-active"
/>
<StepDirective
iconCss="sf-icon-transport"
label="Delivery"
cssClass="step-completed"
/>
<StepDirective
iconCss="sf-icon-payment"
label="Payment"
cssClass="step-pending"
/>
</StepsDirective>
</StepperComponent>Define CSS classes:
.step-active {
background-color: #007bff;
color: white;
}
.step-completed {
background-color: #28a745;
color: white;
}
.step-pending {
background-color: #f5f5f5;
color: #666;
}Validation States
Mark steps as valid or invalid to show validation results:
<StepperComponent>
<StepsDirective>
<StepDirective
iconCss="sf-icon-cart"
label="Cart"
isValid={true}
/>
<StepDirective
iconCss="sf-icon-transport"
label="Delivery"
isValid={false}
/>
<StepDirective
iconCss="sf-icon-payment"
label="Payment"
isValid={null}
/>
</StepsDirective>
</StepperComponent>Values:
true- Shows success/checkmark indicatorfalse- Shows error/cross indicatornull- Default state, no validation indicator
Troubleshooting
Issue: Steps not appearing
- ✅ Ensure
StepsDirectiveandStepDirectiveare imported - ✅ Verify at least one
StepDirectiveis defined - ✅ Check parent
StepperComponentis rendered
Issue: Active step not updating
- ✅ Verify
activeStepindex is valid (0 to steps.length - 1) - ✅ If using state, ensure state update triggers re-render
- ✅ Check browser DevTools for errors
Issue: Disabled steps still clickable
- ✅ Verify
disabled={true}is set on the step - ✅ Check CSS isn't overriding disabled styles
- ✅ If using
readOnly, ensure entire stepper isn't read-only