ContextualSaveBar
v1.0.7Updated 27 Aug 2026Polaris-style dirty-form save bar. A portal-rendered bar docked to the top or bottom edge that slides in while a form has unsaved changes, with a Discard action (optional confirm dialog) and a Save action with loading sp
Preview
Live · 9 stories▸Why this is recommendedsavedirtyunsavedform+5
The default, out-of-the-box ContextualSaveBar.
- ✓Settings / profile edit pages
- ✓Record detail forms (customer, invoice, product)
- ✓Bulk-edit screens where changes accumulate before an explicit save
- Controlled (open) or uncontrolled (defaultOpen) visibility
- Top or bottom docking with slide-in animation (skipped under prefers-reduced-motion)
- Portal-rendered — document.body (fixed) or a custom container element (absolute)
- Save with loading state — controlled `saving` flag or automatic from a Promise-returning onSave
- Save disabled while `invalid`
- Discard with optional confirm dialog (confirmDiscard: true or custom copy)
- Customizable message + secondary detail slot
- Opt-in beforeunload tab-close guard while dirty
- Opt-in Ctrl/⌘+S save shortcut
- Polite live-region announcement + labelled role="region" for screen readers
- useDirtyState helper with stable (key-order-independent) JSON comparison
- i18n: labels prop overrides the built-in screen-reader strings (region aria-label, unsaved-changes announcement); visible copy already has message/saveLabel/discardLabel props — see ContextualSaveBarLabels / DEFAULT_CONTEXTUAL_SAVE_BAR_LABELS
- ✕Transient feedback (use Sonner toasts)
- ✕Wizard/step flows with per-step commit (use Wizard)
- ✕Auto-saving forms with no explicit save action
Install
Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.
Usage
Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.
Props
openControlled visibility — pass the dirty flag (e.g. from useDirtyState)
—defaultOpenUncontrolled initial visibility; bar closes itself after save/discard
—onOpenChangeVisibility change callback
—onSaveSave handler; a returned Promise drives the spinner and closes on resolve
yesonDiscardDiscard handler — reset form state here
yessavingControlled saving flag (spinner + locked actions)
—invalidDisables Save (e.g. validation errors)
—messageUnsaved-changes message (default "Unsaved changes")
—detailSecondary line under the message
—positionDocking edge (default top)
—confirmDiscardConfirm dialog before discarding; object customises copy
—confirmBeforeUnloadRegister beforeunload guard while the bar is open
—saveOnShortcutOpt-in Ctrl/⌘+S triggers Save while open
—containerPortal target; defaults to document.body
—labelsOverride the built-in screen-reader strings — region aria-label + unsaved-changes announcement (i18n). Visible copy uses message/saveLabel/discardLabel/confirmDiscard props. Merged over DEFAULT_CONTEXTUAL_SAVE_BAR_LABELS.
—