Save bar
A sticky bottom strip that slides up the moment a form has unsaved changes. Carries a dirty-state label on the left and Discard + Save on the right.
Default
Add .dirty when the form has unsaved changes; remove it on save / discard.
With save status
Use the summary slot for autosave timestamps or validation hints.
Parts
| Property | Value | Notes |
|---|---|---|
| position | sticky; bottom: 0 | Lives inside the form / panel that scrolls. |
| padding | 12 16 (+ safe-area) | Adds env(safe-area-inset-bottom) on iOS. |
| border-top | 1 px --border-strong | Reads as a divider, not a card. |
| shadow | 0 -6 16 -8 | Soft lift to suggest "above the content." |
| transform | translateY(110%) → 0 | Slide-up on .dirty; 240 ms ease-out. |
| label | flex 1 | Title + optional summary line. |
| actions | flex none | Discard (ghost) + Save (primary). Save sits right. |
Do & don't
Show the bar the instant the form goes dirty. The operator should never wonder whether their edits are tracked.
Hide the bar behind a fixed footer or bottom nav. It needs to sit above the page chrome.
Summarise what changed in the summary slot — "3 fields edited", "Quantity adjusted". Operators trust what they can see.
Use destructive styling for Discard. It's a soft action that loses unsaved work, not a delete.
Disable Save while validation is failing — but keep the bar visible so the operator knows changes are pending.
Auto-save and show the bar at the same time. Pick one mental model per surface.
Accessibility
The bar appears and disappears — screen readers need to know.
- Wrap the bar in
role="region" aria-label="Unsaved changes"so it lands in landmark navigation. - Move keyboard focus to the bar (or its primary button) when the form first goes dirty, only if the operator triggered the dirty state via the keyboard.
- Discard should require a confirm step on irreversible edits (use alert dialog).
- Respect
prefers-reduced-motion: the slide-up reduces to a fade in the global motion rule.
Code
<!-- Sticky save bar — toggle .dirty when the form has unsaved changes --> <div class="p-save-bar dirty" role="region" aria-label="Unsaved changes"> <div class="label"> <div class="title">Unsaved changes</div> <div class="summary">3 fields edited</div> </div> <div class="actions"> <button class="btn btn-ghost" type="button">Discard</button> <button class="btn btn-primary" type="submit">Save changes</button> </div> </div>
Related
For a soft confirmation that a save just happened, see Record saved banner. For the whole-form chrome, see Form shell.