Provider and feedback

Wrap the client part of an application in SubmitFlowProvider to enable built-in dialogs and shared notifications. A provider instance owns its own modal queue and toast host; nested providers are isolated.

"use client";
import { SubmitFlowProvider } from "react-submit-flow";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <SubmitFlowProvider toastDuration={5000} maxVisibleToasts={3}>
      {children}
    </SubmitFlowProvider>
  );
}

Provider props include dialogDefaults, toastDuration, maxVisibleToasts, portalContainer, className, and dir. Set dir="rtl" when the provider-rendered feedback belongs to an RTL subtree; omit it to inherit direction from the surrounding document. The current built-in dialog renderer uses the browser's native <dialog> top layer. portalContainer is part of the public options shape; custom adapter renderers should use it when mounting their own UI.

dialogDefaults.closeOnConfirm controls action confirmations globally. Set it to false to keep every action confirmation open during its action by default. An action's top-level closeOnConfirm overrides the confirmation's option, which overrides this provider default; the final default is true.

<SubmitFlowProvider dialogDefaults={{ closeOnConfirm: false }}>{children}</SubmitFlowProvider>

Now an action can omit its own setting:

publish: {
  run: publishDraft,
  confirm: { title: 'Publish this draft?' },
  pendingLabel: 'Publishing…',
}

The built-in confirm button remains visible and disabled while the action is pending, displaying pendingLabel. Custom adapters must implement retainedConfirmation to support this provider-wide behavior.

Standalone dialog hook

useSubmitFeedback() returns confirm(options) and alert(options). It must be called under a provider. Confirmation returns a boolean; alert returns an acknowledgement or dismissal result.

const feedback = useSubmitFeedback();
const confirmed = await feedback.confirm({
  title: "Delete this item?",
  destructive: true,
  confirmLabel: "Delete",
});
if (confirmed) await deleteItem();

The provider serializes its built-in modal requests. Notifications do not move focus; success messages time out by default and errors remain until dismissed. See Dialogs for dismissal behavior and the standalone demo.