react-screen-flow is a small, typed workflow engine for React apps that move through named screens: onboarding, checkout, signup, profile completion, permission gates, and router-backed flows.
The library keeps flow rules in one place. Your app owns the real state; react-screen-flow decides which screen is active, whether navigation is allowed, and how async guards feel in the UI.
pnpm add @hbarkallah/react-screen-flow
This package expects React to be provided by your app.
import { useScreenFlow, type FlowDefinition } from "@hbarkallah/react-screen-flow";
type AppState = {
user: { name: string } | null;
profileCompleted: boolean;
};
function LoginScreen() {
return <h2>Login</h2>;
}
function DashboardScreen() {
return <h2>Dashboard</h2>;
}
function CheckoutScreen() {
return <h2>Checkout</h2>;
}
const screens = {
login: LoginScreen,
dashboard: DashboardScreen,
checkout: CheckoutScreen,
};
const flow: FlowDefinition<AppState, typeof screens> = {
screens,
loadingStrategy: "deferred",
resolveStart: (state) => {
if (!state.user) return "login";
if (!state.profileCompleted) return "dashboard";
return "checkout";
},
transitions: {
login: {
next: "dashboard",
canGoNext: (state) => Boolean(state.user),
},
dashboard: {
prev: "login",
next: "checkout",
canGoNext: (state) =>
state.profileCompleted
? true
: {
allowed: false,
message: "Complete your profile first.",
},
},
checkout: {
prev: "dashboard",
},
},
};
export function FlowView({ state }: { state: AppState }) {
const flowState = useScreenFlow(flow, state, {
autoNavigate: true,
});
const ScreenComponent = flowState.ScreenComponent;
return (
<main>
{flowState.isTransitioning ? <p>Checking...</p> : null}
<ScreenComponent />
{flowState.lastBlocked ? <p>{flowState.lastBlocked}</p> : null}
<button onClick={() => void flowState.goPrev()} disabled={!flowState.canGoPrev}>
Back
</button>
<button onClick={() => void flowState.goNext()} disabled={!flowState.canGoNext}>
Next
</button>
</main>
);
}
react-screen-flow for typed business decisions about which screen is allowed.react-screen-flow when your model is mostly screens, guards, and branching.react-screen-flow when flow order depends on external app state, async guards, redirects, or router sync.import {
createFlowEngine,
useScreenFlow,
applyTransitionResult,
resolveFlowLoadingStrategy,
} from "@hbarkallah/react-screen-flow";
import type {
FlowDefinition,
FlowEngine,
LoadingStrategy,
TransitionResult,
GuardDecision,
ScreenIdOf,
} from "@hbarkallah/react-screen-flow";
Most React apps should use useScreenFlow. createFlowEngine evaluates flows outside React or in tests. For router-backed flows, add a small route sync helper in your app — see the React Router guide.
For deeper internals, see the Flow Engine and Runtime reference pages.