layout.title

layout.subtitle

@ng-orbit/wizard

Wizard

A headless wizard controller for multi-step flows where the consumer keeps ownership of forms, layout, validation, and submission side effects.

Use orbitWizard for navigation state and completion rules, then pair it with your own form UI or an optional renderer package.

Install @ng-orbit/wizard

    pnpm add @ng-orbit/wizard @ng-orbit/wizard-kit
  

Import

    import { OrbitWizardDirective, type OrbitWizardStepDef } from '@ng-orbit/wizard';
import { OrbitWizardStepFormSyncDirective } from '@ng-orbit/wizard-kit';
  

Mental model

OrbitWizard owns navigation state, visited steps, completion rules, and validity tracking. The consumer owns forms, fields, and actual side effects.

  • No CSS or design-system assumptions in the core package.
  • No form generation or field ownership.
  • Optional renderers stay focused on UI composition.

What the consumer owns

You decide how each step is rendered, when data is saved, and which validations should block moving forward.

  • Provide step definitions.
  • Sync form validity into the controller.
  • Handle submit, autosave, analytics, and route guards at the app level.

Why the kit matters

Wizard examples often rely on OrbitWizardStepFormSyncDirective because it keeps reactive form validity aligned with the headless controller.

  • The directive is optional but useful for form-backed steps.
  • You can still call wizard.setValid(stepId, valid) manually if you prefer.
  • Renderers never own your submission rules.

Quickstart

    import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { OrbitWizardDirective, type OrbitWizardStepDef } from '@ng-orbit/wizard';
import { OrbitWizardStepFormSyncDirective } from '@ng-orbit/wizard-kit';

@Component({
  standalone: true,
  imports: [ReactiveFormsModule, OrbitWizardDirective, OrbitWizardStepFormSyncDirective],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <section orbitWizard #wizard="orbitWizard" [steps]="steps" (completed)="submit()">
      <form
        [formGroup]="accountForm"
        [orbitWizardStepFormSync]="wizard"
        orbitWizardStepId="account"
        [orbitWizardStepForm]="accountForm"
      >
        <input type="text" formControlName="fullName" />
      </form>

      <button type="button" [disabled]="!wizard.canNext()" (click)="wizard.next()">
        {{ wizard.isLast() ? 'Finish' : 'Next' }}
      </button>
    </section>
  `
})
export class SignupWizardComponent {
  constructor(private readonly formBuilder: FormBuilder) {}

  readonly steps: readonly OrbitWizardStepDef[] = [
    { id: 'account', title: 'Account' },
    { id: 'summary', title: 'Summary', kind: 'summary' }
  ];

  readonly accountForm = this.formBuilder.nonNullable.group({
    fullName: ['', [Validators.required, Validators.minLength(2)]]
  });

  submit(): void {
    // Persist data here. The controller only emits completion.
  }
}