Welcome to the fourth chapter of our Wizard tutorial!
In the previous chapter, Standardized Dialog Layout, we created a consistent visual shell (the title, borders, and page numbers) for our wizard steps.
However, a pretty interface isn't enough. Imagine handing someone a complex map but removing the legend. They can see the terrain, but they don't know what the symbols mean or which path to take.
The Problem: A user looks at your fancy "Select an Option" screen.
Enter to select?Space?j and k to move up and down?Ctrl+C?
The Solution:
We use the Navigation Guidance System (WizardNavigationFooter).
This component acts as the Map Legend at the bottom of the screen. It tells the user exactly which keys are active. It also acts as a Safety Guard, catching accidental exit attempts so the user doesn't lose their progress.
WizardNavigationFooterThe footer sits at the very bottom of the dialog. It has two modes of operation:
โโ navigate โข Enter select โข Esc back.Ctrl+C), the footer changes instantly to warn them: Press Ctrl+C again to exit.Imagine a step where the user picks a color.
Ctrl+C instead of Ctrl+V.In most cases, you don't need to touch this directly! The Standardized Dialog Layout we built in Chapter 3 already includes this footer automatically.
However, you can customize the instructions if your step is special.
If you are building a text input where Enter adds a new line instead of submitting, you should tell the user.
// Inside a Step Component
import { WizardNavigationFooter } from './WizardNavigationFooter';
export function MyTextStep() {
return (
<>
{/* Your content here */}
{/* Customizing the legend */}
<WizardNavigationFooter
instructions="Ctrl+S to Save โข Esc to Cancel"
/>
</>
);
}
The footer is smart. It listens for global keyboard events to handle the "Safety Mode" automatically.
Let's visualize the flow when a user interacts with the app:
Let's look inside WizardNavigationFooter.tsx. We use a specialized hook to handle the complex "double-tap to exit" logic.
We import a hook that listens for Ctrl+C (or Ctrl+D).
// Inside WizardNavigationFooter.tsx
import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js';
export function WizardNavigationFooter({ instructions }) {
// This hook tells us if the user is trying to quit
const exitState = useExitOnCtrlCDWithKeybindings();
// exitState looks like: { pending: true, keyName: "Ctrl+C" }
If the developer didn't pass custom instructions, we provide a sensible default. We use helper components (Byline, KeyboardShortcutHint) to style keys nicely (e.g., making "Enter" look like a button).
// Default instructions if none provided
const defaultContent = (
<Byline>
<KeyboardShortcutHint shortcut="โโ" action="navigate" />
<KeyboardShortcutHint shortcut="Enter" action="select" />
{/* ... more hints */}
</Byline>
);
const finalInstructions = instructions || defaultContent;
Note: The Byline component simply arranges these hints in a neat row.
Finally, we decide what text to show based on the exitState. This is a simple conditional render.
return (
<Box marginLeft={3} marginTop={1}>
<Text dimColor>
{/* The Magic Switch */}
{exitState.pending
? `Press ${exitState.keyName} again to exit`
: finalInstructions
}
</Text>
</Box>
);
}
In this chapter, we learned:
Ctrl+C to prevent accidental exits.We have now built the entire internal machinery of our Wizard:
But how do we package all of this so other developers can import it cleanly, without seeing all the messy internal files?
In the final chapter, we will organize our code into a clean Public API.
Next Chapter: Public Module Interface
Generated by Code IQ