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

Should I Use useFlow?

useFlow is purpose-built for multi-step UI flows with built-in persistence and analytics. Use this guide to determine if it’s the right fit for your project.

If you’re building flows with 2+ steps where users progress through sequential or branching paths:

  • Onboarding flows - Welcome → Profile → Preferences → Complete
  • Checkout processes - Cart → Shipping → Payment → Confirmation
  • Application forms - Personal Info → Employment → Documents → Review
  • Wizards - Setup → Configuration → Testing → Deploy

If users should continue where they left off after closing browser/app:

  • Long forms that users might abandon and return to
  • Complex multi-session processes
  • Mobile users with interrupted sessions
  • Critical flows where lost progress = lost users

If you need to track user behavior through your flow:

  • Measure conversion at each step
  • Identify drop-off points
  • A/B test different flow versions
  • Monitor user journey completion rates

If you want compile-time safety for your flow logic:

  • Catch navigation errors before runtime
  • Autocomplete for step names and context
  • Refactor flows with confidence
  • Large teams need consistent flow patterns

For one-page forms without steps:

// ❌ Overkill: Simple login form
<Flow flow={loginFlow}>
{({ renderStep }) => renderStep({
login: <LoginForm />
})}
</Flow>
// ✅ Better: Just use a form library
<form onSubmit={handleLogin}>
<input name="email" />
<input name="password" />
<button>Login</button>
</form>

Use instead: React Hook Form, Formik, or vanilla forms

For intricate business logic beyond UI flows:

// ❌ Wrong tool: Complex state machine
const [state, send] = useActor(complexBusinessMachine);
// ✅ Better: Use XState for complex logic
const [state, send] = useMachine(orderProcessingMachine);

Use instead: XState for complex state machines

For basic page navigation:

// ❌ Overkill: Simple page routing
<Flow flow={pageFlow}>
{({ renderStep }) => renderStep({
home: <HomePage />,
about: <AboutPage />
})}
</Flow>
// ✅ Better: Use a router
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>

Use instead: Next.js Router, React Router, or similar

Ask yourself these questions:

  • 1 step: Use a form library
  • 2+ steps: ✅ Consider useFlow
  • Not right now: That’s fine! Adding persistence later is one line of code
  • Yes, definitely: ✅ Perfect for useFlow

The reality: Most multi-step flows eventually need persistence. Users will close the tab, their phone will die, they will expect to continue where they left off. With useFlow, it’s trivial to add when (not if) you need it.

  • Not tracking yet: No problem! Analytics hooks are built-in when you’re ready
  • Need conversion data: ✅ Great fit for useFlow

The reality: You’ll want to know where users drop off. Even if you don’t need analytics today, when product asks “where are users getting stuck?”, you’ll be glad it’s just adding an onNext callback.

  • Simple linear flow: ✅ Perfect for useFlow
  • Some branching: ✅ Great for useFlow
  • Complex state machine: Consider XState
  • Small team, simple needs: Any solution works
  • Large team, needs consistency: ✅ useFlow provides structure

E-commerce Checkout

const checkoutFlow = defineFlow({
id: "checkout",
start: "cart",
steps: {
cart: { next: "shipping" },
shipping: { next: "payment" },
payment: { next: "confirm" },
confirm: {},
},
});

Benefits:

  • ✅ Users don’t lose cart on page refresh
  • ✅ Track exactly where users drop off (via analytics hooks)
  • ✅ Type-safe step navigation
  • ✅ Easy to add A/B testing when needed

User Onboarding

const onboardingFlow = defineFlow({
id: "onboarding",
start: "welcome",
steps: {
welcome: { next: "profile" },
profile: { next: "preferences" },
preferences: { next: "complete" },
complete: {},
},
});

Benefits:

  • ✅ New users can complete onboarding across sessions
  • ✅ Track which steps cause drop-offs
  • ✅ Skip steps for returning users
  • ✅ Measure onboarding completion rates

Contact Form

// ❌ Overkill for single form
<Flow flow={contactFlow}>
{({ renderStep }) => renderStep({
contact: <ContactForm />
})}
</Flow>
// ✅ Much simpler
<ContactForm />

Page Navigation

// ❌ Wrong tool for routing
<Flow flow={navigationFlow}>
{({ renderStep }) => renderStep({
home: <HomePage />,
about: <AboutPage />,
contact: <ContactPage />
})}
</Flow>
// ✅ Use proper routing
<Router>
<Route path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
<Route path="/contact" component={ContactPage} />
</Router>

useFlow is lightweight - under 5KB gzipped with zero dependencies.

The real comparison: Building equivalent functionality yourself (navigation, persistence, type safety, analytics hooks) would likely result in more code than useFlow itself. You’re trading a small, tested library for maintaining your own implementation.

Still unsure? Here are some quick questions:

Can I use useFlow with my existing form library?

Yes! useFlow complements form libraries perfectly:

function ProfileStep() {
const { context, setContext, next } = useFlowState();
// Use your favorite form library for validation
const form = useForm({
defaultValues: context,
onSubmit: (values) => {
setContext(values); // Save to flow context
next(); // Navigate to next step
},
});
return <form onSubmit={form.handleSubmit}>{/* ... */}</form>;
}
How do I migrate from my existing solution?

Start incrementally:

  1. Identify one multi-step flow in your app (pick the simplest one first)
  2. Rewrite it with useFlow - should be faster and cleaner than original
  3. Test thoroughly to ensure identical user experience
  4. Deploy when confident, then migrate other flows
  5. Remove old code as you replace each flow

No need to rewrite everything at once! Pick flows one by one and migrate as your team has capacity.

What if I only need 2 steps?

2 steps is perfect for useFlow! You still get:

  • ✅ Automatic progress persistence
  • ✅ Navigation management
  • ✅ Analytics hooks
  • ✅ Type safety

Even “simple” 2-step flows become complex when you add validation, back navigation, and persistence.

Is the learning curve worth it?

useFlow is extremely simple:

  • 5 minutes: Read the Quick Start and understand the concepts
  • 10 minutes: Build your first working flow
  • 2 minutes: Add persistence (one line of code)
  • 2 minutes: Add analytics hooks (one callback)

That’s it. There’s not much to learn - it’s just declarative flow definitions with a few simple hooks.

Ready to get started?

Still evaluating?