AdminKitDocs
Forms

Next.js Input Mask

A Next.js input mask for the App Router in AdminKit PRO: a client component that loads Inputmask in the browser and masks dates, amounts and IP addresses.

Pro

This component is only available in AdminKit PRO. Learn more.

Some fields only make sense in one format: a date, an amount, an IP address. MaskedInput keeps people to that format while they type, with Inputmask 5 on a plain <input type="text">. It is part of the PRO version of AdminKit for Next.js.

MaskedInput is a client component, a file that starts with "use client", because Inputmask works in the browser. The HTML that Next.js renders on the server has the plain, unmasked input. Once the browser has taken over that HTML (hydration), a useEffect imports Inputmask with a dynamic import() and applies the mask. So the library never runs on the server, and it only loads on pages that use it.

Example

Your page can stay a server component and render MaskedInput directly, as long as mask is a string or an object of plain values. The demo's Advanced Inputs page does this:

src/app/(dashboard)/vehicles/page.tsx
import { MaskedInput } from "@/components/plugins/masked-input";
import { pageMetadata } from "@/lib/site";

export const metadata = pageMetadata("/vehicles");

export default function VehiclesPage() {
  return (
    <>
      <h1 className="h3 mb-3">Vehicles</h1>
      <div className="mb-3">
        <label className="form-label" htmlFor="maskDate">Date</label>
        <MaskedInput
          className="form-control"
          id="maskDate"
          mask={{ alias: "datetime", inputFormat: "dd/mm/yyyy" }}
          aria-describedby="maskDateHelp"
        />
        <div className="form-text" id="maskDateHelp">e.g "DD/MM/YYYY"</div>
      </div>
      <div>
        <label className="form-label" htmlFor="maskPlate">License plate</label>
        <MaskedInput
          className="form-control"
          id="maskPlate"
          mask="[9-]AAA-999"
          aria-describedby="maskPlateHelp"
        />
        <div className="form-text" id="maskPlateHelp">e.g "9-ABC-123"</div>
      </div>
    </>
  );
}

9 is a digit, A a letter (shown in upper case), and [ ] marks an optional part.

Props

PropTypeDefaultDescription
maskstring | MaskOptionsA mask string, or Inputmask's options (mask, alias, regex and others); read once, after the input mounts
refRef<HTMLInputElement>The input element, merged with the component's own ref

Other props, such as className, id, name, placeholder and defaultValue, go to the <input>. type defaults to "text". A server component can only pass plain data to the browser, so Inputmask options that are functions, such as onBeforeMask, cannot come from it. Pass them from a client component. The component itself is in src/components/plugins/masked-input.tsx.

Aliases

For common formats, Inputmask has aliases: named masks with their own options. These are the masks of the Advanced Inputs demo:

FieldmaskExample
Date{ alias: "datetime", inputFormat: "dd/mm/yyyy" }31/12/2026
Date{ alias: "datetime", inputFormat: "mm/dd/yyyy" }12/31/2026
Currency{ alias: "numeric", groupSeparator: ",", digits: 2, digitsOptional: false, prefix: "€ ", placeholder: "0" }€ 9.95
Decimal{ alias: "decimal", groupSeparator: "," }123,456,789
IP address{ alias: "ip" }123.123.123.123
Email{ alias: "email" }support@adminkit.io
<label className="form-label" htmlFor="maskCurrency">Amount</label>
<MaskedInput
  className="form-control"
  id="maskCurrency"
  mask={{
    alias: "numeric",
    groupSeparator: ",",
    digits: 2,
    digitsOptional: false,
    prefix: "$ ",
    placeholder: "0",
  }}
/>

See the Inputmask README for all options.

Reading the value

Inputmask writes the input's value itself, so MaskedInput is uncontrolled: never pass value. Read the value from the form when it is submitted, in a client component. Then post it to your API with fetch, because a static export has no Server Actions:

src/components/phone-form.tsx
"use client";

import type { FormEvent } from "react";
import { MaskedInput } from "@/components/plugins/masked-input";

export function PhoneForm() {
  const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    await fetch("https://api.example.com/profile", {
      method: "POST",
      body: new FormData(event.currentTarget),
    });
  };

  return (
    <form onSubmit={onSubmit}>
      <label className="form-label" htmlFor="phone">Phone</label>
      <MaskedInput className="form-control mb-3" id="phone" name="phone" mask="(999) 999-9999" />
      <button type="submit" className="btn btn-primary">Save</button>
    </form>
  );
}

The submitted value keeps the mask's literal characters, such as (555) 123-4567. A ref gives you the input element too. mask is read once. To change it, render a new input with another key.

Accessibility

  • Label the input with <label htmlFor> and its id.
  • Screen readers do not announce the mask. Show the format in help text linked with aria-describedby.
  • Add inputMode ("numeric", "decimal", "email") for the right mobile keyboard.

Last updated on

On this page