# Stepper Command Dialog

> Step-by-step guidance for building a multi-step wizard dialog (StepperCommandDialog) in a Cratis Arc application. Use whenever a command requires gathering information across multiple steps, implementing a wizard flow, breaking a complex form into named stages, or using StepperCommandDialog, StepperPanel, validateOnInit, or wizard-style navigation.

- Skill: `cratis/stepper-command-dialog` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cratis/stepper-command-dialog`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cratis/stepper-command-dialog/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Cratis (https://skillmd.com/u/cratis)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cratis/stepper-command-dialog

---


# StepperCommandDialog — Wizard Dialogs

`StepperCommandDialog` organizes a single command form across multiple named steps. Users navigate with **Previous** and **Next** buttons; **Submit** only appears on the last step when every field across all steps is valid.

Use this instead of `CommandDialog` when:
- The form has too many fields to show at once
- Fields can be grouped into logical stages (e.g. "Contact Info → Project Details → Summary")
- You want guided, linear input with per-step validation feedback
- The operation feels like a wizard or an onboarding flow

---

## Step 1 — Define the command

A single command collects all fields across all steps. Each step contributes properties to the same command instance.

```csharp
// Projects/CreateProject/CreateProject.cs — the slice file
[Command]
public record CreateProject(ProjectName Name, EmailAddress Email, Description Description, Money Budget)
{
    public ProjectCreated Handle() => new(Name, Email, Description, Budget);
}
```

Run a Debug `dotnet build` to generate the `CreateProject` TypeScript proxy before importing it.

---

## Step 2 — Build the dialog component

```tsx
import { StepperCommandDialog } from '@cratis/components/CommandDialog';
import { StepperPanel } from '@cratis/components/CommandDialog';
import { InputTextField, TextAreaField, NumberField } from '@cratis/components/CommandForm/fields';
import { DialogResult, useDialogContext } from '@cratis/arc.react/dialogs';
import { CreateProject } from '../api/Projects/CreateProject';

const CreateProjectDialog = () => {
    const { closeDialog } = useDialogContext();

    return (
        <StepperCommandDialog<CreateProject>
            command={CreateProject}
            title="Create New Project"
            okLabel="Create"
            onConfirm={() => closeDialog(DialogResult.Ok)}
            onCancel={() => closeDialog(DialogResult.Cancelled)}
        >
            <StepperPanel header="Contact Info">
                <InputTextField<CreateProject>
                    value={c => c.email}
                    title="Contact Email"
                    placeholder="Enter contact email"
                    type="email"
                />
            </StepperPanel>
            <StepperPanel header="Project Details">
                <InputTextField<CreateProject>
                    value={c => c.name}
                    title="Project Name"
                    placeholder="Enter project name"
                />
                <TextAreaField<CreateProject>
                    value={c => c.description}
                    title="Description"
                    placeholder="Describe the project"
                    rows={4}
                />
            </StepperPanel>
            <StepperPanel header="Budget">
                <NumberField<CreateProject>
                    value={c => c.budget}
                    title="Budget"
                    placeholder="Enter budget"
                />
            </StepperPanel>
        </StepperCommandDialog>
    );
};
```

**Rules:**
- Each `StepperPanel` takes a `header` string — this is the step label shown in the wizard navigation bar
- All `CommandForm` fields inside any `StepperPanel` are bound to the **same** command instance
- Fields map to command properties via the `value={c => c.propertyName}` accessor
- The `Next` button is disabled while the current step has validation errors
- `Submit` only appears on the **last** step when all fields (across all steps) are valid

---

## Step 3 — Wire the dialog to a parent component

```tsx
import { useDialog } from '@cratis/arc.react/dialogs';
import { Button } from 'primereact/button';

export const ProjectsPage = () => {
    const [CreateProjectDialogWrapper, showCreateProject] = useDialog(CreateProjectDialog);

    return (
        <>
            <Button label="New Project" onClick={() => showCreateProject()} />
            <CreateProjectDialogWrapper />
        </>
    );
};
```

Use the PrimeReact `Button` (or a Cratis Components wrapper) — never a raw `<button className="p-button">`.

---

## Navigation behavior

| Step | Footer buttons shown |
|------|---------------------|
| First step | **Next** |
| Middle step | **Previous**, **Next** |
| Last step (any step invalid) | **Previous** |
| Last step (all valid) | **Previous**, **Submit** |

Cancel is always available via the **×** button in the dialog header.

---

## Validation indicators

Step number circles in the wizard navigation bar change color to reflect validity:

- **Red circle** — the step contains at least one field with a validation error
- **Green circle** — the step has been visited (navigated through) and all its fields are valid
- **Default color** — the step has not been visited yet
- **Dimmed** — a step that is not the currently active step

To trigger validation immediately on open (before the user types anything), pass `validateOnInit`:

```tsx
<StepperCommandDialog
    command={CreateProject}
    validateOnInit
    ...
>
```

This is useful when the dialog opens with pre-populated values that may already be invalid.

---

## Customizing step labels

The `okLabel`, `nextLabel`, and `previousLabel` props override the default button text:

```tsx
<StepperCommandDialog
    command={CreateProject}
    title="Register Employee"
    okLabel="Register"
    nextLabel="Continue →"
    previousLabel="← Back"
    ...
>
```

---

## Pre-populating values (edit wizard)

Use `currentValues` for fields the user can change and `initialValues` for hidden/fixed fields (e.g. an ID):

```tsx
<StepperCommandDialog
    command={UpdateProject}
    currentValues={{ name: project.name, description: project.description }}
    initialValues={{ projectId: project.id }}
    ...
>
```

---

## Stepper orientation

For longer wizards, vertical orientation can be more readable:

```tsx
<StepperCommandDialog orientation="vertical" ...>
    <StepperPanel header="Step One"> ... </StepperPanel>
    <StepperPanel header="Step Two"> ... </StepperPanel>
</StepperCommandDialog>
```

---

## Props reference

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `command` | `Constructor<TCommand>` | — | **Required.** Command class constructor |
| `title` | `string` | — | **Required.** Dialog header title |
| `children` | `StepperPanel[]` | — | **Required.** Wizard steps |
| `okLabel` | `string` | `'Submit'` | Submit button label (last step) |
| `nextLabel` | `string` | `'Next'` | Next button label |
| `previousLabel` | `string` | `'Previous'` | Previous button label |
| `visible` | `boolean` | `true` | Controls dialog visibility |
| `width` | `string` | `'600px'` | Dialog width |
| `isValid` | `boolean` | — | Extra validity gate combined with form validity |
| `validateOnInit` | `boolean` | — | Run validation on mount to show errors immediately |
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Stepper layout direction |
| `linear` | `boolean` | `true` | Require steps to be completed in order |
| `initialValues` | `Partial<TCommand>` | — | Fixed initial values (not shown to user as editable) |
| `currentValues` | `Partial<TCommand>` | — | Pre-populated editable values |
| `onConfirm` | `() => void \| Promise<void>` | — | Called after successful execute |
| `onCancel` | `() => void \| Promise<void>` | — | Called when user dismisses dialog |
| `onBeforeExecute` | `(values: TCommand) => TCommand` | — | Transform values before execution |
| `pt` | `StepperProps['pt']` | — | PrimeReact PassThrough for deep DOM customization |

---

## Common mistakes

| Mistake | Fix |
|---------|-----|
| Putting a Cancel button in the footer | Don't — the × in the dialog header is the cancel action |
| One step per field | Group related fields; aim for 2–5 fields per step |
| Fields from different steps sharing the same `value` accessor | Each property should appear on exactly one step |
| Forgetting `header` on `StepperPanel` | Always set `header` — it is the navigation label |
| Using `CommandDialog` for a 4+ field form | Consider `StepperCommandDialog` to reduce cognitive load |

