OTP Share Guard

Says in one sentence what a one-time code really approves and warns hard during a call or screen share, so it's less likely to be read out to a scammer.

StopsHelps stop an OTP being read out to a scammer

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/otp-share-guard.json

The files land in your project. They're yours to edit.

Usage

otp-share-guard-usage.tsx
import { OtpShareGuard } from "@/components/ui/otp-share-guard"

<OtpShareGuard
  code="482917"
  brand="Acme Bank"
  action={{ kind: "payment", amount: 24999, payee: "Croma Electronics" }}
  expected="login"
  onCall={onCall}
  screenSharing={sharing}
  variant={sms ? "sms" : "app"}
  expiresIn={300}
  onNotMe={() => new Promise((resolve) => setTimeout(resolve, 500))}
/>

What it catches

  • Code approving something other than what the user started
  • Active phone call or screen share while a code is shown
  • Code drawn on a shared screen: hidden while sharing
  • Quick copy under pressure: Copy is delayed
  • "I didn't request this" reporting in one tap

Props

OtpShareGuard props
PropTypeDefault
code* (required)

The one-time code. Anything but letters and digits is dropped.

string—
action* (required)

What the code authorises; drives the plain-language sentence.

OtpAction—
expected

What the user started in your app. A different `action.kind` triggers the strong warning.

OtpActionKind—
onCall

Your app knows a phone call is active.

boolean—
screenSharing

Your app knows the screen is being shared or recorded. The code is never drawn while true.

boolean—
brand

Your bank or app name, used in "{brand} will never call you for it".

string"We"
variant

`app` is an in-app card; `sms` draws it as the text message in RBI order (purpose first, code last).

"app" | "sms""app"
holdToReveal

Always require press-and-hold to see the code, even with no risk. Risks always require it.

boolean—
copyDelay

Seconds before Copy works when a risk is present. Defaults to 5.

number5
expiresIn

Seconds until the code expires, counted from when it is shown.

number—
onCodeCopy

Called after the code is copied.

(code: string) => void—
onNotMe

"I didn't request this": block the code and alert your fraud team. May return a Promise.

() => void | Promise<void>—
locale

BCP 47 locale for amounts and times.

string"en-IN"
messages

Any visible string.

MessageOverrides—

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Hold-to-reveal works with Space or Enter held as well as pointer
  • The hidden code is aria-hidden until revealed
  • Warnings are text with icons aria-hidden

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Bind each code to one action and amount, expire it quickly, and block approval when the user reports "not me".

  • Call and screen-share state must come from your app (onCall, screenSharing); the web can't detect them reliably
  • A determined user can still read the code aloud
  • Wording assumes Indian banking defaults (INR); pass your own messages

What it stops

Helps stop an OTP being read out to a scammer

Also known as

otp · one time password · scam · fraud · screen share · vishing · i didn't request this · sms code

Field type

Dependencies

Pairs well with