
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
ink-stepper
Advanced tools
Step-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 │
│ │
└─────────────────────────────────────────────────┘
# npm
npm install ink-stepper
# jsr
npx jsr add @archcorsair/ink-stepper
# pnpm
pnpm add ink-stepper
# bun
bun add ink-stepper
import { Stepper, Step } from "ink-stepper";
import { Text } from "ink";
function App() {
return (
<Stepper onComplete={() => process.exit(0)} onCancel={() => process.exit(1)}>
<Step name="Theme">
<ThemeSelector />
</Step>
<Step name="Directory" canProceed={pathIsValid}>
{({ goNext, goBack }) => (
<PathInput onConfirm={goNext} onBack={goBack} />
)}
</Step>
<Step name="Review">
{({ goBack, isLast }) => (
<Review onBack={goBack} showFinish={isLast} />
)}
</Step>
</Stepper>
);
}
<Stepper>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<boolean> | - | 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 |
<Step>Marker component for defining individual steps.
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | required | Display name in progress bar |
canProceed | boolean | (() => boolean | Promise<boolean>) | true | Whether navigation to next step is allowed (supports async) |
children | ReactNode | (ctx: StepContext) => ReactNode | required | Step content |
Context passed to step content when using the render function pattern:
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
}
Context passed to custom progress bar renderer:
interface ProgressContext {
currentStep: number;
steps: Array<{
name: string;
completed: boolean;
current: boolean;
}>;
}
By default, keyboard navigation is enabled:
canProceed is true)Disable with keyboardNav={false}.
Control navigation with the canProceed prop:
function App() {
const [isValid, setIsValid] = useState(false);
return (
<Stepper onComplete={handleComplete}>
<Step name="Input" canProceed={isValid}>
{({ goNext }) => (
<TextInput
onChange={(value) => setIsValid(value.length > 0)}
onSubmit={goNext}
/>
)}
</Step>
</Stepper>
);
}
canProceed supports async functions for server-side validation:
function App() {
const validateEmail = async () => {
const response = await fetch(`/api/validate?email=${email}`);
return response.ok;
};
return (
<Stepper onComplete={handleComplete}>
<Step name="Email" canProceed={validateEmail}>
{({ goNext, isValidating }) => (
<Box flexDirection="column">
<TextInput value={email} onChange={setEmail} />
{isValidating && <Text color="yellow">Validating...</Text>}
<Button onPress={goNext} disabled={isValidating}>
Continue
</Button>
</Box>
)}
</Step>
</Stepper>
);
}
The isValidating flag in StepContext is true while async validation is running, allowing you to show loading states.
Execute logic when entering or leaving steps:
<Stepper
onComplete={handleComplete}
onEnterStep={(step) => {
analytics.track(`entered_step_${step}`);
}}
onExitStep={async (step) => {
// Save draft before leaving
await saveDraft(step);
return true; // Allow navigation
}}
>
...
</Stepper>
onExitStep can return false (sync or async) to cancel navigation:
<Stepper
onComplete={handleComplete}
onExitStep={(step) => {
if (hasUnsavedChanges) {
return confirm("Discard changes?");
}
return true;
}}
>
...
</Stepper>
When steps contain interactive inputs (TextInput, Select, etc.), use useStepperInput to prevent keyboard conflicts:
import { useStepperInput } from "ink-stepper";
function EmailInput() {
const { disableNavigation, enableNavigation } = useStepperInput();
const [value, setValue] = useState("");
return (
<TextInput
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.
For external state management, use the step prop:
function App() {
const [currentStep, setCurrentStep] = useState(0);
return (
<Stepper
step={currentStep}
onStepChange={setCurrentStep}
onComplete={handleComplete}
>
<Step name="One">...</Step>
<Step name="Two">...</Step>
</Stepper>
);
}
Steps can be wrapped in custom components, fragments, or conditional logic:
const StepGroup = ({ children }) => <>{children}</>;
<Stepper onComplete={handleComplete}>
<StepGroup>
<Step name="Wrapped">
<Text>This works!</Text>
</Step>
</StepGroup>
{showOptional && (
<Step name="Optional">
<Text>Conditional step</Text>
</Step>
)}
</Stepper>
Customize the progress bar markers without replacing the entire component:
<Stepper
onComplete={handleComplete}
markers={{ completed: "[X]", current: "[>]", pending: "[ ]" }}
>
...
</Stepper>
Default markers: ✓ (completed), ● (current), ○ (pending)
Full control over progress bar rendering:
<Stepper
onComplete={handleComplete}
renderProgress={({ currentStep, steps }) => (
<Text>
Step {currentStep + 1} of {steps.length}: {steps[currentStep].name}
</Text>
)}
>
...
</Stepper>
For advanced use cases, access the full stepper context:
import { useStepperContext } from "ink-stepper";
function CustomStepContent() {
const { stepContext, currentStepId } = useStepperContext();
return (
<Box>
<Text>Step {stepContext.currentStep + 1}</Text>
<Button onPress={stepContext.goNext}>Next</Button>
</Box>
);
}
// 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
FAQs
A step-by-step wizard/stepper component for Ink terminal applications
The npm package ink-stepper receives a total of 326 weekly downloads. As such, ink-stepper popularity was classified as not popular.
We found that ink-stepper demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.