Skip to main content
RapidDev - Software Development Agency
V0 TemplatesUI ComponentBeginner to customize

Save Button V0 Template: 4-State Animation + Server Action Prompts

The Save Button template is a polished four-state button (idle → saving → saved → error) built with Next.js, shadcn/ui Button, and Framer Motion. It handles AnimatePresence icon swaps and animated label transitions between states. Ideal for any form or data-editing interface. The prompt pack covers wiring to a Next.js server action, adding auto-save with react-hook-form, and persisting save timestamps to Supabase.

UI ComponentBeginner~5 minutes

Best for

Any form or data-editing interface that needs a polished animated save state (idle → saving → saved → error)

Stack

Next.jsTypeScriptTailwind CSSshadcn/uiFramer Motion

A ready-made Save Button UI you can fork, run, and customize with the prompt pack below.

What's actually inside

The honest engineer's breakdown — what the Save Buttontemplate does, how it's wired, and where it's opinionated.

The Save Button template is a self-contained state machine wrapping a shadcn/ui Button. The outer SaveButton component manages four states — idle, saving, saved, error — using a single useState enum. Inside it, the StateIcon component swaps between a floppy-disk icon, a loader spinner, a check mark, and an X icon (all from lucide-react) using Framer Motion's AnimatePresence with mode='wait', so the old icon fades out before the new one fades in. The LabelText component mirrors the same transition for the text label, shifting between 'Save', 'Saving…', 'Saved!', and 'Error'.

An optional ProgressIndicator (thin bar or spinner ring) runs during the saving state to give users a visual sense of progress. The ButtonShell uses the shadcn/ui Button's disabled prop wired to the saving state, preventing double-submission. The base Button already ships with a correct focus ring and keyboard accessibility — one of the few areas where the template is better than rolling your own.

Important caveat: the template is purely a button state machine. It does not handle form validation — that is the parent form's responsibility. The simulated async delay in the onClick handler is intentional placeholder logic; you need to replace it with your actual save call using the prompts in the pack below.

Key UI components

SaveButton

Root button component managing all four states (idle, saving, saved, error) via a single useState enum

StateIcon

Switches between floppy-disk, loader spinner, check, and X icons (lucide-react) based on current state

LabelText

Animated text label ('Save', 'Saving…', 'Saved!', 'Error') with Framer Motion fade/slide between states

ProgressIndicator

Optional thin progress bar or spinner ring running during the saving state

ButtonShell

shadcn/ui Button as the base interactive element with disabled prop wired to saving state

Libraries it leans on

Framer Motion

AnimatePresence for smooth icon and label swap animations between all four button states

shadcn/ui (Button)

Accessible base button with focus ring, disabled state, and variant support

lucide-react

Save (floppy-disk), Loader2, Check, and X icons used in StateIcon for each state

Fork it and get it running

The entire fork and deploy flow is browser-based. The button renders correctly in production with no extra configuration beyond wiring your save logic.

1

Fork the template

Open https://v0.dev/chat/community/pAqpV1P3aMY in your browser and click 'Fork' to create your personal editable copy. V0 creates a new chat session with all the SaveButton source files. You own this copy and changes will not affect the original community template.

Tip: Sign in to v0.dev before clicking Fork to avoid a redirect to the login page.

You should see: The forked project opens with the Vercel Sandbox preview showing the SaveButton in its idle state.

2

Test all four states in the preview

In the Vercel Sandbox preview, click the SaveButton and watch it cycle through all four states: idle (floppy-disk icon, 'Save' label) → saving (spinner, 'Saving…') → saved (check icon, 'Saved!') → then back to idle after the timeout. This confirms the Framer Motion AnimatePresence transitions and the StateIcon swaps are all working.

You should see: All four states render correctly with smooth icon and label transitions, and the button returns to idle after the saved state.

3

Change button colour and label text with Design Mode

Press Option+D to open Design Mode. Click the button label text to rename it (e.g., 'Save Changes' instead of 'Save'). To change the saved-state colour without spending credits, note the current class and make a quick AI prompt change instead — colour changes involving Framer Motion animate values are faster via chat.

Tip: Design Mode edits are free and do not count against your V0 credits.

You should see: Updated label text and colour appear in the Sandbox preview immediately.

4

Wire the button to your actual save logic

In the AI chat, paste the 'Wire to a Next.js server action' prompt from the pack below. The AI replaces the simulated async delay in the SaveButton's onClick handler with a real server action call and maps the resolved or rejected promise to the saved or error states respectively.

Tip: You can also wire to a fetch() call or a TanStack Query useMutation — see the advanced prompts for those patterns.

You should see: The SaveButton now drives your actual save function and reflects the real success or failure state.

5

Publish to production

Click Share (top-right icon) → Publish tab → 'Publish to Production'. Vercel deploys in under 60 seconds. The SaveButton's Framer Motion animations work identically in production — no client-side-only caveats. Click Publish again after subsequent edits to push updates live.

You should see: A live *.vercel.app URL is generated with the fully functional animated save button.

6

Export to GitHub (optional)

Open the Git panel → Connect → select your GitHub repository → V0 creates a branch v0/main-{hash} and opens a pull request with all source files. Merge the PR to bring the SaveButton component into your main codebase for integration into a larger form or dashboard.

Tip: The Button component code is highly stable — this is one of the safest shadcn/ui imports to keep rather than inlining.

You should see: A GitHub PR appears with the SaveButton component files ready to merge and integrate.

The prompt pack

Copy-paste these straight into v0's chat to customize the Save Buttontemplate. Each one names this template's own components — no generic filler.

1

Change saved state colour to green with scale animation

Gives the saved state a clear green success colour with a micro-animation on the check icon for better user feedback.

Quick win
Paste into v0 chat
Update the SaveButton background and StateIcon colour in the 'saved' state from the current accent colour to Tailwind green-500 (bg-green-500, text-white). Also add a subtle scale-up spring animation (scale: [1, 1.15, 1]) to the check icon in StateIcon when it enters via AnimatePresence so there is a satisfying 'pop' moment on save success.
2

Auto-reset button to idle after 3 seconds

Returns the button to its idle state automatically so users do not need to manually reset it between saves.

Quick win
Paste into v0 chat
After the SaveButton enters the 'saved' state, automatically reset it back to 'idle' after 3 seconds using a useEffect that sets up a setTimeout and clears it with clearTimeout in the cleanup function. Ensure that if the user clicks save again before the 3-second timeout, the previous timeout is cleared so multiple rapid saves do not queue multiple resets.
3

Wire the SaveButton to a Next.js server action

Wires the button state machine to a real server action, making idle→saving→saved/error reflect the actual outcome of your save operation.

Medium
Paste into v0 chat
Replace the simulated async delay in the SaveButton's onClick handler with a call to a Next.js server action (a function in a separate file marked 'use server'). Map the action's resolved promise result to the 'saved' state and any thrown error to the 'error' state. Wrap the entire handler in try/catch/finally so the state always resolves even if the server action throws synchronously before the await.
4

Add optimistic UI with TanStack Query

Adds proper mutation lifecycle management to the SaveButton with automatic retry logic and consistent state tracking.

Medium
Paste into v0 chat
Wrap the SaveButton's save trigger in a useMutation hook from TanStack Query (@tanstack/react-query). Configure the mutation so the SaveButton immediately shows 'saving' state when clicked, transitions to 'saved' on mutation success, and 'error' on mutation failure with a 3-retry policy before giving up. Pass the mutation's isPending, isSuccess, and isError booleans to drive the SaveButton's state enum.
5

Persist last-saved timestamp to Supabase

Logs every save event to Supabase and shows users exactly when their work was last saved, reducing anxiety in document editors.

Advanced
Paste into v0 chat
After a successful save, call a Next.js server action that upserts a row in a Supabase table `document_saves (doc_id uuid, user_id uuid, saved_at timestamptz)` with the current timestamp. Below the SaveButton, display a 'Last saved X minutes ago' label that fetches the most recent `saved_at` for this document on mount and updates after each successful save. Add SUPABASE_URL and SUPABASE_ANON_KEY to the Vars panel.
6

Add auto-save on form field change with react-hook-form

Enables automatic saving as the user types, with the SaveButton visually confirming each auto-save cycle — a Google Docs-style experience.

Advanced
Paste into v0 chat
In the parent component that uses the SaveButton, add a react-hook-form watch() subscription that listens to all form fields. Debounce any change by 2 seconds of inactivity, then automatically trigger the save server action and drive the SaveButton through its full state cycle (idle → saving → saved/error) without any user click. Display a small 'Auto-saving…' text below the button only when the debounce timer is active.

Gotchas when you extend it

The failures people actually hit when they push this template past its defaults — and the exact fix for each.

Saving state never clears if the async function throws synchronously

Why: If the save handler throws before the await, the button stays locked in 'saving' with no way out for the user because the catch block never transitions state back to idle or error.

Fix: Always wrap the entire SaveButton onClick handler in try/catch/finally and set state to 'idle' or 'error' in the finally block regardless of whether the async operation succeeded, failed, or threw synchronously.

Fix prompt — paste into v0
Wrap the SaveButton onClick handler in a try/catch/finally block so the state always resets to 'idle' or 'error' even if the save function throws synchronously before reaching the await.
Button appears enabled during SSR then flashes to disabled on hydration

Why: The disabled prop on ButtonShell is tied to a client-side useState value. SSR renders the initial idle state (not disabled), but hydration may evaluate saving state differently, causing a visible flash.

Fix: Add suppressHydrationWarning to the ButtonShell element, or initialize the saving state from a server-rendered prop rather than client-only useState.

Fix prompt — paste into v0
Add suppressHydrationWarning to the ButtonShell component to prevent the disabled-state hydration mismatch flash on initial page load.
StateIcon AnimatePresence plays unwanted entry animation on initial page load

Why: AnimatePresence with mode='wait' treats the first render as if a previous icon just exited, so the initial icon (floppy-disk) slides or fades in even though the user has not interacted with the button yet.

Fix: Use a hasInteracted ref initialized to false. Only enable AnimatePresence animations once the ref is set to true on the first button click.

Fix prompt — paste into v0
Prevent the StateIcon AnimatePresence from playing its entry animation on the initial page render by tracking a hasInteracted ref and only enabling animations after the first click.
The component at https://ui.shadcn.com/r/styles/new-york-v4/button.json was not found.

Why: The shadcn Button registry entry path may differ between what V0 expects and the version installed locally, causing the import to fail on export or local development.

Fix: Run `npx shadcn add button` in your local project root. The Button component is the most stable shadcn component and is almost always available. Alternatively, inline a plain HTML button with Tailwind styling.

Fix prompt — paste into v0
Replace the shadcn/ui Button import in ButtonShell with a plain HTML button element styled with Tailwind classes (px-4 py-2 rounded-md font-medium transition-colors focus:outline-none focus:ring-2) to remove the registry dependency entirely.

Template vs. custom — the honest call

A forked template gets you far, fast. Here's where it holds up, and where you'll outgrow it.

The template is enough when

  • You need a drop-in save button for a form, settings page, or document editor where the four-state cycle matches your flow exactly
  • The save operation is a single async call and the button is self-contained with no sibling state dependencies
  • You want the Framer Motion animation polish without writing AnimatePresence transition logic from scratch
  • You are prototyping a form and want a realistic save UX before wiring the real backend

Go custom when

  • You need a save button that also validates the form before submitting and shows per-field error states inline
  • The saving operation must handle partial saves where some fields succeed and some fail with granular error feedback
  • You need undo functionality where clicking 'Saved' within 5 seconds reverts the change
  • Multiple save buttons on the same page need to share state or coordinate with each other

RapidDev regularly wires V0 save button patterns into full form systems with server actions, optimistic updates, and conflict resolution — reach out if your editing flow has grown beyond the template's scope.

Frequently asked questions

Is the Save Button V0 template free to use?

Yes. It is a free community template on v0.dev. Forking uses credits from your V0 plan. Once forked, the code is yours with no ongoing licensing fee.

Can I use this template in a commercial product?

Yes. The generated code carries no licensing restriction from v0.dev. Framer Motion is MIT-licensed and shadcn/ui is MIT-licensed. You can include this in a paid SaaS or client project without attribution.

Does this template handle form validation?

No. The SaveButton is a pure state machine — it manages idle, saving, saved, and error states for the button itself. Form validation is the parent form's responsibility. Use react-hook-form + zod in the parent component and trigger the SaveButton's save function only after validation passes.

Why does my fork break in the V0 preview after I add server action wiring?

The most common cause is the save handler throwing synchronously before the await, leaving the button stuck in 'saving' state. Always wrap the onClick handler in try/catch/finally with state cleanup in the finally block. See the Gotchas section for the exact fix prompt.

How do I connect the save button to a real backend?

Use the 'Wire the SaveButton to a Next.js server action' medium prompt from the pack. It replaces the simulated delay with a real server action call and maps success/failure to the correct states. For TanStack Query, use the optimistic UI prompt instead.

Why does the check icon animate on the initial page load before I click anything?

Framer Motion's AnimatePresence treats the first render as an entry transition. Add a hasInteracted ref initialized to false and only enable AnimatePresence animations after the first button click. The fix prompt in the Gotchas section handles this precisely.

Can RapidDev help integrate this button into a larger form system?

Yes. RapidDev wires V0 save button patterns into full form systems with server actions, optimistic updates, conflict resolution, and auto-save — reach out if your editing flow has outgrown what the template handles on its own.

Outgrowing the template?

RapidDev turns v0 prototypes into production apps — real auth, database, and payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Matt Graham

Written by

Matt Graham · CEO & Founder, RapidDev

1,000+ client projects delivered. Columbia University & Harvard Business School alumnus, U.S. Navy veteran. About the author →

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.