Ana içeriğe geç

ViraStack Mask: Input Masks for React Hook Form

Teknik2 dkEnglish

In a checkout form, the card number field alone is enough trouble: how many digits, where the spaces go, whether the number is valid. Validity needs the Luhn algorithm; American Express changes the mask and grows the CVV to four digits. Phone numbers and IBANs bring rules of their own. Rewriting all of it in every project costs time and invites bugs.

ViraStack Mask (@virastack/mask) gives you ready, tested masks for these fields. It works with React Hook Form: the user sees the formatted value, the form keeps the raw one.

What it does

  • Built for React Hook Form: The input shows 4111 1111 1111 1111, the form holds 4111111111111111. reset, setValue, watch and resolvers keep working.
  • Type-safe: Field names are checked against your form type; a wrong one fails at compile time.
  • International presets: E.164 phone numbers, any IBAN with mod-97, cards with Luhn, currencies through Intl, dates.
  • Same rules on the server: @virastack/mask/server validates the same schema in a Server Action.
  • Zero dependencies: Under 10 KB for everything.

Try it

The card number, expiry and CVV work together. Type an Amex number starting with 34 and watch the mask and the CVV length change. Under each field you'll see the raw value next to the formatted one.

rawValue: —value: —
rawValue: —value: —
rawValue: —value: —

Install

npm install @virastack/mask

Usage

Pass the form and a schema; each key is a field name, each value a preset:

credit-card-form.tsx
import { useForm } from "react-hook-form"
import { useViraMask } from "@virastack/mask"
 
type Card = { card: string; expiry: string; cvv: string }
 
export function CreditCardForm() {
  const form = useForm<Card>({ defaultValues: { card: "", expiry: "", cvv: "" } })
 
  const { card, expiry, cvv } = useViraMask(form, {
    card: "card",
    expiry: "expiry",
    cvv: { preset: "cvv", from: "card" }
  })
 
  return (
    <form onSubmit={form.handleSubmit(console.log)}>
      <input {...card} placeholder="0000 0000 0000 0000" />
      <input {...expiry} placeholder="MM/YY" />
      <input {...cvv} placeholder="123" />
    </form>
  )
}

Type 4111 1111 1111 1111 and form.watch("card") returns "4111111111111111", no spaces. from: "card" links the CVV to the card, so an Amex number switches it to four digits.

Validation errors come back as keys, not text, so the wording stays yours:

import { createMask } from "@virastack/mask/server"
 
createMask("card").validate("4111")                   // "card.incomplete"
createMask("card").validate("4111111111111112")       // "card.invalid" (Luhn)
createMask("expiry").validate("0120")                 // "expiry.min", expired
createMask("iban").validate("GB82WEST12345698765431") // "iban.invalid" (mod-97)

Add "phone", "iban", "date" or { preset: "currency", currency: "USD" } to the same schema for the other fields.

Every preset, server validation, resolvers and live examples are in the virastack.com/mask docs, and the source is in github.com/virastack/form. For a password field in the same form, see ViraStack Password.