# TaxIdInput

> EIN, SSN, VAT and GST numbers with per-scheme format checks, masked at rest.

Source: https://lopifi.com/components/tax-id-input
Part of: Lopifi — US GAAP books on autopilot, checked by a CPA (https://lopifi.com)

---

- **Family:** Tax & compliance — The obligations around the books: what is due to whom, who has been verified, and which controls held.
- **Status:** Implemented
- **Spec:** https://lopifi.com/docs/catalog/14-tax-compliance

## Install

```bash
npx shadcn@latest add @lopifi/theme
npx shadcn@latest add @lopifi/tax-id-input
```

## Specification

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."

## Rules this component obeys

Money is minor units, never floats. `null` is not `0` and neither is redacted. Negatives render in accounting parentheses with the parenthesis column reserved on both sides. Debit and credit are two columns. Per-currency decimal places. Polarity is explicit — up is not always good. Every figure states its period, entity, currency, basis and as-of.
