# iOS Wallet

> Stacked cards that lift on tap with transaction history.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/templates/ios-wallet
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/ios-wallet.json
```

Adds the page at `app/ios-wallet/page.tsx` together with the components below. Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Components

- [iOS Kit](https://chromotion.dev/r/ios-kit.md): `@/components/ios-kit`

## Dependencies

```bash
npm install lucide-react
```

## Source

```tsx title="app/ios-wallet/page.tsx"
"use client"

import * as React from "react"
import { Plane, Plus, Train, Wifi } from "lucide-react"

import { cn } from "@/lib/utils"
import { IOSDevice } from "@/components/ios-kit"

const cards = [
  { id: "visa", brand: "Atelier Card", kind: "Credit", last: "4821", gradient: "from-zinc-200 via-zinc-300 to-zinc-500", text: "text-zinc-900" },
  { id: "bank", brand: "Northwind", kind: "Debit", last: "0937", gradient: "from-indigo-500 via-violet-600 to-fuchsia-600", text: "text-white" },
  { id: "transit", brand: "Metro Pass", kind: "Transit", last: "1205", gradient: "from-emerald-400 to-teal-700", text: "text-white" },
  { id: "flight", brand: "SkyLine Air", kind: "Boarding pass", last: "SFO → JFK", gradient: "from-sky-400 to-blue-700", text: "text-white" },
]

const transactions: Record<string, { name: string; place: string; amount: string }[]> = {
  visa: [
    { name: "Blue Bottle Coffee", place: "San Francisco", amount: "$5.50" },
    { name: "Apple Store", place: "Union Square", amount: "$129.00" },
    { name: "Whole Foods", place: "Potrero", amount: "$64.23" },
  ],
  bank: [
    { name: "Rent", place: "Transfer", amount: "$2,400.00" },
    { name: "Spotify", place: "Subscription", amount: "$11.99" },
  ],
  transit: [
    { name: "BART", place: "Embarcadero", amount: "$4.20" },
    { name: "Muni", place: "Market St", amount: "$2.50" },
  ],
  flight: [{ name: "Gate B22", place: "Boards 10:15 AM · Seat 14A", amount: "" }],
}

const CARD_H = 200
const PEEK = 56

export default function IOSWallet() {
  const [active, setActive] = React.useState<string | null>("visa")

  return (
    <IOSDevice light className="bg-black">
      <div className="relative flex min-h-0 flex-1 flex-col text-white">
        <header className="flex h-[98px] shrink-0 items-end justify-between px-5 pb-1">
          <h1 className="text-[34px] leading-[41px] font-bold">Wallet</h1>
          <button type="button" aria-label="Add to Wallet" className="mb-1 flex size-8 items-center justify-center rounded-full bg-white/15 active:scale-90">
            <Plus className="size-5" strokeWidth={2.4} />
          </button>
        </header>

        <div
          className="relative mx-4 shrink-0 overflow-hidden transition-[height] duration-500 ease-[cubic-bezier(0.32,0.72,0,1)]"
          style={{ height: active ? CARD_H + 8 + (cards.length - 1) * 22 :CARD_H + PEEK * (cards.length - 1) }}
        >
          {cards.map((card, index) => {
            // The selected card rises to the top; the others collapse into strips under it.
            const lifted = card.id === active
            const rank = cards.filter((c) => c.id !== active).findIndex((c) => c.id === card.id)
            const top = !active ? index * PEEK : lifted ? 0 : CARD_H + 8 + rank * 22
            return (
              <button
                key={card.id}
                type="button"
                aria-pressed={lifted}
                onClick={() => setActive(lifted ? null : card.id)}
                style={{ top, zIndex: lifted ? cards.length + 1 : index + 1, height: CARD_H }}
                className={cn(
                  "absolute inset-x-0 flex flex-col justify-between rounded-[18px] bg-gradient-to-br p-4 text-left shadow-[0_-4px_14px_rgb(0_0_0/0.45)] transition-[top] duration-500 ease-[cubic-bezier(0.32,0.72,0,1)]",
                  card.gradient,
                  card.text
                )}
              >
                <span className="flex items-start justify-between">
                  <span className="text-[19px] font-semibold tracking-tight">{card.brand}</span>{" "}
                  {card.id === "transit" ? <Train className="size-6" /> : card.id === "flight" ? <Plane className="size-6" /> : <Wifi className="size-5 rotate-90" />}
                </span>
                <span className="flex items-end justify-between">
                  <span>
                    <span className="block text-[12px] opacity-70">{card.kind}</span>{" "}
                    <span className="block text-[17px] font-medium tracking-[0.15em] tabular-nums">
                      {card.id === "flight" ? card.last : `•••• ${card.last}`}
                    </span>
                  </span>
                  {card.id !== "flight" && <span className="h-6 w-9 rounded-[5px] bg-gradient-to-br from-amber-200 to-amber-500 opacity-90" />}
                </span>
              </button>
            )
          })}
        </div>

        <section aria-live="polite" className="mx-4 mt-4 min-h-0 flex-1 overflow-y-auto pb-[40px] [scrollbar-width:none]">
          {active ? (
            <>
              <h2 className="px-1 pb-1.5 text-[13px] text-white/55 uppercase">Latest Transactions</h2>
              <ul className="overflow-hidden rounded-[16px] bg-[#1c1c1e]">
                {transactions[active].map((tx) => (
                  <li key={tx.name} className="relative flex items-center justify-between px-4 py-3 after:absolute after:right-0 after:bottom-0 after:left-4 after:h-px after:bg-white/15 last:after:hidden">
                    <span className="min-w-0">
                      <span className="block truncate text-[17px]">{tx.name}</span>
                      <span className="block truncate text-[14px] text-white/55">{tx.place}</span>
                    </span>
                    <span className="text-[17px] tabular-nums">{tx.amount}</span>
                  </li>
                ))}
              </ul>
            </>
          ) : (
            <p className="pt-6 text-center text-[15px] text-white/50">Tap a card to see its details</p>
          )}
        </section>
      </div>
    </IOSDevice>
  )
}
```
