TaxIdInput
EIN, SSN, VAT and GST numbers with per-scheme format checks, masked at rest.
npx shadcn add @lopifi/tax-id-inputExamples
The same component, case by case. Each stage is the shipped code with the props it shows; switch to Code for the usage.
How it behaves
What it renders, what it refuses to render, and why. The same paragraphs as the family spec, so the two can’t disagree.
One field for ein | ssn | itin | abn | gst | vat, each in its issuer's printed form, checked by the issuer's published algorithm where one exists and by format where none does. The result says which kind of check passed.
| Scheme | Printed | Check |
|---|---|---|
ein |
12-3456789 |
Format only: nine digits. There's no check digit, and the IRS's valid-prefix list changes, so a stale copy here would reject a real number. |
ssn |
123-45-6789 |
Format: the area is never 000, 666 or 900–999, the group never 00, the serial never 0000. |
itin |
9XX-XX-XXXX |
Format: a leading 9, with the fourth and fifth digits in 50–65, 70–88, 90–92 or 94–99. |
abn |
12 345 678 901 |
Checksum: subtract 1 from the first digit, apply the weights 10, 1, 3, 5 … 19, and the sum must be divisible by 89. |
gst |
123456789 RT0001 |
Canadian GST/HST: the nine-digit business number must pass Luhn, followed by the RT program account. |
vat |
DE123456789 |
Format by country prefix only (EU member states, XI, GB). An unknown prefix is "not checked", not invalid. |
A pass is not a verification. A well-formed number may never have been issued. Registry lookups (IRS TIN matching, VIES, HMRC, ABN Lookup) belong to the consumer's backend and come back through error.
Masked whenever it isn't focused. Every character but the last four is a bullet in the DOM, so a screenshot, a screen share or a copied selection carries the mask, not the number. A VAT number keeps its country prefix, since the prefix isn't the secret. Focus shows the whole number, and formatting applies on blur only, because inserting a hyphen mid-typing moves the caret. With name, a hidden input posts the real value. The visible field never does.
Never logged: no console output, no data attribute, autoComplete="off", no spellcheck. type="text" with the scheme's inputMode, and paste is never blocked.
Validation timing: "incomplete" waits until the field is left. A wrong number at full length says so at once. The message line is reserved, so a result arriving doesn't move the form. It crossfades in and never shakes.
Accessibility: a real <label>. aria-invalid and aria-describedby point at the message. At rest the masked field is described as "SSN hidden, ending 6789. Focus the field to show it."
The accounting it encodes
The concepts this component renders, each defined once in the lexicon.
More in Tax & compliance
See also
Where this component is specified, defined and running.
Elsewhere in the system
The live section TaxIdInput renders in, the calculators behind its figures, and the ledger payloads that feed it.
- TaxIdInput, runningThe compliance showcase — live, with theme, density, grayscale and reduced-motion switches.
- Tax & complianceControlTestResult sits beside them and speaks their idiom. Every component in this family takes an explicit asOf. "Today" is never the client clock, so a server render, a printed copy and a screenshot agree on what is late. Shared types live in src/ledger/lib/compliance.types.ts; the tax-ID checks l
Components are copied into your repository and meant to be edited. Rounding policy, fiscal calendar and compliance copy differ per company.
Release notes
Get an email when Tax & compliance changes
Your copy doesn’t update itself when we fix something here.
- One email when a Tax & compliance component needs a migration
- Nothing when nothing changes
Or install it now
npx shadcn add @lopifi/tax-id-input