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

> This component is only available in **AdminKit PRO**. [Learn more](/pricing/#nextjs).

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](https://github.com/RobinHerbots/Inputmask) 5 on a plain `<input type="text">`. It is part of the PRO version of [AdminKit for Next.js](/nextjs/).

`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:

```html
<div class="mb-3">
  <label class="form-label" for="maskDate">Date</label>
  <input type="text" class="form-control" id="maskDate" data-inputmask-alias="datetime" data-inputmask-inputformat="dd/mm/yyyy" aria-describedby="maskDateHelp">
  <div class="form-text" id="maskDateHelp">e.g "DD/MM/YYYY"</div>
</div>
<div>
  <label class="form-label" for="maskPlate">License plate</label>
  <input type="text" class="form-control" id="maskPlate" data-inputmask-mask="[9-]AAA-999" aria-describedby="maskPlateHelp">
  <div class="form-text" id="maskPlateHelp">e.g "9-ABC-123"</div>
</div>
```
```tsx code title="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

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `mask` | `string \| MaskOptions` | | A mask string, or Inputmask's options (`mask`, `alias`, `regex` and others); read once, after the input mounts |
| `ref` | `Ref<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:

| Field | `mask` | Example |
| --- | --- | --- |
| 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 |

```html
<label class="form-label" for="maskCurrency">Amount</label>
<input type="text" class="form-control" id="maskCurrency" data-inputmask="'alias': 'numeric', 'groupSeparator': ',', 'digits': 2, 'digitsOptional': false, 'prefix': '$ ', 'placeholder': '0'">
```
```tsx code
<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](https://github.com/RobinHerbots/Inputmask) 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:

```tsx title="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.

## Related

- [React input mask](/react/docs/forms/input-mask/): the same component in AdminKit for React (Vite)
- [Bootstrap 5 input mask](/bootstrap/docs/forms/input-mask/): the input mask of the HTML version
- [Form controls](/nextjs/docs/forms/form-control/): plain inputs and submitting a form
- [Advanced Inputs demo](https://nextjs-demo.adminkit.io/forms-advanced-inputs)

---

Canonical HTML page: https://adminkit.io/nextjs/docs/forms/input-mask/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
