Detent web components
By Shane Logsdon

Commerce & auth

Wallet Balance

Implemented

Interactive payment cards tucked into a leather pouch with eye-toggle balance obscuring

detent-wallet-balance

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-data
Group
Commerce & auth

6.5 KB gzip for the class module

Variants

Sample data; nothing is fetched or sent. Press the eye to mask every balance: the amounts are replaced by ******, so the real values are not in the DOM while hidden, and a polite live region announces Total balance … or Balances hidden. This pouch is filled through the property-only accounts array in euros. Every toggle emits one composed detent-visible-change with { visible }; the demo only logs it.

Recorded states

  • pressed recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

import '/components/wallet-balance/define.js';

// or register it yourself
import { DetentWalletBalance } from '/components/wallet-balance/wallet-balance.js';

Attributes

  • currency
  • default-visible
  • locale
  • total-label
  • visible

Properties

  • accounts
  • onVisibleChange

Slots

No slots are recorded for this component.

Events

  • detent-visible-change — {}

Example

Runnable example: site/examples/wallet-balance.ts. The snippet below is generated from the catalogued attributes.

<detent-wallet-balance
  currency="USD"
  default-visible
  locale="en-US"
  total-label="Total Balance"
  visible
></detent-wallet-balance>

Property-only values

const element = document.querySelector('detent-wallet-balance');
if (element !== null) {
  // accounts: WalletAccount[]
  element.accounts = [];
  // onVisibleChange: (visible: boolean) => void
  element.onVisibleChange = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/WalletBalance.tsx sha256 f5b368575784…

Native files

The native module and its artifacts:

  • src/components/wallet-balance/wallet-balance.ts
  • src/components/wallet-balance/styles.css
  • src/components/wallet-balance/define.ts
  • docs/components/wallet-balance.md
  • site/examples/wallet-balance.ts
  • tests/browser/wallet-balance.spec.ts
  • evidence/components/wallet-balance.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
currencycurrencystring'USD'Kebab-case attribute; camelCase property.
default-visibledefaultVisiblebooleantruePresence semantics: present means true, absent means false.
localelocalestring'en-US'Kebab-case attribute; camelCase property.
total-labeltotalLabelstring'Total Balance'Kebab-case attribute; camelCase property.
visiblevisiblebooleanundefinedPresence semantics: present means true, absent means false.

Properties

Properties
PropertyTypeDefaultNotes
accountsWalletAccount[]DEFAULT_ACCOUNTSProperty-only: assign from script; not accepted as an attribute.
onVisibleChange(visible: boolean) => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-visible-changeonVisibleChange(visible: boolean) => voidComposed, bubbles; fires only for a user action.

Slots

No slots are recorded for this component.

CSS customisation

  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • className is replaced by documented CSS parts and custom properties.
  • WalletAccount.logo is a string; the upstream brand marks are typographic stand-ins.
  • background and amountColor are validated with CSS.supports(color, ...) and ignored when invalid.
  • Default amountColors are darkened (#3a2f66 / #2a4f1b / #4a4a4a) so each reaches 4.5:1 on its default brand surface.
  • mask/maskClassName/show/value/open are internal EyeIcon/Swap params, not host API: observedAttributes is exactly visible, default-visible, currency, locale and total-label.
  • The mask replaces one text node where upstream crossfaded two AnimatePresence children; that is what makes the no-amount-in-the-DOM-while-hidden guarantee provable.
  • Framer springs become a cubic-bezier(0.34,1.3,0.64,1) transform transition with the upstream 0.03 s stagger (PC-10.4 substitution; the motion gate stays open).
  • The totals block sits 9px lower than upstream so its opaque pouch-coloured paint, which makes the contrast measurable, clears the dashed stitch.

What it does

Three brand cards tucked into a leather pouch, an eye toggle that obscures or reveals every balance with a blur crossfade, staggered fan/tuck transitions and a polite live region.

Upstream behaviour
  • Interactive eye-toggle button to reveal or mask balances with blur-crossfade and popLayout transitions
  • Staggered spring animations for fanning out and tucking cards into the pouch
  • Responsive stage scaling with ResizeObserver for crisp rendering on any viewport width
  • Customizable accounts array with brand marks, individual balances, and background themes
  • Full accessibility support with aria-pressed, live screen reader announcements, and prefers-reduced-motion
Differences from upstream
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • className is replaced by documented CSS parts and custom properties.
  • WalletAccount.logo is a string; the upstream brand marks are typographic stand-ins.
  • background and amountColor are validated with CSS.supports(color, ...) and ignored when invalid.
  • Default amountColors are darkened (#3a2f66 / #2a4f1b / #4a4a4a) so each reaches 4.5:1 on its default brand surface.
  • mask/maskClassName/show/value/open are internal EyeIcon/Swap params, not host API: observedAttributes is exactly visible, default-visible, currency, locale and total-label.
  • The mask replaces one text node where upstream crossfaded two AnimatePresence children; that is what makes the no-amount-in-the-DOM-while-hidden guarantee provable.
  • Framer springs become a cubic-bezier(0.34,1.3,0.64,1) transform transition with the upstream 0.03 s stagger (PC-10.4 substitution; the motion gate stays open).
  • The totals block sits 9px lower than upstream so its opaque pouch-coloured paint, which makes the contrast measurable, clears the dashed stitch.
Previous: Notice Toast Next: Wallet Card

Docs

Components

Project