A step-by-step wizard/stepper component for Ink terminal applications
npm install ink-stepperStep-by-step wizard component for Ink terminal applications.
``
━━━━━ ✓ ━━━━━━━━━━ ✓ ━━━━━━━━━━●━━━━━━━━━━○━━━━━
Theme Directory Review Done
┌─────────────────────────────────────────────────┐
│ │
│ Review your selections: │
│ │
│ Theme: Dark │
│ Directory: ~/projects │
│ │
│ Press Enter to continue, Escape to go back │
│ │
└─────────────────────────────────────────────────┘
`
`bashnpm
npm install ink-stepper
Usage
`tsx
import { Stepper, Step } from "ink-stepper";
import { Text } from "ink";function App() {
return (
process.exit(0)} onCancel={() => process.exit(1)}>
{({ goNext, goBack }) => (
)}
{({ goBack, isLast }) => (
)}
);
}
`API
$3
Main container component that orchestrates step navigation.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
children | ReactNode | required | Step elements |
| onComplete | () => void | required | Called when advancing past the last step |
| onCancel | () => void | - | Called when canceling (Escape on first step or cancel()) |
| onStepChange | (step: number) => void | - | Called when current step changes (zero-based index) |
| onEnterStep | (step: number) => void | - | Called after entering a step |
| onExitStep | (step: number) => void \| boolean \| Promise | - | Called before leaving a step (return false to cancel) |
| step | number | - | Controlled step index (zero-based) |
| keyboardNav | boolean | true | Enable Enter/Escape navigation |
| showProgress | boolean | true | Show the progress bar |
| renderProgress | (ctx: ProgressContext) => ReactNode | - | Custom progress bar renderer |
| markers | StepperMarkers | - | Custom progress bar markers |$3
Marker component for defining individual steps.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
name | string | required | Display name in progress bar |
| canProceed | boolean \| (() => boolean \| Promise | true | Whether navigation to next step is allowed (supports async) |
| children | ReactNode \| (ctx: StepContext) => ReactNode | required | Step content |$3
Context passed to step content when using the render function pattern:
`tsx
interface StepContext {
goNext: () => void; // Navigate to next step (respects canProceed)
goBack: () => void; // Navigate to previous step
goTo: (step: number) => void; // Jump to specific step (zero-based)
cancel: () => void; // Cancel the wizard
currentStep: number; // Current step index (zero-based)
totalSteps: number; // Total number of steps
isFirst: boolean; // Whether this is the first step
isLast: boolean; // Whether this is the last step
isValidating: boolean; // Whether async validation is in progress
}
`$3
Context passed to custom progress bar renderer:
`tsx
interface ProgressContext {
currentStep: number;
steps: Array<{
name: string;
completed: boolean;
current: boolean;
}>;
}
`Keyboard Navigation
By default, keyboard navigation is enabled:
- Enter - Advance to next step (if
canProceed is true)
- Escape - Go back (or cancel if on first step)Disable with
keyboardNav={false}.Validation
$3
Control navigation with the
canProceed prop:`tsx
function App() {
const [isValid, setIsValid] = useState(false); return (
{({ goNext }) => (
onChange={(value) => setIsValid(value.length > 0)}
onSubmit={goNext}
/>
)}
);
}
`$3
canProceed supports async functions for server-side validation:`tsx
function App() {
const validateEmail = async () => {
const response = await fetch(/api/validate?email=${email});
return response.ok;
}; return (
{({ goNext, isValidating }) => (
{isValidating && Validating... }
)}
);
}
`The
isValidating flag in StepContext is true while async validation is running, allowing you to show loading states.Lifecycle Hooks
$3
Execute logic when entering or leaving steps:
`tsx
onComplete={handleComplete}
onEnterStep={(step) => {
analytics.track(entered_step_${step});
}}
onExitStep={async (step) => {
// Save draft before leaving
await saveDraft(step);
return true; // Allow navigation
}}
>
...
`onExitStep can return false (sync or async) to cancel navigation:`tsx
onComplete={handleComplete}
onExitStep={(step) => {
if (hasUnsavedChanges) {
return confirm("Discard changes?");
}
return true;
}}
>
...
`Input Coordination
When steps contain interactive inputs (TextInput, Select, etc.), use
useStepperInput to prevent keyboard conflicts:`tsx
import { useStepperInput } from "ink-stepper";function EmailInput() {
const { disableNavigation, enableNavigation } = useStepperInput();
const [value, setValue] = useState("");
return (
value={value}
onChange={setValue}
onFocus={disableNavigation} // Disable Enter/Escape handling
onBlur={enableNavigation} // Re-enable when done
/>
);
}
`This prevents Enter from advancing the step while the user is typing.
Controlled Mode
For external state management, use the
step prop:`tsx
function App() {
const [currentStep, setCurrentStep] = useState(0); return (
step={currentStep}
onStepChange={setCurrentStep}
onComplete={handleComplete}
>
...
...
);
}
`Wrapped & Nested Steps
Steps can be wrapped in custom components, fragments, or conditional logic:
`tsx
const StepGroup = ({ children }) => <>{children}>;
This works!
{showOptional && (
Conditional step
)}
`Custom Progress Bar
$3
Customize the progress bar markers without replacing the entire component:
`tsx
onComplete={handleComplete}
markers={{ completed: "[X]", current: "[>]", pending: "[ ]" }}
>
...
`Default markers:
✓ (completed), ● (current), ○ (pending)$3
Full control over progress bar rendering:
`tsx
onComplete={handleComplete}
renderProgress={({ currentStep, steps }) => (
Step {currentStep + 1} of {steps.length}: {steps[currentStep].name}
)}
>
...
`Advanced: useStepperContext
For advanced use cases, access the full stepper context:
`tsx
import { useStepperContext } from "ink-stepper";function CustomStepContent() {
const { stepContext, currentStepId } = useStepperContext();
return (
Step {stepContext.currentStep + 1}
);
}
`Exports
`tsx
// Components
export { Stepper, Step } from "ink-stepper";// Hooks
export { useStepperContext, useStepperInput } from "ink-stepper";
// Types
export type {
StepperProps,
StepProps,
StepContext,
ProgressContext,
StepperMarkers,
StepperContextValue,
RegisteredStep,
UseStepperInputReturn,
} from "ink-stepper";
``MIT