Skip to main content
View Zag.js on Github
Join the Discord server

Pin Input

The pin input is optimized for entering a sequence of digits or letters. The input fields allow one character at a time. When the digit or letter is entered, focus transfers to the next input in the sequence, until every input is filled.



  • Automatically focuses the next field on typing and focuses the previous field on deletion.
  • Supports numeric and alphanumeric values.
  • Support for masking value (for sensitive data).
  • Support for copy/paste to autofill all fields.
  • Supports fast paste SMS-code.


To use the pin input machine in your project, run the following command in your command line:

npm install @zag-js/pin-input @zag-js/react # or yarn add @zag-js/pin-input @zag-js/react

This command will install the framework agnostic pin input logic and the reactive utilities for your framework of choice.


To set up the pin input correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.


First, import the pin input package into your project

import * as pinInput from "@zag-js/pin-input"

The pin input package exports two key functions:

  • machine — The state machine logic for the pin input widget.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.

You'll need to provide a unique id to the useMachine hook. This is used to ensure that the every part has a unique identifier.

Next, import the required hooks and functions for your framework and use the pin input machine in your project 🔥

import * as pinInput from "@zag-js/pin-input" import { useMachine, normalizeProps } from "@zag-js/react" export function PinInput() { const [state, send] = useMachine(pinInput.machine({ id: "1" })) const api = pinInput.connect(state, send, normalizeProps) return ( <div> <div {...api.rootProps}> <input {...api.getInputProps({ index: 0 })} /> <input {...api.getInputProps({ index: 1 })} /> <input {...api.getInputProps({ index: 2 })} /> </div> <button onClick={api.clearValue}>Clear</button> </div> ) }

Setting a default value

To set the initial value of the pin input, pass the value property to the machine's context.

const [state, send] = useMachine( pinInput.machine({ value: ["1", "2", ""], }), )

Changing the placeholder

To customize the default pin input placeholder (○) for each input, pass the placeholder prop and set it to your desired value.

const [state, send] = useMachine( pinInput.machine({ placeholder: "*", }), )

Blur on complete

By default, the last input maintains focus when filled and we invoke the onComplete callback. To blur the last input when the user completes the input, set the blurOnComplete: true in the machine's context.

const [state, send] = useMachine( pinInput.machine({ blurOnComplete: true, }), )

Allowing alphanumeric values

By default, the pin input accepts only number values but you can choose between numeric, alphanumeric and alphabetic values. To change the input mode, pass the type context property and set its value to alphanumeric.

const [state, send] = useMachine( pinInput.machine({ type: "alphanumeric", }), )

Using OTP mode

To trigger smartphone OTP auto-suggestion, it is recommended to set the autocomplete attribute to "one-time-code". The pin-input machine provides support for this automatically when you set the otp context property to true.

const [state, send] = useMachine( pinInput.machine({ otp: true, }), )

Securing the text input

When collecting private or sensitive information using the pin input, you might need to mask the value entered, similar to <input type="password"/>. Pass the mask context property and set it to true.

const [state, send] = useMachine( pinInput.machine({ mask: true, }), )

Listening for changes

The pin input machine invokes several callback functions when the user enters:

  • onValueChange — Function invoked when the value is changed.
  • onValueComplete — Function invoked when all fields have been completed (by typing or pasting).
  • onValueInvalid — Function invoked when an invalid value is entered into the input. An invalid value is any value that doesn't match the specified "type".
const [state, send] = useMachine( pinInput.machine({ onValueChange(value) { // value => string[] console.log("value changed to:", value) }, onValueComplete(details) { // details => { value: string[], valueAsString: string } console.log("completed value:", details) }, onValueInvalid(details) { // details => { index: number, value: string } console.log("invalid value:", details) }, }), )

RTL support

The pin input machine supports RTL writing directions. To set the dir property in the machine's context.

When this attribute is set, we attach a dir attribute to the root part.

const [state, send] = useMachine( pinInput.machine({ dir: "rtl", }), )

Styling guide

Earlier, we mentioned that each pin input's part has a data-part attribute added to them to select and style them in the DOM.

Completed state

When all values have been filled, we attach a data-complete attribute to the root and input parts.

[data-part="root"][data-complete] { /* styles for when all value has been filled */ } [data-part="input"][data-complete] { /* styles for when all value has been filled */ }

Invalid state

When an invalid value is entered, we attach a data-invalid attribute to the affected input part.

[data-part="input"][data-invalid] { /* styles for when the input is invalid */ }

Disabled state

When the pin-input is disabled, we attach a data-disabled attribute to the root and input parts.

[data-part="root"][data-disabled] { /* styles for when the input is disabled */ } [data-part="input"][data-invalid] { /* styles for when the input is disabled */ }

Methods and Properties

Machine Context

The pin input machine exposes the following context properties:

  • namestringThe name of the input element. Useful for form submission.
  • formstringThe associate form of the underlying input element.
  • patternstringThe regular expression that the user-entered input value is checked against.
  • idsPartial<{ root: string; hiddenInput: string; label: string; control: string; input(id: string): string; }>The ids of the elements in the pin input. Useful for composition.
  • disabledbooleanWhether the inputs are disabled
  • placeholderstringThe placeholder text for the input
  • autoFocusbooleanWhether to auto-focus the first input.
  • invalidbooleanWhether the pin input is in the invalid state
  • otpbooleanIf `true`, the pin input component signals to its fields that they should use `autocomplete="one-time-code"`.
  • valuestring[]The value of the the pin input.
  • type"alphanumeric" | "numeric" | "alphabetic"The type of value the pin-input should allow
  • onValueComplete(details: ValueChangeDetails) => voidFunction called when all inputs have valid values
  • onValueChange(details: ValueChangeDetails) => voidFunction called on input change
  • onValueInvalid(details: ValueInvalidDetails) => voidFunction called when an invalid value is entered
  • maskbooleanIf `true`, the input's value will be masked just like `type=password`
  • blurOnCompletebooleanWhether to blur the input when the value is complete
  • selectOnFocusbooleanWhether to select input value when input is focused
  • translationsIntlTranslationsSpecifies the localized strings that identifies the accessibility elements and their states
  • dir"ltr" | "rtl"The document's text/writing direction.
  • idstringThe unique identifier of the machine.
  • getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The pin input api exposes the following methods:

  • valuestring[]The value of the input as an array of strings.
  • valueAsStringstringThe value of the input as a string.
  • isValueCompletebooleanWhether all inputs are filled.
  • setValue(value: string[]) => voidFunction to set the value of the inputs.
  • clearValue() => voidFunction to clear the value of the inputs.
  • setValueAtIndex(index: number, value: string) => voidFunction to set the value of the input at a specific index.
  • focus() => voidFunction to focus the pin-input. This will focus the first input.

Edit this page on GitHub

On this page