# Vault Item Card

Vault Item Card [#vault-item-card]

The **Vault Item Card** renders a single encrypted vault item (a note or a text
document). When **locked**, it shows a masked placeholder body with the
reveal/delete actions disabled — no decrypted content is present in the DOM. When
**unlocked**, it shows the title/timestamp with a reveal/hide toggle over the
caller-supplied decrypted `content` and a delete action, all via callbacks.

> **Backend-agnostic and key-safe.** The card never receives or renders ciphertext
> or key material — the consuming app decrypts and passes plaintext in `content`
> only when `revealed` is true. Recommended LocalMode source: the `items`,
> `readItem`, and `deleteItem` of `useEncryptedVault` from `@localmode/react` — a
> recommendation, never a requirement.

**When to use it:** any encrypted-store UI listing items whose plaintext is
revealed on demand — password managers, secure notes, encrypted document vaults.

Preview [#preview]

```tsx
'use client';

import { useState } from 'react';

import { VaultItemCard } from '@/components/vault-item-card';

/**
 * Demo for VaultItemCard, used by the docs live preview. Fixture-driven — a
 * lock toggle flips both cards between their locked (masked) and unlocked
 * (revealable) states. No crypto, no storage: the "decrypted" content is a
 * static fixture string revealed via callback.
 */
export default function VaultItemCardDemo() {
  const [locked, setLocked] = useState(false);
  const [revealedId, setRevealedId] = useState<string | null>('note');

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <button
        type="button"
        onClick={() => setLocked((v) => !v)}
        className="self-start rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium transition-colors hover:bg-accent"
      >
        {locked ? 'Unlock vault' : 'Lock vault'}
      </button>

      <VaultItemCard
        title="API keys"
        kind="note"
        createdAt="2 days ago"
        locked={locked}
        revealed={revealedId === 'note'}
        content={revealedId === 'note' ? 'sk-live-4f9a...redacted-in-demo' : undefined}
        onReveal={() => setRevealedId('note')}
        onHide={() => setRevealedId(null)}
        onDelete={() => {}}
      />

      <VaultItemCard
        title="recovery-codes.txt"
        kind="document"
        createdAt="just now"
        locked={locked}
        revealed={revealedId === 'doc'}
        content={revealedId === 'doc' ? '1) 8fd2-01\n2) 77ac-93\n3) 12de-55' : undefined}
        onReveal={() => setRevealedId('doc')}
        onHide={() => setRevealedId(null)}
        onDelete={() => {}}
      />
    </div>
  );
}
```

Installation [#installation]

```bash
npx shadcn@latest add @localmode/ui/security-privacy/vault-item-card
```

Dependencies [#dependencies]

* **Data source:** renders the lock state, title/timestamp, and (only when revealed) the decrypted `content` you pass; emits `onReveal`/`onHide`/`onDelete`. Works with any backend. Recommended LocalMode hook: `useEncryptedVault` (optional).
* `lucide-react` — icons
* `clsx` + `tailwind-merge` — via the shared `cn()` util (installed automatically as a registry dependency)

Files installed [#files-installed]

* `vault-item-card.tsx` — the component
* `lib/utils.ts` — the `cn()` helper (if not already present)

Props [#props]

**VaultItemCard**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `string` | — | **Required.** Item title (a non-sensitive envelope field — safe to render while locked). |
| `createdAt` | `string` | — | Creation timestamp (preformatted display string, e.g. "2 days ago"). |
| `locked` | `boolean` | — | **Required.** Whether the vault is locked. When `true`, the body is masked, no content is rendered, and the reveal/delete actions are disabled. |
| `revealed` | `boolean` | `false` | Whether the decrypted content is currently revealed. Only meaningful while unlocked. The consuming app toggles this via `onReveal`/`onHide`. |
| `content` | `string` | — | The decrypted plaintext content — supplied by the caller ONLY when revealed. The card never receives ciphertext or key material; the app decrypts and passes plaintext in when the user reveals. |
| `kind` | `"document" \| "note"` | `'note'` | Item kind — drives the icon and the masked-body wording. |
| `onReveal` | `function` | — | Fired when the user requests to reveal (decrypt-on-view) the content. |
| `onHide` | `function` | — | Fired when the user hides the revealed content. |
| `onDelete` | `function` | — | Fired when the user deletes the item. |

Examples [#examples]

Decrypt-on-view from an encrypted vault [#decrypt-on-view-from-an-encrypted-vault]

Pass plaintext only while the item is revealed — the vault decrypts on demand:

```tsx
import { useEncryptedVault } from '@localmode/react';
import { VaultItemCard } from '@/components/vault-item-card';

export function VaultList() {
  const { status, items, deleteItem } = useEncryptedVault<{ title: string; body: string }>({ name: 'notes' });
  const [openId, setOpenId] = useState<string | null>(null);
  return items.map((item) => (
    <VaultItemCard
      key={item.id}
      title={item.data.title}
      locked={status !== 'unlocked'}
      revealed={openId === item.id}
      content={openId === item.id ? item.data.body : undefined}
      onReveal={() => setOpenId(item.id)}
      onHide={() => setOpenId(null)}
      onDelete={() => deleteItem(item.id)}
    />
  ));
}
```

Document variant [#document-variant]

```tsx
<VaultItemCard title="recovery-codes.txt" kind="document" locked={false} content={text} revealed />
```

Customization [#customization]

The masked body, the reveal/hide toggle, and the delete affordance are all styled
with shadcn/ui CSS-variable utilities so they inherit your theme. Because you own
the copied file, you can change the mask rendering, add a copy-to-clipboard
action, or render a richer preview for the document `kind`.