primitives
Save Bounce Button
A card-like button presenting a layered file icon. On press, the card plays a brief scale-down animation and the top icon layer bounces off with a rotation and upward translation, fading to transparent — evoking a satisfying 'save' gesture. Built from pure CSS :active keyframes with no JS animation library. Accessible: native button semantics, visible label as the accessible name, focus-visible ring from --ring, icon layers are aria-hidden.
Ported from Codecite (MIT)
Install
1. Register the namespace (once per project):
json
// components.json — register the @bottega namespace once
{
"registries": {
"@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
}
}2. Add the component:
bash
npx shadcn add @bottega/save-bounce-buttonUsage
Usagetsx
<SaveBounceButton />Props
Standard props — see source.
Source
save-bounce-button.tsxtsx
"use client";
import type React from "react";
import styles from "./save-bounce-button.module.css";
export type SaveBounceButtonProps = React.ComponentProps<"button"> & {
/** Text label shown below the icon. Provides the button's accessible name. */
label?: string;
};
/** Reusable layered file icon — rendered twice (back + bouncing front). */
function FileIcon({ className }: { className?: string }) {
return (
<svg
width="50"
viewBox="0 0 19 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
className={className}
>
{/* Back shadow layer — lightest green (#C4FFE4) */}
<path
className={styles.pathLight}
d="M3.4,4 L11.5,4 L11.5,4 L16,8.25 L16,17.6 C16,19.4777681 14.4777681,21 12.6,21 L3.4,21 C1.52223185,21 6.74049485e-16,19.4777681 0,17.6 L0,7.4 C2.14128934e-16,5.52223185 1.52223185,4 3.4,4 Z"
/>
{/* Main file body — medium green (#85EBBC) */}
<path
className={styles.pathMid}
d="M6.4,0 L12,0 L12,0 L19,6.5 L19,14.6 C19,16.4777681 17.4777681,18 15.6,18 L6.4,18 C4.52223185,18 3,16.4777681 3,14.6 L3,3.4 C3,1.52223185 4.52223185,7.89029623e-16 6.4,0 Z"
/>
{/* Corner fold — darkest green (#64B18D) */}
<path
className={styles.pathDark}
d="M12,0 L12,5.5 C12,6.05228475 12.4477153,6.5 13,6.5 L19,6.5 L19,6.5 L12,0 Z"
/>
</svg>
);
}
/**
* A card-like button with a layered file icon. On press the card scales down
* and the top icon layer bounces off — pure CSS :active keyframes, no JS
* animation library.
*/
export function SaveBounceButton({
label = "Save",
className,
...props
}: SaveBounceButtonProps) {
return (
<button
type="button"
className={[styles.container, className].filter(Boolean).join(" ")}
{...props}
>
{/* Icon group: back icon sets the size; front icon overlays and bounces */}
<span className={styles.iconGroup} aria-hidden="true">
<FileIcon className={styles.iconBack} />
<FileIcon className={styles.iconFront} />
</span>
{/* Visible label — also the button's accessible name */}
<span className={styles.label}>{label}</span>
</button>
);
}
Dependencies
- @bottega/tokens