cards
Credit Wallet Card
A CSS-only wallet widget showing three stacked payment cards (primary/accent/background surfaces) held in a leather-look SVG pocket. Hover the wallet to lift it and fan the cards out with spring-eased transforms; hover an individual card to pop it to the front and reveal the full card number. The hidden balance reveals with a fade+slide transition and the eye icon toggles open. Zero JS — all interaction is pure CSS :hover. Entry animation slides cards in from above on mount. Reduced-motion: entry animation and fan transforms are disabled; balance/number reveal on hover still works instantly.
Ported from byllzz (MIT)
ARCO
HolderALEX SMITH
**** 42425524 9910 4242
ATELIER
BusinessSTUDIO LLC
**** 88109012 4432 8810
CARTE
Emailhello@work.com
**** 00943312 0045 0094
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/credit-wallet-cardUsage
Usagetsx
<CreditWalletCard />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| holderName | string | ALEX SMITH | Name shown on the primary (top) card. |
| businessName | string | STUDIO LLC | Label shown on the accent (middle) card. |
| string | hello@work.com | Email shown on the light (front) card. | |
| balance | string | $12,450.00 | Balance revealed when the wallet is hovered. |
| className | string | — | Extra class name merged onto the root element. |
Source
credit-wallet-card.tsxtsx
import type React from "react";
import styles from "./credit-wallet-card.module.css";
export type CreditWalletCardProps = {
/** Name shown on the primary (top) card. */
holderName?: string;
/** Label shown on the accent (middle) card. */
businessName?: string;
/** Email shown on the light (front) card. */
email?: string;
/** Balance revealed when the wallet is hovered. */
balance?: string;
/** Extra class name merged onto the root element. */
className?: string;
};
function EyeSlashIcon() {
return (
<svg
className={`${styles.eyeIcon} ${styles.eyeSlash}`}
aria-hidden="true"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<circle cx="12" cy="12" r="3" />
<line x1="3" y1="3" x2="21" y2="21" />
</svg>
);
}
function EyeOpenIcon() {
return (
<svg
className={`${styles.eyeIcon} ${styles.eyeOpen}`}
aria-hidden="true"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<circle cx="12" cy="12" r="3" />
</svg>
);
}
/**
* CreditWalletCard — A CSS-only wallet widget with three stacked payment cards
* in a leather-style SVG pocket. Hover the wallet to fan out the cards and
* reveal the hidden balance; hover an individual card to pop it up and show the
* full card number. No JS or state — pure CSS `:hover` transitions throughout.
* Fully server-renderable.
*/
export function CreditWalletCard({
holderName = "ALEX SMITH",
businessName = "STUDIO LLC",
email = "hello@work.com",
balance = "$12,450.00",
className,
}: CreditWalletCardProps) {
return (
<div
className={[styles.wallet, className].filter(Boolean).join(" ")}
role="img"
aria-label="Credit wallet"
>
{/* Leather back of the wallet */}
<div className={styles.walletBack} aria-hidden="true" />
{/* Card 1 — primary surface */}
<div className={`${styles.card} ${styles.cardPrimary}`}>
<div className={styles.cardInner}>
<div className={styles.cardTop}>
<span>ARCO</span>
<div className={styles.chip} aria-hidden="true" />
</div>
<div className={styles.cardBottom}>
<div className={styles.cardInfo}>
<span className={styles.label}>Holder</span>
<span className={styles.value}>{holderName}</span>
</div>
<div className={styles.cardNumberWrapper}>
<span className={styles.hiddenStars}>**** 4242</span>
<span className={styles.cardNumber}>5524 9910 4242</span>
</div>
</div>
</div>
</div>
{/* Card 2 — accent surface */}
<div className={`${styles.card} ${styles.cardAccent}`}>
<div className={styles.cardInner}>
<div className={styles.cardTop}>
<span>ATELIER</span>
<div className={styles.chip} aria-hidden="true" />
</div>
<div className={styles.cardBottom}>
<div className={styles.cardInfo}>
<span className={styles.label}>Business</span>
<span className={styles.value}>{businessName}</span>
</div>
<div className={styles.cardNumberWrapper}>
<span className={styles.hiddenStars}>**** 8810</span>
<span className={styles.cardNumber}>9012 4432 8810</span>
</div>
</div>
</div>
</div>
{/* Card 3 — light (background) surface */}
<div className={`${styles.card} ${styles.cardLight}`}>
<div className={styles.cardInner}>
<div className={styles.cardTop}>
<span>CARTE</span>
<div className={styles.chip} aria-hidden="true" />
</div>
<div className={styles.cardBottom}>
<div className={styles.cardInfo}>
<span className={styles.label}>Email</span>
<span className={styles.value}>{email}</span>
</div>
<div className={styles.cardNumberWrapper}>
<span className={styles.hiddenStars}>**** 0094</span>
<span className={styles.cardNumber}>3312 0045 0094</span>
</div>
</div>
</div>
</div>
{/* SVG-shaped pocket that holds the cards */}
<div className={styles.pocket} aria-hidden="true">
<svg
className={styles.pocketSvg}
viewBox="0 0 280 160"
fill="none"
aria-hidden="true"
>
{/* Pocket body fill */}
<path
className={styles.pocketPath}
d="M 0 20 C 0 10, 5 10, 10 10 C 20 10, 25 25, 40 25 L 240 25 C 255 25, 260 10, 270 10 C 275 10, 280 10, 280 20 L 280 120 C 280 155, 260 160, 240 160 L 40 160 C 20 160, 0 155, 0 120 Z"
/>
{/* Pocket decorative dashed border */}
<path
className={styles.pocketBorder}
d="M 8 22 C 8 16, 12 16, 15 16 C 23 16, 27 29, 40 29 L 240 29 C 253 29, 257 16, 265 16 C 268 16, 272 16, 272 22 L 272 120 C 272 150, 255 152, 240 152 L 40 152 C 25 152, 8 152, 8 120 Z"
/>
</svg>
<div className={styles.pocketContent}>
{/* Balance row: stars hidden until hover reveals the real amount */}
<div className={styles.balanceWrapper}>
<div className={styles.balanceStars}>******</div>
<div className={styles.balanceReal}>{balance}</div>
</div>
<div className={styles.balanceTotalLabel}>Total Balance</div>
<div className={styles.eyeIconWrapper}>
<EyeSlashIcon />
<EyeOpenIcon />
</div>
</div>
</div>
</div>
);
}
Dependencies
- @bottega/tokens