Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

VAT Validator Billing Block for React Aria

The tax an invoice carries for a buyer: a country and a VAT number checked for format, a three-case outcome list with the matching case marked and the total of a 100.00 invoice in each, and a preview of how the totals print.

Framework
UI library

Preview

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Overview

Show a seller what tax a European sale carries, with this VAT validator for React with React Aria. A hairline frame with a plus mark on each corner holds a form and a side cell. The form has a Native Select for the buyer's country and an optional VAT number in an Input, with a Shine Button that checks the number, the only colored control. The check is a pure function on the format: the prefix must match the country and the length and digits must fit. The side cell shows how a 100.00 invoice will print, with VAT at 19 percent or a "Reverse charge" line and the buyer's number. Below, a ruled list of the three cases (a valid number from another EU country, a valid German number, no number or a bad one) marks the one that applies with an outline Badge and a bar at its start, each with its total. The status line says what was found, and that no registry was asked. It's built from Field, Input, Native Select, Badge, Shine Button and Guides, with a CSS entrance only. For SaaS billing settings, B2B checkouts and invoicing apps that sell across borders.

FAQ

How do I change the seller country, the rate or the countries?

Edit the constants at the top of the file. SELLER is the seller's country code and RATE its VAT percentage; the three rows in OUTCOMES carry the case, its text and the rate it applies; PLACES lists the buyer countries, each with a code, a name, an example shown in the hint and the pattern a whole number must match, prefix included. The totals on the rows and in the side cell are worked out from NET and the rate.

Does it ask a VAT registry, and what stays local?

No. It checks the format only: the country prefix, the length and the digits. It cannot tell you whether a number is registered or belongs to the buyer. The status line says so, and so does the hint. For a real check, send the number to your server and ask the EU VIES service or a tax provider before you zero-rate an invoice, then keep the answer and its date with the invoice. The sentence and the marked row in the demo come from the local function.

How is the case chosen?

An empty field is a consumer and falls in the last case. A number that does not fit the chosen country's format also falls there, and the field says what is wrong, including when the number starts with another country's prefix. A valid number from the seller's own country is the second case, because domestic sales keep VAT. A valid number from any other country in the list is the first case, reverse charge at 0 percent, and the side cell prints "Reverse charge" with the buyer's number.

What happens when I edit a field after a check?

The marked row and the side cell keep the last check's result, and the status line says "Edited since the last check. Check again to update the outcome." The result changes only when you press Check VAT number, so what you read is always one that was computed from the values it names.

Is it accessible?

Both fields have visible labels, the number's hint and its problem share one slot that is read with the field, and focus goes to the number when a check fails. The marked row has aria-current and a visible badge, so it is not only a color or a bar. The status line is a polite live region. Amounts and numbers are set left to right inside right-to-left pages.

How does it look on a phone?

The country and the number stack, the side cell moves under the form, and the three cases stay a list with the total at the end of each row. The check button comes straight after the fields, and the status line sits at the bottom with room for the longest sentence, so the panel does not jump.