Taiwan Business Tax ID Input (React)
A React component that validates Taiwan's 8-digit business tax ID (統一編號) in real time — it implements the 2023 check-digit algorithm (weights 1,2,1,2,1,2,4,1, including the special case where the 7th digit is 7) and shows pass/fail with animated feedback as you type.
This implements the Ministry of Finance's published check-digit algorithm for the Taiwan business tax ID: each digit is multiplied by the weights 1,2,1,2,1,2,4,1, the digit sums of those products are added up, and the total passes if it is divisible by 5 (the rules in force since April 2023, backwards compatible with older tax IDs), with the special case where the 7th digit is 7 handled as well. Validation runs the moment 8 digits are entered — a green check pulses on success, and the whole row shakes with a red outline and an explanation on failure. Pasting a string with noise in it is cleaned automatically down to the digits.
npx shadcn@latest add https://webberui.com/r/tax-id-input.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<TaxIdInput />
Installation
npx shadcn@latest add https://webberui.com/r/tax-id-input.jsonOr, once registries are configured in components.json, install it as @webberui/tax-id-input.
Usage
import { TaxIdInput, validateTaxId } from "@/components/ui/tax-id-input";
<TaxIdInput
label="統一編號"
onChange={(value) => console.log(value)}
onValid={(ok, value) => ok && console.log(`${value} can be invoiced`)}
/>
// The validator can be used on its own (handy for one more check before form submission)
validateTaxId("12345675"); // true (an algorithm test number, not a real company)
validateTaxId("12345678"); // falseProps
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled value (digits only, at most 8); when omitted the component is uncontrolled |
defaultValue | string | "" | Initial value in uncontrolled mode |
onChange | (value: string) => void | — | Callback when the value changes; the argument is the cleaned digits-only string |
onValid | (valid: boolean, value: string) => void | — | Callback when 8 digits have been entered and validation completes; it fires once per value |
label | string | "統一編號" | Field label text |
disabled | boolean | false | Disable the input |
name | string | — | Form field name forwarded to the native input |
className | string | — | Forwarded to the outermost container |
validateTaxId(input), sanitizeTaxId(raw), and the TaxIdStatus type are also named exports, so the same validation logic can be reused at the form layer.
Accessibility
- The real input is carried by a single native
input(inputMode="numeric",pattern="[0-9]*"); the 8 visual boxes are presentational only and markedaria-hidden, so keyboard behavior and the mobile numeric keypad behave exactly like an ordinary input - The validation message sits in an
aria-live="polite"region, so state changes are read out by screen readers;aria-invalidis set at the same time when validation fails - When the user has "reduce motion" enabled at the system level, the shake, pulse, and caret blink animations are disabled and only the color and text feedback remains