Skip to main content

FlowOutlet

FlowOutlet is where the flow appears in the UI. It renders the active step, the loading state, the error state, and the idle content for the flow host.

FlowOutlet is not exposed directly. It is returned as SequentOutlet from useSequentFlow, which binds the outlet to its control functions and reactive state.

Props

type SequentOutletProps = {
children?: ReactNode;
fallback?: ReactNode;
errorStep?: (context: ErrorStepContext) => ReactNode;
chrome?: (children: ReactNode) => ReactNode;
transition?: (props: TransitionSlotProps) => ReactNode;
};

type ErrorStepContext = {
error: unknown;
componentStack: string | null;
failedStep: ComponentType;
};

type TransitionSlotProps = {
previousStep: ReactNode | null;
nextStep: ReactNode;
phase: "exiting" | "entering" | "exited";
onExited: () => void;
transitionKey: number;
};

children

Content rendered when no flow is active.

See: Idle state below.

fallback

The placeholder shown while the active step is loading.

This is the loading surface for async step loaders before the step becomes available.

Behavior:

  • shown only while the current step is loading, defined as the time between an async step loader being activated and its promise resolving
  • hidden once the step is ready
  • if omitted, no loading UI is shown
  • should be lightweight, because it may appear during transitions

errorStep

Custom error UI for a step that fails while rendering or during a step transition.

The callback receives an ErrorStepContext object with:

  • error — the thrown value
  • componentStack — the React component stack when available
  • failedStep — the step component that failed

Behavior:

  • used when the active step throws an error
  • replaces the active step UI with your error UI
  • if omitted, the outlet renders no special error UI

chrome

A wrapper for the active step area.

This is the surrounding UI for the active step, such as:

  • a layer and frame for a modal or side panel
  • a card or sheet layout
  • a header with a close button
  • a progress indicator
  • a fixed footer or toolbar

Behavior:

  • receives the rendered step area as children
  • wraps the active step and any loading or error UI inside that shell
  • is not used while the outlet is idle
  • if omitted, the outlet renders the step area directly

transition

An optional render prop that enables animated step transitions.

When provided, the outlet enters transition mode: instead of immediately swapping steps, it renders both the exiting and entering step simultaneously and passes them to this render prop along with phase information and lifecycle callbacks.

The consumer wires their animation library (CSS, framer-motion, GSAP, etc.) to the provided signals. Ready-made CSS transitions are available from react-sequent/transitions — see Transitions.

Step subtrees are rendered through portals into stable hosts, so previousStep and nextStep are lightweight placeholders you can position anywhere in your layout — the outgoing step keeps its component instance (local state and effects intact) while it animates out, even though your wrapper structure changes between phases.

TransitionSlotProps:

  • previousStep — the exiting step element. null when no transition is in flight.
  • nextStep — the entering/current step element. Always present when a flow is active.
  • phase — current transition phase:
    • "exiting" — previous step is animating out; call onExited when done.
    • "entering" — previous step has unmounted; enter-animation window for one tick.
    • "exited" — the flow is settled; no animation in progress.
  • onExited — call to signal the exit animation has completed. Only meaningful during the "exiting" phase.
  • transitionKey — monotonically increasing identity for the current transition. Attach as a React key on animation wrapper elements to force remounting across back-to-back transitions.

Constraints:

  • A step may only initiate one transition — subsequent advance()/retreat() calls from the same step render are silently dropped.
  • While in "exiting" phase, the entering step may enqueue a navigation.
  • resolve()/abort() during any phase tears down both steps immediately; onExited is not expected.
  • Chrome wraps the transition output and is never unmounted during a transition.
  • When omitted, step swaps are immediate (current behavior).

Public states

The outlet has a small set of user-facing states that matter when you build against it.

Idle

No flow is currently active.

What you see:

  • children

What this means:

  • this is the default state before a flow starts
  • idle content reappears after a flow ends
  • this is the place for entry UI or post-flow content

Active

A flow is in progress and the current step is being shown.

What you see:

  • the active step
  • optional chrome around that step

What this means:

  • the step uses the flow and step hooks to continue, resolve, or abort the flow
  • the outlet no longer renders the idle children

Transitioning

When the transition prop is provided, step changes pass through animated phases instead of swapping instantly.

What you see depends on phase:

  • exiting — both the previous and next step are mounted. The consumer plays an exit animation.
  • entering — the previous step has unmounted. The next step renders alone (enter-animation window, one tick).
  • exited — the flow is settled; same as the normal Active state.

The transition render prop receives all step elements, the current phase, onExited (call to complete the exit), and transitionKey (for animation wrapper remounting).

Loading

The current step is not yet ready because its loader is asynchronous.

What you see:

  • fallback

What this means:

  • this state is temporary
  • it exists only for async step loaders
  • the current flow remains in progress while the fallback is shown

Error

The active step failed.

What you see:

  • the result of errorStep, if provided

What this means:

  • the error UI may describe the failure and can provide a retry path
  • the outlet remains in an error state until the flow changes or is restarted

Finished

The flow resolved or was aborted.

What you see:

  • the outlet returns to idle and renders children again

What this means:

  • the flow is no longer active
  • this is the terminal outcome for the current run of the flow
  • the next flow starts from the idle state again

Example

function CheckoutShell() {
const { SequentOutlet } = useSequentFlow();

return (
<SequentOutlet
fallback={<div>Loading step…</div>}
errorStep={({ error }) => (
<div>
<h2>Something went wrong</h2>
<pre>{String(error)}</pre>
</div>
)}
chrome={(step) => <div className="sheet">{step}</div>}
>
<main>
<p>Nothing is active yet.</p>
</main>
</SequentOutlet>
);
}

Relationship to useSequentFlow

useSequentFlow returns the flow controller and the bound outlet component together.

The outlet defines the visible shell for the flow. The control functions start the flow and report its status and result.

A good mental model is:

  • the hook starts and reports on the flow
  • the outlet renders the flow
  • the steps perform the flow logic

Conclusion

The outlet is the place where flow UI lives:

  • children for idle content
  • fallback for loading UI
  • errorStep for error UI
  • chrome for surrounding layout

That keeps the outlet easy to reason about and keeps flow logic inside the hooks and steps where it belongs.