Navigation
Navigate between workflow steps with validation, conditions, and skip logic.
Workflow navigation lives on the useFlow() hook: forward/backward movement, skipping, validation gating, conditional visibility, and cross-step data.
Navigation Methods
All methods come from useFlow() and return Promise<boolean> (did the navigation succeed).
import { useFlow } from '@rilaykit/workflow/react';
function MyNavigation() {
const { goNext, goPrevious, goToStep, skipStep, canGoNext, canGoPrevious } = useFlow();
return (
<div className="flex gap-2">
<button onClick={() => goPrevious()} disabled={!canGoPrevious()}>Back</button>
<button onClick={() => skipStep()}>Skip</button>
<button onClick={() => goNext()} disabled={!canGoNext()}>Next</button>
<button onClick={() => goToStep(0)}>First step</button>
</div>
);
}| Method | Signature | Description |
|---|---|---|
goNext() | () => Promise<boolean> | Runs onAfterValidation, marks the step passed, then advances to the next visible step. |
goPrevious() | () => Promise<boolean> | Goes to the previous visible step. No validation. |
goToStep(index) | (stepIndex: number) => Promise<boolean> | Jumps to a step by its original index. Fails if hidden or out of bounds. |
skipStep() | () => Promise<boolean> | Skips without validation when the step is skippable. Fires onStepSkip. Skipping the last visible step completes the workflow. |
Field validation belongs to the step's form: <FlowNext> submits the form (validating per its mode/reValidateMode) and only calls goNext() on success. Calling goNext() directly does not run field validation.
Navigation Guards
Guards check whether an action is possible — use them to disable buttons.
| Guard | Returns true when |
|---|---|
canGoNext() | A visible step exists after the current one. |
canGoPrevious() | A visible step exists before the current one. |
canGoToStep(index) | The target step is in bounds and visible. |
canSkipCurrentStep() | allowSkip resolves to true or the skippable condition is true. |
Automatic Step Skipping
Hidden steps are skipped transparently: goNext() and goPrevious() land on the nearest visible step in their direction.
Steps: [1: visible] [2: hidden] [3: hidden] [4: visible]
goNext() from step 1 --> lands on step 4
goPrevious() from step 4 --> lands on step 1If the current step itself becomes hidden (a condition changed while viewing it), the workflow relocates to the nearest visible step — forward first, then backward.
Step Conditions
Conditions control visibility and skippability from workflow data, using the when() builder from @rilaykit/core.
interface StepConditionalBehavior {
visible?: ConditionConfig; // false = hidden and skipped over
skippable?: ConditionConfig; // true = step can be skipped
}import { when } from '@rilaykit/core';
const workflow = flow.create(rilay, 'onboarding', 'Onboarding')
.step({ id: 'personal-info', title: 'Personal Info', formConfig: personalInfoForm })
.step({
id: 'company-info',
title: 'Company Info',
formConfig: companyInfoForm,
conditions: {
// Only show this step when accountType is "business"
visible: when('accountType').equals('business').build(),
},
})
.step({
id: 'preferences',
title: 'Preferences',
formConfig: preferencesForm,
conditions: {
skippable: when('hasExistingPreferences').equals(true).build(),
},
});You can also attach conditions after the fact with .addStepConditions(stepId, conditions).
Conditions evaluate against a flattened view of all workflow data: fields from any step are addressable by field ID when evaluating conditions on another step.
Skipping rules
A step is skippable when allowSkip resolves to true or its skippable condition does. allowSkip is a boolean or a predicate over the collected data:
allowSkip: ({ allData }) => allData.plan === 'free'Skipped steps are absent from the completion payload and listed in meta.skippedSteps. Navigating back onto a skipped step lets you skip it again — or complete it, which removes it from the skipped set. <FlowSkip> renders only when the current step is skippable.
The onAfterValidation Callback
Runs after the step's form passes validation, before the transition. Use it for API calls on the validated data, pre-filling later steps, or external checks.
.step({
id: 'registration',
title: 'Business Registration',
formConfig: registrationForm,
onAfterValidation: async (stepData, helper, context) => {
const companyInfo = await fetchCompanyBySiren(stepData.siren);
// Pre-fill the next visible step
helper.setNextStepFields({
companyName: companyInfo.name,
address: companyInfo.address,
});
// Or target a specific step by ID
helper.setStepFields('company-details', { legalForm: companyInfo.legalForm });
},
})If onAfterValidation throws, navigation is cancelled and the user stays on the current step (the error is also reported to analytics/monitoring).
onAfterValidation?: (
stepData: Record<string, any>,
helper: StepDataHelper,
context: WorkflowContext
) => void | Promise<void>;StepDataHelper
Read and write data across steps from within onAfterValidation. The setNextStep* methods target the next visible step — a conditionally hidden step in between never swallows the prefill.
interface StepDataHelper {
setStepData(stepId: string, data: Record<string, any>): void; // replace a step's data
setStepFields(stepId: string, fields: Record<string, any>): void; // merge fields into a step
getStepData(stepId: string): Record<string, any>;
setNextStepField(fieldId: string, value: any): void;
setNextStepFields(fields: Record<string, any>): void;
getAllData(): Record<string, any>;
getSteps(): StepConfig[];
}Navigation Flow Summary
When the user clicks Next:
- Form validation — the step's form validates; errors block navigation.
- onAfterValidation — called with the validated data and helper; a throw cancels navigation.
- Step marked passed — the step ID joins the
passedStepsset. - Find next visible step — hidden steps are skipped over.
- Transition —
onStepChangefires, the index updates, the new step is marked visited. If no visible step remains, the workflow completes instead.