Componentfeedback0.7 KB gzipZero dependencies

Save Indicator

Auto-save status chip: idle, saving, saved, error states.

Preview

Live Preview
✓ Saved

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add save-indicator

Or install the full npm package:

npm install bigbullui
import { SaveIndicator } from "@/components/ui/save-indicator";

<SaveIndicator />

Usage guidance

Use Save Indicator when a ticket stub surface needs Auto-save status chip: idle, saving, saved, error states. Keep props minimal and prefer composition with sibling stubs.

Avoid Save Indicator for long form flows or dense data grids where a dedicated layout block fits better.

Source code

save-indicator.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface SaveIndicatorProps extends React.HTMLAttributes<HTMLSpanElement> {
  /** idle | saving | saved | error */
  status?: "idle" | "saving" | "saved" | "error";
  label?: string;
}

/** Autosave state indicator: smooth state transitions with checkmark. */
export function SaveIndicator({ status = "idle", label, className, ...props }: SaveIndicatorProps) {
  const meta: Record<string, { text: string; tone: string; icon: React.ReactNode }> = {
    idle: { text: "Saved", tone: "text-muted-foreground", icon: <span className="size-1.5 rounded-full bg-border" /> },
    saving: { text: "Saving…", tone: "text-muted-foreground", icon: <span className="size-2.5 rounded-full border-2 border-border border-t-accent animate-spin motion-reduce:animate-none" /> },
    saved: { text: "✓ Saved", tone: "text-success", icon: <span className="size-1.5 rounded-full bg-success animate-pulse motion-reduce:animate-none" /> },
    error: { text: "Save failed", tone: "text-destructive", icon: <span className="size-1.5 rounded-full bg-destructive animate-pulse motion-reduce:animate-none" /> },
  };
  const m = meta[status];

  return (
    <span
      className={cn(
        "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-2 py-0.5 font-mono text-[10px] uppercase tracking-wider",
        "transition-all duration-300 motion-reduce:transition-none",
        m.tone,
        className
      )}
      role="status"
      aria-live="polite"
      {...props}
    >
      {m.icon}
      {label ?? m.text}
    </span>
  );
}

Props

PropTypeDescription
status"idle" | "saving" | "saved" | "error"idle | saving | saved | error
labelstring