Save Bar

Pro

The unsaved-changes bar of a settings form: it rises from the bottom edge once the form is dirty, sticks to the bottom of whatever scrolls, and sinks back on save or discard.

A summary every Monday.
Email me when someone mentions me.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add save-bar

Usage#

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Anatomy#

import { SaveBar, SaveBarMessage, SaveBarActions } from "@/components/ui/save-bar"<SaveBar>  <SaveBarMessage />  <SaveBarActions /></SaveBar>

Placement#

Put the bar last inside the column it saves. It is sticky to the bottom of the nearest scroller, so the same markup works in a panel that scrolls on its own (the demo above) and in a page that grows and lets the window scroll (the docked app shell drops its sheet overflow when the content fits). It stays pinned while its column is on screen and settles into its own slot at the end, so it never covers the last field. The slot is always there, which keeps the layout still when the bar comes and goes. An ancestor with overflow set that does not actually scroll would capture the sticky, so keep the column free of it.

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Motion#

It rises 12px and fades in on duration-base with an ease-out, and sinks back faster on duration-fast with an ease-in, so saving feels like the bar is put away rather than dismissed. With reduced motion it only fades.

Surface#

The default bar is inverted: dark in every theme, the buttons inside resolve against the dark theme on their own. variant="surface" keeps the popover surface of the page theme for quieter screens. Edit a field to see it.

A summary every Monday.
Email me when someone mentions me.

Message and actions#

The words are yours: count the pending edits, or name the actions after what they do. Keep the pair to a ghost and a primary, the primary last.

3 unpublished edits

API reference#

SaveBar

The root: an in-flow slot, sticky to the bottom of the nearest scroller, holding the bar itself (role="region"). It stays mounted while closed, inert and hidden from assistive tech, so the exit is a real transition. className and the remaining props go to the bar; every part accepts className, merged last.

PropTypeDefaultDescription
open*boolean-Shows the bar. Pass the form's dirty state.
variant"inverted" | "surface""inverted"inverted is a dark bar in every theme, so it reads as a layer above the page; surface is the popover surface of the current theme.
labelstring"Unsaved changes"The region's accessible name. Falls back to unsavedChanges from the nearest LabelsProvider.
Data attributeDescription
data-state"open" or "closed", on the bar (data-slot="save-bar-content").

SaveBarMessage

The muted status text, truncated when space runs out. Without children it says unsavedChanges from the nearest LabelsProvider. Forwards all native <span> props.

SaveBarActions

The row of buttons, kept from shrinking. Put the quiet action first as a ghost Button and the primary last. Forwards all native <div> props.

saveBarVariants

The tv recipe behind every part.

FAQ#

A fixed bar is centred on the window, not on the form, and it floats over everything when the form sits in a panel or a dialog. A sticky bar belongs to its column: it centres over it, follows it in any scroller and leaves when the column scrolls away.