{"slug":"pin-input","title":"Pin Input","description":"Using the pin input machine in your project.","contentType":"component","framework":"react","content":"The pin input is optimized for entering a sequence of digits or letters. The\ninput fields allow one character at a time. When the digit or letter is entered,\nfocus transfers to the next input in the sequence, until every input is filled.\n\n## Resources\n\n\n[Latest version: v1.39.1](https://www.npmjs.com/package/@zag-js/pin-input)\n[Logic Visualizer](https://zag-visualizer.vercel.app/pin-input)\n[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/pin-input)\n\n\n\n**Features**\n\n- Automatically focuses the next field on typing and focuses the previous field\n  on deletion\n- Supports numeric and alphanumeric values\n- Supports masking value (for sensitive data)\n- Supports copy/paste to autofill all fields\n- Supports fast paste SMS-code\n\n## Installation\n\nInstall the pin input package:\n\n```bash\nnpm install @zag-js/pin-input @zag-js/react\n# or\nyarn add @zag-js/pin-input @zag-js/react\n```\n\n## Anatomy\n\nCheck the pin input anatomy and part names.\n\n> Each part includes a `data-part` attribute to help identify them in the DOM.\n\n\n\n## Usage\n\nImport the pin input package:\n\n```tsx\nimport * as pinInput from \"@zag-js/pin-input\"\n```\n\nThese are the key exports:\n\n- `machine` - State machine logic.\n- `connect` - Maps machine state to JSX props and event handlers.\n\n> You'll need to provide a unique `id` to the `useMachine` hook. This is used to\n> ensure every part has a unique identifier.\n\nThen use the framework integration helpers:\n\n```tsx\nimport * as pinInput from \"@zag-js/pin-input\"\nimport { useMachine, normalizeProps } from \"@zag-js/react\"\nimport { useId } from \"react\"\n\nexport function PinInput() {\n  const service = useMachine(pinInput.machine, { id: useId() })\n\n  const api = pinInput.connect(service, normalizeProps)\n\n  return (\n    <div>\n      <div {...api.getRootProps()}>\n        <input {...api.getInputProps({ index: 0 })} />\n        <input {...api.getInputProps({ index: 1 })} />\n        <input {...api.getInputProps({ index: 2 })} />\n      </div>\n      <button onClick={api.clearValue}>Clear</button>\n    </div>\n  )\n}\n```\n\n### Setting a default value\n\nSet `defaultValue` to define the initial pin value. It must be an array of\nstrings.\n\n```tsx {2}\nconst service = useMachine(pinInput.machine, {\n  defaultValue: [\"1\", \"2\", \"\"],\n})\n```\n\n### Controlled value\n\nUse the `value` and `onValueChange` properties to programmatically control the\nvalue of the pin input.\n\n```tsx\nimport { useState } from \"react\"\n\nexport function ControlledPinInput() {\n  const [value, setValue] = useState([\"\", \"\", \"\", \"\"])\n\n  const service = useMachine(pinInput.machine, {\n    value,\n    onValueChange(details) {\n      setValue(details.value)\n    },\n  })\n}\n```\n\n### Setting input count\n\nSet `count` to define the number of input fields to render.\n\n```tsx\nconst service = useMachine(pinInput.machine, {\n  count: 6,\n})\n```\n\n### Changing the placeholder\n\nTo customize the default pin input placeholder (○) for each input, pass the\n`placeholder` prop and set it to your desired value.\n\n```tsx {2}\nconst service = useMachine(pinInput.machine, {\n  placeholder: \"*\",\n})\n```\n\n### Blur on complete\n\nBy default, the last input maintains focus when filled and `onValueComplete` is\ninvoked. To blur the last input when the user completes the input, set the\n`blurOnComplete: true` in the machine's context.\n\n```tsx {2}\nconst service = useMachine(pinInput.machine, {\n  blurOnComplete: true,\n})\n```\n\n### Auto-submitting on complete\n\nSet `autoSubmit` to automatically submit the owning form when all inputs are\nfilled.\n\n```tsx {2}\nconst service = useMachine(pinInput.machine, {\n  autoSubmit: true,\n})\n```\n\n### Allowing alphanumeric values\n\nBy default, the pin input accepts only number values but you can choose between\n`numeric`, `alphanumeric` and `alphabetic` values. To change the input mode,\npass the `type` context property and set its value to `alphanumeric`.\n\n```tsx {2}\nconst service = useMachine(pinInput.machine, {\n  type: \"alphanumeric\",\n})\n```\n\n### Using a custom pattern\n\nUse `pattern` to provide a custom regex for validating input values. Each\ncharacter entered is checked against this pattern.\n\n```tsx {2}\nconst service = useMachine(pinInput.machine, {\n  pattern: \"[0-9a-fA-F]\", // hex characters only\n})\n```\n\n### Using OTP mode\n\nTo trigger smartphone OTP auto-suggestion, it is recommended to set the\n`autocomplete` attribute to \"one-time-code\". The pin-input machine provides\nsupport for this automatically when you set the `otp` context property to\n`true`.\n\n```tsx {2}\nconst service = useMachine(pinInput.machine, {\n  otp: true,\n})\n```\n\n### Securing the text input\n\nWhen collecting private or sensitive information using the pin input, you might\nneed to mask the value entered, similar to `<input type=\"password\"/>`. Pass the\n`mask` context property and set it to `true`.\n\n```tsx {2}\nconst service = useMachine(pinInput.machine, {\n  mask: true,\n})\n```\n\n### Listening for changes\n\nThe pin input machine invokes several callback functions when the user enters:\n\n- `onValueChange` — Function invoked when the value is changed.\n- `onValueComplete` — Function invoked when all fields have been completed (by\n  typing or pasting).\n- `onValueInvalid` — Function invoked when an invalid value is entered into the\n  input. An invalid value is any value that doesn't match the specified \"type\".\n\n```tsx\nconst service = useMachine(pinInput.machine, {\n  onValueChange(details) {\n    // details => { value: string[], valueAsString: string }\n    console.log(\"value changed to:\", details.value)\n  },\n  onValueComplete(details) {\n    // details => { value: string[], valueAsString: string }\n    console.log(\"completed value:\", details)\n  },\n  onValueInvalid(details) {\n    // details => { index: number, value: string }\n    console.log(\"invalid value:\", details)\n  },\n})\n```\n\n### Sanitizing pasted values\n\nUse `sanitizeValue` to strip dashes, spaces, or other formatting from pasted\nvalues before validation.\n\n```tsx\nconst service = useMachine(pinInput.machine, {\n  sanitizeValue: (value) => value.replace(/[-\\s]/g, \"\"),\n})\n```\n\n### Autofocus and selection behavior\n\nUse `autoFocus` to focus the first input on mount, and `selectOnFocus` to select\nthe active character on focus.\n\n```tsx\nconst service = useMachine(pinInput.machine, {\n  autoFocus: true,\n  selectOnFocus: true,\n})\n```\n\n### RTL support\n\nThe pin input machine supports RTL writing directions. Set `dir` to `rtl`.\n\nWhen this attribute is set, we attach a `dir` attribute to the root part.\n\n```tsx {2}\nconst service = useMachine(pinInput.machine, {\n  dir: \"rtl\",\n})\n```\n\n### Submitting with an external form\n\nSet `form` if the hidden input should submit with a form outside the current DOM\nsubtree. Use `required` to mark the input as required for form validation.\n\n```tsx\nconst service = useMachine(pinInput.machine, {\n  name: \"otp\",\n  form: \"checkout-form\",\n  required: true,\n})\n```\n\n### Customizing accessibility labels\n\nUse `translations.inputLabel` to customize screen-reader labels per input.\n\n```tsx\nconst service = useMachine(pinInput.machine, {\n  count: 4,\n  translations: {\n    inputLabel: (index, length) => `Digit ${index + 1} of ${length}`,\n  },\n})\n```\n\n## Styling guide\n\nEach part includes a `data-part` attribute you can target in CSS.\n\n### Completed state\n\nWhen all values have been filled, we attach a `data-complete` attribute to the\nroot and input parts.\n\n```css\n[data-part=\"root\"][data-complete] {\n  /* styles for when all value has been filled */\n}\n\n[data-part=\"input\"][data-complete] {\n  /* styles for when all value has been filled */\n}\n```\n\n### Invalid state\n\nWhen an invalid value is entered, we attach a `data-invalid` attribute to the\naffected input part.\n\n```css\n[data-part=\"input\"][data-invalid] {\n  /* styles for when the input is invalid */\n}\n```\n\n### Disabled state\n\nWhen the pin-input is disabled, we attach a `data-disabled` attribute to the\nroot and input parts.\n\n```css\n[data-part=\"root\"][data-disabled] {\n  /* styles for when the input is disabled */\n}\n\n[data-part=\"input\"][data-disabled] {\n  /* styles for when the input is disabled */\n}\n```\n\n## Methods and Properties\n\n### Machine Context\n\nThe pin input machine exposes the following context properties:\n\n**`name`**\nType: `string`\nDescription: The name of the input element. Useful for form submission.\n\n**`form`**\nType: `string`\nDescription: The associate form of the underlying input element.\n\n**`pattern`**\nType: `string`\nDescription: The regular expression that the user-entered input value is checked against.\n\n**`ids`**\nType: `Partial<{ root: string; hiddenInput: string; label: string; control: string; input: (id: string) => string; }>`\nDescription: The ids of the elements in the pin input. Useful for composition.\n\n**`disabled`**\nType: `boolean`\nDescription: Whether the inputs are disabled\n\n**`placeholder`**\nType: `string`\nDescription: The placeholder text for the input\n\n**`autoFocus`**\nType: `boolean`\nDescription: Whether to auto-focus the first input.\n\n**`invalid`**\nType: `boolean`\nDescription: Whether the pin input is in the invalid state\n\n**`required`**\nType: `boolean`\nDescription: Whether the pin input is required\n\n**`readOnly`**\nType: `boolean`\nDescription: Whether the pin input is in the valid state\n\n**`otp`**\nType: `boolean`\nDescription: If `true`, the pin input component signals to its fields that they should\nuse `autocomplete=\"one-time-code\"`.\n\n**`value`**\nType: `string[]`\nDescription: The controlled value of the the pin input.\n\n**`defaultValue`**\nType: `string[]`\nDescription: The initial value of the the pin input when rendered.\nUse when you don't need to control the value of the pin input.\n\n**`type`**\nType: `\"alphanumeric\" | \"numeric\" | \"alphabetic\"`\nDescription: The type of value the pin-input should allow\n\n**`onValueComplete`**\nType: `(details: ValueChangeDetails) => void`\nDescription: Function called when all inputs have valid values\n\n**`onValueChange`**\nType: `(details: ValueChangeDetails) => void`\nDescription: Function called on input change\n\n**`onValueInvalid`**\nType: `(details: ValueInvalidDetails) => void`\nDescription: Function called when an invalid value is entered\n\n**`mask`**\nType: `boolean`\nDescription: If `true`, the input's value will be masked just like `type=password`\n\n**`autoSubmit`**\nType: `boolean`\nDescription: Whether to auto-submit the owning form when all inputs are filled.\n\n**`blurOnComplete`**\nType: `boolean`\nDescription: Whether to blur the input when the value is complete\n\n**`selectOnFocus`**\nType: `boolean`\nDescription: Whether to select input value when input is focused\n\n**`sanitizeValue`**\nType: `(value: string) => string`\nDescription: Function to sanitize pasted values before validation.\nUseful for stripping dashes, spaces, or other formatting.\n\n**`translations`**\nType: `IntlTranslations`\nDescription: Specifies the localized strings that identifies the accessibility elements and their states\n\n**`count`**\nType: `number`\nDescription: The number of inputs to render to improve SSR aria attributes.\nThis will be required in next major version.\n\n**`dir`**\nType: `\"ltr\" | \"rtl\"`\nDescription: The document's text/writing direction.\n\n**`id`**\nType: `string`\nDescription: The unique identifier of the machine.\n\n**`getRootNode`**\nType: `() => ShadowRoot | Node | Document`\nDescription: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.\n\n### Machine API\n\nThe pin input `api` exposes the following methods:\n\n**`value`**\nType: `string[]`\nDescription: The value of the input as an array of strings.\n\n**`valueAsString`**\nType: `string`\nDescription: The value of the input as a string.\n\n**`complete`**\nType: `boolean`\nDescription: Whether all inputs are filled.\n\n**`count`**\nType: `number`\nDescription: The number of inputs to render\n\n**`items`**\nType: `number[]`\nDescription: The array of input values.\n\n**`setValue`**\nType: `(value: string[]) => void`\nDescription: Function to set the value of the inputs.\n\n**`clearValue`**\nType: `VoidFunction`\nDescription: Function to clear the value of the inputs.\n\n**`setValueAtIndex`**\nType: `(index: number, value: string) => void`\nDescription: Function to set the value of the input at a specific index.\n\n**`focus`**\nType: `VoidFunction`\nDescription: Function to focus the pin-input. This will focus the first input.\n\n### Data Attributes\n\n**`Root`**\n\n**`data-scope`**: pin-input\n**`data-part`**: root\n**`data-invalid`**: Present when invalid\n**`data-disabled`**: Present when disabled\n**`data-complete`**: Present when the pin-input value is complete\n**`data-readonly`**: Present when read-only\n\n**`Label`**\n\n**`data-scope`**: pin-input\n**`data-part`**: label\n**`data-invalid`**: Present when invalid\n**`data-disabled`**: Present when disabled\n**`data-complete`**: Present when the label value is complete\n**`data-required`**: Present when required\n**`data-readonly`**: Present when read-only\n\n**`Input`**\n\n**`data-scope`**: pin-input\n**`data-part`**: input\n**`data-disabled`**: Present when disabled\n**`data-complete`**: Present when the input value is complete\n**`data-filled`**: \n**`data-index`**: The index of the item\n**`data-invalid`**: Present when invalid\n\n## Accessibility\n\n### Keyboard Interactions\n\n**`ArrowLeft`**\nDescription: Moves focus to the previous input\n\n**`ArrowRight`**\nDescription: Moves focus to the next input\n\n**`Backspace`**\nDescription: Deletes the value in the current input and moves focus to the previous input\n\n**`Delete`**\nDescription: Deletes the value in the current input\n\n**`Control + V`**\nDescription: Pastes the value into the input fields","package":"@zag-js/pin-input","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/components/pin-input.mdx"}