Gift Card
ProA premium gift-card / e-gift visual: a locale-formatted denomination or remaining balance, a redeemable code that is masked, gated behind the host's own authorization and re-hidden on a timer, plus redeemed / void / expired states, a zone-aware expiry, recipient, sender and an optional greeting. Five gradient themes with a decorative ribbon and shimmer.
Complexity tier: Interactive The primitive bar, plus declared async states, a keyboard model, focus restoration and undo for destructive actions.
import { SvGiftCardComponent } from '@styloviz-pro/commerce';Demo & usage
Basic usage
ProUsage example — open the gallery for the full interactive demo.
An interactive demo for this component lives in the gallery.
Open live demoMore examples
Reveal the code (two-way)
ProRecipient and message
ProAnswer the reveal gate with your own step-up
ProAudit a copy without logging the code
ProRemaining balance and a zone-aware expiry
ProA spent or cancelled card
ProWhen to use
- A live preview beside a gift-card purchase or gifting form.
- An e-gift confirmation email or order receipt.
- The account wallet, showing a redeemable balance and code.
- A support console, where the last four identifies the card and nobody needs the code.
Async states
None — this component has nothing to load, so it has no loading, empty or error state to drive.
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| brand | string | 'Styloviz' | Brand / store name shown top-left. |
| amount | number | 0 | Gift-card value. |
| currency | string | 'USD' | ISO 4217 currency code. |
| locale | string | 'en-US' | BCP-47 locale for number formatting. |
| code | string | '' | Redeemable code. Grouped into blocks of 4 when it has no separators of its own; a punctuated code like `FRESH-GIFT-2K` is printed as written. |
| recipient | string | '' | Recipient name (To). |
| sender | string | '' | Sender name (From). |
| message | string | '' | Optional greeting / message. |
| expiry | string | '' | Validity line as free text, e.g. "Valid through 12/28". Display only: it carries no expiry logic, so a card whose text has gone stale still reads as active. Pass `expiresAt` to have the card actually expire, and this is used only as the fallback when `expiresAt` is null. |
| expiresAt | Date | string | null | null | When the card stops being redeemable. A `Date`, or an ISO string. A string with `Z` or an offset names an instant and is taken as-is; a bare `2028-12-31` or an offsetless `2028-12-31T18:00` is read as wall-clock time in `timeZone`, and a bare date means the END of that day there. See `wallClockToInstant`. |
| timeZone | string | null | null | The zone `expiresAt` is written in — the merchant's, normally, since that is whose midnight the terms mean. IANA name, e.g. `'Europe/Berlin'`. Falls back to UTC, never to the reader's own zone: a card must not expire at a different moment for each customer. |
| now | Date | number | null | null | The clock expiry is measured against — for tests, and for a host that wants the card to flip while it is on screen by feeding its own tick. Left null it reads `Date.now()` when the inputs change, which is right for a card the reader opens and closes; it does NOT tick on its own, because a permanent interval in a display component costs more than it buys. |
| status | GiftCardStatus | 'active' | What the host knows about the card. `expired` is not accepted here — it is derived from `expiresAt` — and a declared status wins over both expiry and a zero balance, because "redeemed" says more than "expired" about a card that is both. |
| balance | number | null | null | Amount still on the card, when it differs from the face value. Null means the card is worth its face value — not that the balance is unknown, which is indistinguishable to a reader and so is not offered. |
| requireAuthorization | boolean | true | Whether pressing Reveal needs the host's say-so. On by default: the caller who has already re-authenticated the user says so by setting `revealAuthorized`, and the caller who has not gets an event to act on instead of a code on screen. |
| revealAuthorized | boolean | false | The host has verified the person in front of the screen recently enough to show them the code. Set it after a password prompt, a step-up, a fresh login. |
| revealTimeout | number | 20000 | How long a revealed code stays on screen, in ms. 0 disables the auto-hide. A code revealed on a screen someone else can see is a code spent by whoever is behind you, and the reader who reveals it has no reason to remember to hide it again. 20 seconds is long enough to read twelve characters aloud. |
| allowCopy | boolean | true | Offer a copy button once the code is actually visible. |
| variant | GiftCardVariant | 'aurora' | Gradient theme. |
| showCode | boolean | true | Show the redeem-code row. |
| maskCode | boolean | true | Mask all but the last four of the code until revealed. |
| privacy | boolean | false | Declare that this session is recorded, screen-shared or replayed. While set, the code stays masked to its last four whatever `maskCode` or `revealed` say, the reveal and copy controls go away, and the card says why. An app-wide `provideSvSensitivePrivacy()` does the same while this is left unset. A declaration, not a detection — a page cannot observe a capture it did not start itself. |
| withheldLabel | string | 'Hidden while this screen may be shared' | Shown in place of the reveal control while `privacy` withholds the code. |
| showAmount | boolean | true | Show the denomination. |
| label | string | 'Gift Card' | Small caption above the amount. |
| redeemedLabel | string | 'Redeemed' | State chip for a spent card. |
| expiredLabel | string | 'Expired' | State chip for a card past its expiry. |
| voidLabel | string | 'Void' | State chip for a cancelled card. |
| remainingLabel | string | 'remaining of' | Caption on the remaining balance. |
| revealLabel | string | 'Reveal' | Reveal button, when the code is hidden. |
| hideLabel | string | 'Hide' | Reveal button, when the code is on screen. |
| verifyLabel | string | 'Verify' | Reveal button, when the host has still to authorize. |
| copyLabel | string | 'Copy code' | Copy button. |
| copiedLabel | string | 'Copied' | Confirmation after a copy. |
| copyFailedLabel | string | 'Copy the code manually' | Shown when the clipboard is unavailable or refused. |
| revealed | boolean (two-way) | false | Whether the masked code is revealed (two-way: `[(revealed)]`). A host writing this is authorization by definition — it is the host's own code making the decision — so the gate applies only to the user-initiated path. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| revealRequest | void | The user asked to see the code while `requireAuthorization` was on and `revealAuthorized` was off. Step up, then set `revealAuthorized` — or set `revealed` directly once the challenge passes. |
| codeCopied | GiftCardCopyEvent | The code was copied. Log it: see `GiftCardCopyEvent` for what it carries. |
| revealedChange | boolean | Emits the new `revealed` whenever it changes — the output half of `[(revealed)]`. |
Customization & theming
- Five gradient themes (aurora, gold, rose, midnight, mint).
- Toggle the amount, redeem-code row, code masking and the copy button.
- Reveal gate: requireAuthorization, revealAuthorized, revealRequest, and revealTimeout for the auto-hide (0 disables it).
- balance renders what is left against the face value, with a bar; 0 reads as redeemed.
- status of active / redeemed / void, plus expired derived from expiresAt and timeZone.
- now injects the clock, for tests or for a card that should flip while it is open.
- Every label is an input: redeemedLabel, expiredLabel, voidLabel, remainingLabel, revealLabel, hideLabel, verifyLabel, copyLabel, copiedLabel, copyFailedLabel.
- Recipient, sender, greeting and a validity line.
- Custom brand name and amount caption.
Best practices
- Do: Bind the same signals to the card and to your gifting form for a live preview.
- Do: Answer revealRequest with a real step-up — a password prompt, a passkey, an OTP — then set revealAuthorized.
- Do: Log codeCopied as it arrives: it carries the last four and the time, which is what an auditor needs.
- Do: Pass expiresAt with the merchant timeZone rather than a free-text expiry, so the card actually expires — and expires at one moment for every customer.
- Do: Pass the status your API returned; the card derives expired and treats a zero balance as redeemed on its own.
- Do: Set a currency and locale so the amount formats correctly for the buyer.
- Don't: Do not switch requireAuthorization off just to save a click. Leave it on unless the screen is already behind a fresh re-authentication.
- Don't: Do not put the code in your own audit payload alongside the event — that is exactly the leak the event was shaped to avoid.
- Don't: Do not rely on the free-text expiry for anything but display; it carries no logic, so a stale line still reads as an active card.
- Don't: Do not treat this as an input — it is a display; collect the amount and message with your own form controls.
Accessibility
- The reveal control is a real button whose label says what pressing it will do — verify, reveal, or hide — and it activates on Enter and Space.
- The copy button is a real button with its own label; the outcome is announced once through a status region, not on every tick.
- The auto-hide countdown is hidden from assistive tech, since the button label already carries the fact and a per-second live figure would repeat itself.
- The state chip is text, not a colour: Redeemed, Void and Expired are read out, and the chip declares its own background and ink so the pair is contrast-checkable on all five gradients.
- A spent card is desaturated with a luminance-preserving grayscale filter rather than dimmed with opacity, so the measured ink still clears AA.
- The ribbon, gift mark, shimmer and balance bar are decorative and hidden from assistive tech.
- Each theme carries the one ink measured against all three of its own gradient stops, so contrast holds in light and dark mode.
Feedback on Gift Card
Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.