Skip to content
⚠️ Beta: API may change before v1.0. Pin to ~0.x.0 to avoid breaking changes.

useFlow

Build multi-step flows without the complexity.

Declarative

Define your flow in one place. Navigation logic lives in your flow config, not scattered across components.

Built-in Persistence

Two lines of code. Users resume exactly where they left off.

Type-Safe

Full TypeScript support. Autocomplete for step names, compile-time navigation errors.

Lightweight

Under 5KB gzipped. Zero dependencies. Just React.

Build a complete onboarding flow with conditional navigation in 3 simple steps:

  1. import { defineFlow } from "@useflow/react";
    type OnboardingContext = {
    email?: string;
    accountType?: "business" | "personal";
    company?: string;
    };
    const onboardingFlow = defineFlow({
    id: "onboarding",
    start: "welcome",
    steps: {
    welcome: { next: "userType" },
    userType: { next: ["business", "personal"] }, // 💡 Define all possible next steps
    business: { next: "complete" },
    personal: { next: "complete" },
    complete: {}
    }
    // 💡 Set the context type
    }).with<OnboardingContext>((steps) => ({
    resolvers: {
    // 💡 Type-safe: can only return steps in next array
    userType: (ctx) =>
    ctx.accountType === "business"
    ? steps.business // ✅ Valid
    : steps.personal // ✅ Valid
    // steps.complete would be a TypeScript error ❌
    }
    }));
  2. import { onboardingFlow } from "./flow";
    function UserTypeStep() {
    const { context, setContext, next } = onboardingFlow.useFlowState({ step: "userType" });
    const handleSubmit = () => {
    next(); // ✅ Automatically navigates based on accountType
    };
    return (
    <div>
    <h1>Get Started</h1>
    <input
    type="email"
    placeholder="Email address"
    value={context.email || ""} // 💡 TypeScript knows this is a string
    onChange={(e) => setContext({ email: e.target.value })}
    />
    <select
    value={context.accountType || ""} // 💡 TypeScript knows this is a string
    onChange={(e) => setContext({ accountType: e.target.value })}
    >
    <option value="">Choose account type</option>
    <option value="personal">Personal</option>
    <option value="business">Business</option>
    </select>
    <button
    onClick={handleSubmit}
    disabled={!context.email || !context.accountType}
    >
    Continue
    </button>
    </div>
    );
    }
    import { onboardingFlow } from "./flow";
    function BusinessStep() {
    const { context, setContext, next, back } = onboardingFlow.useFlowState({ step: "business" });
    return (
    <div>
    <h1>Business Details</h1>
    <p>Welcome {context.email}!</p>
    <input
    placeholder="Company name"
    value={context.company || ""} // 💡 TypeScript knows this is a string
    onChange={(e) => setContext({ company: e.target.value })}
    />
    <button onClick={back}>Back</button>
    <button onClick={next}>Continue</button>
    </div>
    );
    }
    import { onboardingFlow } from "./flow";
    function CompleteStep() {
    const { context } = onboardingFlow.useFlowState({ step: "complete" });
    return (
    <div>
    <h1>All Set, {context.name}!</h1>
    <p>Email: {context.email}</p>
    {context.userType === "business" && (
    <p>Company: {context.business?.companyName}</p>
    )}
    </div>
    );
    }
  3. Map your steps to components & add persistence

    Section titled “Map your steps to components & add persistence”
    import { Flow, createLocalStorageStore, createPersister } from "@useflow/react";
    import { onboardingFlow } from "./flow";
    function App() {
    return (
    <Flow
    flow={onboardingFlow}
    // ✅ Add persistence in 1 line!
    persister={createPersister({ store: createLocalStorageStore() })}
    >
    {({ renderStep }) => renderStep({
    // 💡 TypeScript enforces all steps must be provided - can't miss any!
    welcome: <WelcomeStep />,
    userType: <UserTypeStep />,
    business: <BusinessStep />,
    personal: <PersonalStep />,
    complete: <CompleteStep />
    })}
    </Flow>
    );
    }

That’s it! Users can now close their browser and return exactly where they left off. No manual state management, no confusing navigation logic scattered across components.

  • 🚀 Onboarding flows - User registration with multiple steps
  • 🛒 Checkout processes - Cart, shipping, payment, confirmation
  • 📝 Surveys & forms - Long questionnaires users can resume
  • ⚙️ Configuration wizards - Complex setup flows broken into simpler steps

Remote Configuration

Modify flows without redeploying.

A/B Testing

Test flow variants. See which path converts best.

Drop-in Analytics

Track completion rates and drop-offs with a few lines of code.