--- url: /ink-stepper/index.md --- # Getting Started `ink-stepper` is a component for building interactive step-by-step wizard flows in [Ink](https://github.com/vadimdemedes/ink) applications. It handles navigation state, input coordination, and validation so you can focus on building your CLI steps. ## Installation ### Requirements `ink-stepper` declares its runtime as peer dependencies, so install them alongside it: * `ink` — `^6.6.0 || ^7.0.0` * `react` — `^19.2.3` Installing from JSR effectively requires Ink 7: the JSR package pins `npm:ink@^7.0.0` in its import map. ### [NPM](https://www.npmjs.com/package/ink-stepper) ::: code-group ```bash [npm] npm install ink-stepper ``` ```bash [pnpm] pnpm add ink-stepper ``` ```bash [yarn] yarn add ink-stepper ``` ```bash [bun] bun add ink-stepper ``` ```bash [deno] deno add npm:ink-stepper ``` ::: ### [JSR](https://jsr.io/@archcorsair/ink-stepper) ::: code-group ```bash [npm] npx jsr add @archcorsair/ink-stepper ``` ```bash [pnpm] pnpm i jsr:@archcorsair/ink-stepper ``` ```bash [yarn] yarn add jsr:@archcorsair/ink-stepper ``` ```bash [bun] bunx jsr add @archcorsair/ink-stepper ``` ```bash [deno] deno add jsr:@archcorsair/ink-stepper ``` ::: ## Quick Start Here is a minimal example of a stepper with three steps: ```tsx import React from 'react'; import { render, Text } from 'ink'; import { Stepper, Step } from 'ink-stepper'; function App() { return ( console.log('All done!')} onCancel={() => console.log('Cancelled.')} > Welcome to the wizard! Press Enter to continue. This is step 2. Ready to submit? Press Enter to finish. ); } render(); ``` Save it as `app.tsx` and run it with `bun app.tsx` (or `npx tsx app.tsx`) to see an interactive wizard in your terminal — both handle ESM and TSX without extra configuration. ## Try the Example The repository ships a runnable wizard that exercises the full API — render-function steps, an input coordinated with `useStepperInput`, async validation with error handling, a conditional step, and a `goTo` jump, with every lifecycle callback logged as it fires: ```bash git clone https://github.com/archcorsair/ink-stepper cd ink-stepper && bun install bun run example # start on a specific step INITIAL_STEP=2 bun run example ``` The source lives in [`examples/wizard.tsx`](https://github.com/archcorsair/ink-stepper/blob/main/examples/wizard.tsx). ## Docs for LLMs This site publishes itself in LLM-friendly plain text: [`/llms.txt`](https://archcorsair.github.io/ink-stepper/llms.txt) for the index and [`/llms-full.txt`](https://archcorsair.github.io/ink-stepper/llms-full.txt) for every page in one file. --- --- url: /ink-stepper/guide/basic-usage.md --- # Basic Usage The core of `ink-stepper` revolves around the `` container and `` components. ## The Stepper Component The `` component orchestrates the flow. It requires an `onComplete` callback, which triggers when the user presses Enter on the final step. ```tsx process.exit(0)} onCancel={() => process.exit(1)} > {/* Steps go here */} ``` ### Key Props * `onComplete`: Function called when the wizard finishes. * `onCancel`: Function called when the user presses Escape on the first step. * `initialStep`: (Optional) The index of the step to start on in uncontrolled mode (default: `0`). Ignored when the controlled `step` prop is provided. * `onError`: (Optional) Called when an async `canProceed` or `onExitStep` throws. See [Validation](/guide/validation#error-handling). ## Defining Steps Use the `` component to define each page of your wizard. Every step needs a unique `name` which is displayed in the progress bar. ```tsx Step content goes here. ``` ### Accessing Step Context If you need to programmatically control navigation (e.g., from your own key binding instead of just pressing Enter), you can use the function-as-child pattern to access `StepContext`. Ink has no click targets, so wire the control up with `useInput`: ```tsx import { Box, Text, useInput } from 'ink'; function NextHint({ onNext }: { onNext: () => void }) { useInput((input) => { if (input === 'n') onNext(); }); return Press "n" to continue.; } {({ goNext }) => ( Custom controls: )} ``` The context provides: * `goNext()`: Advance to the next step (respects `canProceed`). * `goBack()`: Return to the previous step (cancels the wizard from the first step). * `goTo(index)`: Jump to a specific step. The index is clamped to the valid range, and unlike `goNext` this skips `canProceed` — it is a raw jump. It still fires the full lifecycle, see [Lifecycle Hooks](/guide/lifecycle#programmatic-jumps-with-goto). * `cancel()`: Cancel the wizard (calls `onCancel`). * `isFirst`, `isLast`: Boolean flags for current position. * `currentStep`, `totalSteps`: Numeric indicators. * `isValidating`: `true` while an async `canProceed` is running. `goNext()`, `goBack()` and `goTo()` are all no-ops while validation is in flight or while navigation has been disabled via [`useStepperInput`](/guide/input-coordination). ## Keyboard Navigation Keyboard navigation is on by default: * **Enter** — advance to the next step (subject to `canProceed`). * **Escape** — go back to the previous step; on the first step it cancels the wizard and calls `onCancel`. Turn it off with `keyboardNav={false}` if your steps handle all input themselves: ```tsx {/* ... */} ``` Both keys are ignored while an async `canProceed` is running and while navigation has been disabled via [`useStepperInput`](/guide/input-coordination). ## Conditional Steps Steps may be wrapped in components or rendered conditionally. They are ordered by their position in the element tree, not by the time they mounted, so a step toggled on later slots into its JSX position: ```tsx {/* ... */} {needsBilling && {/* ... */}} {/* ... */} ``` In uncontrolled mode the user stays on the same step when another step is inserted or removed elsewhere — the active step is tracked by identity, not by index — and no lifecycle callbacks fire for that repair. If the active step itself is removed, the index is kept, so whichever step slides into that position becomes active; it only clamps to the last remaining step when the removed step was the last one. ::: warning A `` must not be nested inside another ``; that breaks the tree-order guarantee. Wrapper components, fragments, and conditionals around a `` are fine. ::: --- --- url: /ink-stepper/guide/validation.md --- # Validation You often need to ensure data is valid before allowing the user to proceed to the next step. `ink-stepper` supports both synchronous and asynchronous validation via the `canProceed` prop on ``. ## Synchronous Validation Pass a boolean to `canProceed`. If `false`, the "Enter" key (and `goNext()`) will be ignored. ```tsx function NameStep() { const [name, setName] = useState(''); return ( 0}> Enter your name: {name.length === 0 && Name is required} ); } ``` ## Asynchronous Validation You can also pass a function that returns a `boolean` or `Promise`. This is useful for server-side checks or simulating API calls. ```tsx const checkServer = async () => { // simulate delay await new Promise(r => setTimeout(r, 1000)); return true; }; {({ isValidating }) => ( Checking server status... {isValidating && } )} ``` When validation is in progress: 1. `isValidating` (from context) becomes `true`. 2. Navigation is locked — `goNext()`, `goBack()` and `goTo()` are no-ops, as are the Enter/Escape keys. 3. If the promise resolves to `true`, the stepper advances. 4. If `false`, it stays on the current step. ## Error Handling An async validator can also fail outright — the network call rejects, the server returns garbage. If a `canProceed` (or an [`onExitStep`](/guide/lifecycle)) callback throws or rejects, the Stepper catches it, blocks the navigation, and reports it through the optional `onError` prop: ```tsx const checkServer = async () => { const response = await fetch('/api/validate'); // may reject return response.ok; }; function App() { const [error, setError] = useState(null); return ( setError(String(err))}> {() => ( Press Enter to validate. {error && {error}} )} ); } ``` `onError` receives the thrown value as `unknown`. If you omit the prop, the error is logged with `console.error` instead. Either way the rejection is handled, so it never escapes as an unhandled rejection — which would otherwise terminate the host process. The user stays on the current step after an error, so they can retry. For a live version of this, the Validate step of [`examples/wizard.tsx`](https://github.com/archcorsair/ink-stepper/blob/main/examples/wizard.tsx) arms a throwing validator when you press `e`, then routes the failure through `onError` without crashing the wizard. --- --- url: /ink-stepper/guide/lifecycle.md --- # Lifecycle Hooks `ink-stepper` provides lifecycle hooks that allow you to execute logic when steps are entered or exited. This is useful for analytics, saving data, or performing cleanup. ## Entering a Step The `onEnterStep` callback is triggered whenever the active step changes. It receives the index of the new step. ```tsx { console.log(`Navigated to step ${stepIndex}`); analytics.trackView(`step_${stepIndex}`); }} > {/* ... */} ``` ## Exiting a Step The `onExitStep` callback is triggered *before* leaving the current step. It can be used to validate data, save state, or prevent navigation. ```tsx { console.log(`Leaving step ${stepIndex}`); // Perform cleanup or save - no return value needed await saveData(stepIndex); }} > {/* ... */} ``` The signature is `(step: number) => void | boolean | Promise`. Only an explicit `false` cancels navigation, so a handler that just performs a side effect can return nothing. ### Preventing Navigation If `onExitStep` returns `false` (or a Promise that resolves to `false`), the navigation is cancelled, and the user remains on the current step. This applies to `goNext()`, `goBack()` and `goTo()` alike. ```tsx { if (step === 0 && !formIsValid) { console.log('Cannot leave step 0 yet!'); return false; } return true; }} > {/* ... */} ``` ## Callback Order Every user-initiated navigation runs the same sequence: ``` onExitStep(from) → onStepChange(to) → onEnterStep(to) ``` `goNext()` additionally resolves `canProceed` before any of it; if the check fails, nothing fires. Reaching the end of the wizard calls `onComplete` instead of the change/enter pair, and going back from the first step calls `onCancel`. `onExitStep` runs **before** those terminal callbacks too: ``` onExitStep(last) → onComplete() // advancing past the last step onExitStep(0) → onCancel() // going back from the first step ``` So returning `false` from `onExitStep` blocks completion and cancellation exactly the same way it blocks a step change — the wizard stays where it is and neither `onComplete` nor `onCancel` fires. ## Programmatic Jumps with `goTo` `goTo(index)` fires the same full lifecycle as `goNext`/`goBack`, so `onExitStep` can cancel a jump by returning `false`. Two things make it different: * It **skips `canProceed`** on the current step — `goTo` is a raw jump, not a validated advance. Use it for "back to summary" style navigation, not to bypass validation on the way forward. * The index is **clamped** to the valid range, and a jump to the current index is a no-op (no callbacks fire). ```tsx import { Text, useInput } from 'ink'; function EditFirstHint({ onEdit }: { onEdit: () => void }) { useInput((input) => { if (input === '1') onEdit(); }); return Press "1" to edit the first step.; } {({ goTo }) => goTo(0)} />} ``` The Review step of [`examples/wizard.tsx`](https://github.com/archcorsair/ink-stepper/blob/main/examples/wizard.tsx) is exactly this: press `1` to `goTo(0)` and watch the lifecycle log. Like `goNext` and `goBack`, `goTo` does nothing while async validation is in flight or while navigation is disabled via [`useStepperInput`](/guide/input-coordination). ## Errors in Callbacks If `onExitStep` throws or returns a rejecting Promise, navigation is blocked and the error is passed to the `onError` prop (or logged via `console.error` when that prop is omitted). See [Validation](/guide/validation#error-handling). ## Silent Index Repairs Conditional steps can appear and disappear. When that happens, the Stepper keeps the user on the same step by re-pointing the internal index at the step they were already on. If that step itself was removed, the index stays put — whichever step slides into that position becomes active — and only clamps to the last remaining step when the removed step was the last one. These repairs are **not** navigation: `onExitStep`, `onStepChange` and `onEnterStep` do not fire for them. Only user-initiated `goNext()`/`goBack()`/`goTo()` calls and keyboard navigation trigger the lifecycle. --- --- url: /ink-stepper/guide/input-coordination.md --- # Input Coordination When building CLI applications, multiple components often vie for keyboard input. `ink-stepper` uses `ink`'s `useInput` hook to handle Enter and Escape keys. If your step content also contains interactive components (like text inputs, selects, etc.), this can lead to conflicts. For example, typing "Enter" to submit a text input might accidentally trigger the Stepper to advance to the next step. ## The `useStepperInput` Hook To solve this, `ink-stepper` exports a `useStepperInput` hook. Use this hook within your custom components to temporarily disable the Stepper's navigation handling while your component has focus. ```tsx import { useStepperInput } from 'ink-stepper'; import { TextInput } from 'ink-text-input'; // hypothetically function MyInput() { const { disableNavigation, enableNavigation } = useStepperInput(); const [value, setValue] = useState(''); return ( { // Handle submission logic here // Then potentially manually trigger goNext() }} /> ); } ``` ## How it Works 1. **`disableNavigation()`**: Tells the parent `` to ignore global Enter/Escape keys. 2. **`enableNavigation()`**: Tells the parent `` to resume listening to Enter/Escape keys. This ensures that when a user is interacting with a specific input field, they don't accidentally navigate away from the current step. The flag is not limited to the keyboard: while navigation is disabled, `goNext()`, `goBack()` and `goTo()` are no-ops too. That is what makes the ordering rule below matter. ## Re-enable Before Navigating A submit handler that both releases the input and advances the wizard must call `enableNavigation()` **before** `goNext()`: ```tsx useInput((input, key) => { if (key.return) { enableNavigation(); // must come first onSubmit(); // calls goNext() } }); ``` ::: danger Silent failure If you call `goNext()` while navigation is still disabled, nothing happens — no error, no warning. The symptom is a dead Enter key: the input accepts the submission but the wizard never advances. ::: The same applies to a handler that calls `goBack()` or `goTo()`. If you prefer to re-enable on unmount (a cleanup in `useEffect`), remember that the cleanup runs *after* the navigation call, which is too late — re-enable explicitly in the handler as well. See [`examples/wizard.tsx`](https://github.com/archcorsair/ink-stepper/blob/main/examples/wizard.tsx) (the `NameStep` component) for a complete, runnable version of this pattern. --- --- url: /ink-stepper/guide/controlled-mode.md --- # Controlled Mode By default, `` manages its own internal state (which step is currently active). However, there are scenarios where you might want to control the step index from a parent component, such as: * Syncing the step with a URL or external store. * Implementing complex custom navigation logic outside the stepper. * Restoring a session from a saved state. ## Using the `step` Prop To enable controlled mode, pass the `step` prop (zero-based index) to the `` component. You should also listen to `onStepChange` to update your external state. ```tsx import { useState } from 'react'; import { Stepper, Step } from 'ink-stepper'; function App() { const [currentStep, setCurrentStep] = useState(0); return ( { // You can intercept or modify the change here if needed setCurrentStep(newStep); }} onComplete={() => console.log('Done')} > Step A Step B Step C ); } ``` When `step` is provided: 1. The Stepper will always render the step at that index. 2. Calls to `goNext()`, `goBack()`, etc., will trigger `onStepChange` with the new index, but the Stepper **will not update visually** until you update the `step` prop. 3. The `initialStep` prop is ignored — the parent owns the index from the first render. ::: tip `onStepChange` never fires for the initial render, in either mode. Only `goNext()`, `goBack()` and `goTo()` invoke it, so a wizard that starts on `initialStep={2}` reports nothing until the user actually navigates — seed your own state with the same starting index instead of waiting for a callback. ::: ## Conditional Steps in Controlled Mode In uncontrolled mode the Stepper keeps the user on the same step when steps are added or removed elsewhere in the list. In controlled mode it cannot: the index you pass is the source of truth, so inserting a step **before** the current index changes which step that index points at. ```tsx // step={1} with these children renders "B" A B C // after inserting a step before B, step={1} renders "New" instead A New B C ``` If you mount steps conditionally, adjust your own state when the set changes (for example, increment the index when you insert a step ahead of the user). Step *ordering* itself is unaffected — steps always sort by their position in the element tree, whenever they mount. --- --- url: /ink-stepper/guide/customization.md --- # Customization `ink-stepper` allows you to customize the visual appearance of the progress bar to match your CLI's theme. ## Custom Markers You can change the symbols used for completed, current, and pending steps using the `markers` prop. ```tsx {/* ... */} ``` **Defaults:** * Completed: `" ✓ "` (padded to 3 characters) * Current: `●` * Pending: `○` Markers may differ in width from one another — the default set already does. The progress bar sizes each label column from the marker actually rendered for that step, so the labels stay aligned with their markers as steps complete. ## Pulsing the Current Marker Enable `pulse` to animate the current step's marker, spinner-style: ```tsx {/* ... */} ``` The marker cycles through the three brightness levels a terminal offers — bright → normal → dim → normal — at about 3.5 frames per second, the same frame-swapping technique CLI spinners use. It composes with custom `markers` (only brightness changes, never the glyph) and is ignored when you take over rendering with `renderProgress`. ## Custom Progress Renderer For complete control over the progress bar, use the `renderProgress` prop. This allows you to replace the default renderer entirely. ```tsx ( Step {currentStep + 1} of {steps.length} {steps.map(step => ( {step.completed ? '■' : '□'}{' '} ))} )} onComplete={handleComplete} > {/* ... */} ``` The `renderProgress` function receives a `ProgressContext` object: ```ts interface ProgressContext { /** Current step index (zero-based) */ currentStep: number; /** Array of step metadata */ steps: Array<{ /** Stable unique identifier for the step - safe to use as a React key */ id: string; name: string; completed: boolean; current: boolean; }>; } ``` ::: tip Use `step.id` as the React key when mapping over `steps`. Two steps are allowed to share a `name`, so names are not safe keys. ::: ## Hiding the Progress Bar If you don't want a progress bar at all, set `showProgress={false}`. ```tsx {/* ... */} ``` --- --- url: /ink-stepper/api/components.md --- # Components ## `` The main container component. ### Props | Name | Type | Default | Description | | :--- | :--- | :--- | :--- | | `onComplete` | `() => void` | **Required** | Callback fired when the user completes the final step. | | `children` | `ReactNode` | **Required** | The step components. | | `onCancel` | `() => void` | `undefined` | Callback fired when canceling (Escape or `goBack()` on the first step, or a `stepContext.cancel()` call). | | `onStepChange` | `(step: number) => void` | `undefined` | Callback fired when the active step index changes. | | `onEnterStep` | `(step: number) => void` | `undefined` | Callback fired after entering a new step. | | `onExitStep` | `(step: number) => void \| boolean \| Promise` | `undefined` | Callback fired before leaving a step. Return `false` to prevent navigation; no return value is needed otherwise. | | `onError` | `(error: unknown) => void` | `undefined` | Called when an async `canProceed` or `onExitStep` callback throws or rejects. Navigation is blocked in that case. When omitted, the error is logged via `console.error`. | | `step` | `number` | `undefined` | If provided, puts the stepper in controlled mode. | | `initialStep` | `number` | `0` | Starting step index for uncontrolled mode. Ignored when `step` is provided. | | `keyboardNav` | `boolean` | `true` | Whether to enable built-in Enter/Escape navigation. | | `showProgress` | `boolean` | `true` | Whether to display the progress bar. | | `renderProgress` | `(ctx: ProgressContext) => ReactNode` | `undefined` | Custom renderer for the progress bar. | | `markers` | `StepperMarkers` | `undefined` | Custom configuration for progress bar symbols. | | `pulse` | `boolean` | `false` | Pulse the current-step marker's brightness (bright → normal → dim → normal, spinner-style). Ignored when `renderProgress` is provided. | *** ## `` Represents a single step in the wizard. ### Props | Name | Type | Default | Description | | :--- | :--- | :--- | :--- | | `name` | `string` | **Required** | The display name of the step (used in the progress bar). | | `children` | `ReactNode \| (ctx: StepContext) => ReactNode` | **Required** | The content of the step. Can be a function to access navigation controls. | | `canProceed` | `boolean \| (() => boolean \| Promise)` | `true` | Whether navigation to the next step is allowed. Can be a boolean or an (async) function. | ### Example ```tsx Please verify your identity. ``` ### Ordering Steps sort by their position in the element tree, not by the time they mounted, so a conditionally rendered `` that appears later still occupies its JSX position. ::: warning A `` must not be nested inside another `` — that breaks the tree-order guarantee. Wrapper components, fragments, and conditionals around a `` are supported. ::: --- --- url: /ink-stepper/api/hooks.md --- # Hooks ## `useStepperInput` A hook for coordinating input focus with Stepper keyboard navigation. Use this in custom input components to prevent keyboard conflicts. ```tsx import { useStepperInput } from 'ink-stepper'; const { disableNavigation, enableNavigation, isNavigationDisabled } = useStepperInput(); ``` ### Returns | Name | Type | Description | | :--- | :--- | :--- | | `disableNavigation` | `() => void` | Disables global Stepper navigation (Enter/Escape). | | `enableNavigation` | `() => void` | Re-enables global Stepper navigation. | | `isNavigationDisabled` | `boolean` | Current status of navigation. | While navigation is disabled, `goNext()`, `goBack()` and `goTo()` are no-ops as well — not just the Enter/Escape keys. Call `enableNavigation()` **before** navigating from a submit handler, otherwise the navigation call is silently dropped. See [Input Coordination](/guide/input-coordination#re-enable-before-navigating). Like `useStepperContext` (which it calls internally), this hook throws if used outside a ``. *** ## `useStepperContext` A hook to access the internal Stepper context. Useful for building deeply nested components that need to control the wizard. ```tsx import { useStepperContext } from 'ink-stepper'; const { stepContext, currentStepId } = useStepperContext(); ``` `stepContext` is `null` when no step is active, so guard before using it. The hook throws if called outside a ``. ### Returns [`StepperContextValue`](/api/types#steppercontextvalue). --- --- url: /ink-stepper/api/types.md --- # Types ## `StepContext` Passed to the render function of a `` or available via `useStepperContext().stepContext`. ```ts interface StepContext { /** Navigate to the next step (respects canProceed) */ goNext: () => void; /** Navigate to the previous step */ goBack: () => void; /** * Jump to a specific step by index (zero-based). * * The index is clamped to the valid range and fires the full lifecycle * (`onExitStep` -> `onStepChange` -> `onEnterStep`); returning `false` from * `onExitStep` cancels the jump. Unlike `goNext`, `goTo` deliberately skips * the current step's `canProceed` check - it is a raw jump. */ goTo: (step: number) => void; /** Cancel the wizard (calls onCancel) */ cancel: () => void; /** Current step index (zero-based) */ currentStep: number; /** Total number of steps */ totalSteps: number; /** Whether this is the first step */ isFirst: boolean; /** Whether this is the last step */ isLast: boolean; /** Whether async validation is in progress */ isValidating: boolean; } ``` `goNext`, `goBack` and `goTo` are all no-ops while `isValidating` is `true` or while navigation has been disabled via [`useStepperInput`](/api/hooks#usestepperinput). ## `StepperProps` Props for the `` component. ```ts interface StepperProps { /** Step elements, plus any other content to render alongside every step */ children: ReactNode; /** Called when advancing past the last step */ onComplete: () => void; /** Called when canceling (Escape on first step or cancel() call) */ onCancel?: () => void; /** Called when the current step changes (step is zero-based) */ onStepChange?: (step: number) => void; /** Called before leaving a step (can be async, return false to cancel navigation) */ onExitStep?: (step: number) => void | boolean | Promise; /** Called after entering a step */ onEnterStep?: (step: number) => void; /** * Called when an async `canProceed` or `onExitStep` callback throws or rejects. * Navigation is blocked in that case. When omitted, the error is logged via `console.error`. */ onError?: (error: unknown) => void; /** Controlled step index (zero-based) - when provided, Stepper is controlled */ step?: number; /** Starting step index for uncontrolled mode (default: 0). Ignored when `step` is provided. */ initialStep?: number; /** Enable keyboard navigation (Enter/Escape) (default: true) */ keyboardNav?: boolean; /** Show the progress bar (default: true) */ showProgress?: boolean; /** Custom progress bar renderer */ renderProgress?: (context: ProgressContext) => ReactNode; /** Custom markers for progress bar states */ markers?: StepperMarkers; /** * Pulse the current-step marker in the default progress bar by cycling its * brightness (bright → normal → dim → normal), the way terminal spinners * animate (default: false). Ignored when `renderProgress` is provided. */ pulse?: boolean; } ``` `children` is not restricted to `` elements. Anything else you put inside the `` renders on every step, which is handy for a shared header, footer, or status line. ## `StepProps` Props for the `` component. ```ts interface StepProps { /** Display name shown in progress bar */ name: string; /** Whether navigation to next step is allowed (default: true). Can be boolean or async function. */ canProceed?: boolean | (() => boolean | Promise); /** Step content - either ReactNode or render function receiving StepContext */ children: ReactNode | ((context: StepContext) => ReactNode); } ``` ## `ProgressContext` Passed to `renderProgress`. ```ts interface ProgressContext { /** Current step index (zero-based) */ currentStep: number; /** Array of step metadata */ steps: Array<{ /** Stable unique identifier for the step - safe to use as a React key */ id: string; name: string; completed: boolean; current: boolean; }>; } ``` ## `StepperMarkers` Configuration for default progress bar. ```ts interface StepperMarkers { /** Marker for completed steps (default: ' ✓ ') */ completed?: string; /** Marker for current step (default: '●') */ current?: string; /** Marker for pending steps (default: '○') */ pending?: string; } ``` ## `UseStepperInputReturn` Returned by [`useStepperInput`](/api/hooks#usestepperinput). ```ts interface UseStepperInputReturn { /** Disable Stepper keyboard navigation (call when input is focused) */ disableNavigation: () => void; /** Re-enable Stepper keyboard navigation (call when input blurs) */ enableNavigation: () => void; /** Whether navigation is currently disabled */ isNavigationDisabled: boolean; } ``` ## `StepperContextValue` Returned by [`useStepperContext`](/api/hooks#usesteppercontext). This is the internal Stepper context; the members marked `@internal` exist for the `` component's own bookkeeping and are not part of the supported surface. ```ts interface StepperContextValue { /** Register a new step with the parent Stepper */ registerStep: (step: RegisteredStep) => void; /** Unregister a step (e.g., on unmount) */ unregisterStep: (id: string) => void; /** * Context helper for the current step (navigation methods, status, etc.). * Null if the component is not currently active/rendered. */ stepContext: StepContext | null; /** ID of the currently active step */ currentStepId: string | null; /** Temporarily disable Stepper navigation (e.g., when input is focused) */ disableNavigation: () => void; /** Re-enable Stepper navigation */ enableNavigation: () => void; /** Whether navigation is currently disabled */ isNavigationDisabled: boolean; /** * Claim the next sort order slot from the Stepper's counter. * * Steps claim in layout-effect order, which for non-nested siblings equals tree order. * @internal */ claimOrder: () => number; /** * Bumped by the Stepper whenever a new step id appears, forcing every Step to re-claim * its order against a freshly reset counter so tree order is restored. * @internal */ orderGeneration: number; } ``` ## `RegisteredStep` Metadata for a step registered within the Stepper. ```ts interface RegisteredStep { /** Unique identifier for the step (generated via useId) */ id: string; /** Display name of the step */ name: string; /** * Validation function or boolean flag to control navigation. * If a function, it can be async. */ canProceed: boolean | (() => boolean | Promise); /** * Sort order for the step, claimed from the parent Stepper's counter. * Reflects the step's position in the element tree, not the time it mounted. */ order: number; } ```