# PIN Input

A PIN Input plugin that allows to fill in data for inputs.

[![npm](https://img.shields.io/badge/npm-v4.2.0-blue)](https://www.npmjs.com/package/@preline/pin-input) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Demo](https://img.shields.io/badge/demo-online-brightgreen)](https://preline.co/plugins/pin-input.html)

## Contents

- [Overview](#overview)
- [Installation](#installation)
- [Basic usage](#basic-usage)
- [Configuration Options](#configuration-options)
- [JavaScript API](#javascript-api)
- [Events](#events)
- [Common Patterns](#common-patterns)
- [License](#license)

## Overview

The PIN Input component provides a multi-field input interface for entering PIN codes, verification codes, or similar numeric/alphanumeric sequences. It automatically moves focus between fields and supports paste operations.

**Key Features:**
- Multiple input fields for PIN entry
- Automatic focus management
- Paste support for bulk entry
- Character validation via regex
- Programmatic control via JavaScript API
- Event system for completion tracking
- Accessibility support

## Installation

To get started, install PIN Input plugin via npm, else you can skip this step if you are already using Preline UI as a package.

```bash
npm i @preline/pin-input
```

### CSS

Use [`@source`](https://tailwindcss.com/docs/functions-and-directives#source-directive) to register the plugin's JavaScript path for Tailwind CSS scanning, then [`@import`](https://tailwindcss.com/docs/functions-and-directives#import-directive) the plugin's CSS files into your Tailwind CSS file.

```css
@import "tailwindcss";

/* @preline/pin-input */
@source "../node_modules/@preline/pin-input/*.js";
@import "./node_modules/@preline/pin-input/variants.css";
@import "./node_modules/@preline/pin-input/theme.css";
```

### JavaScript

Include the JavaScript that powers the interactive elements near the end of your `</body>` tag:

```html
<script src="./node_modules/@preline/pin-input/index.js"></script>
```

### Manual Initialization

Use the `non-auto` entry if you need manual initialization. In this mode, automatic initialization on page load is not included, so the component should be initialized explicitly.

```html
<script type="module">
  import HSPinInput from "@preline/pin-input/non-auto.mjs";

  new HSPinInput(document.querySelector("#pin-input"));
</script>
```

### Via Bundler

When using a bundler (Vite, webpack, etc.), import the plugin directly as an ES module.

`@preline/pin-input` is the auto-init entry: it scans the DOM and initializes matching elements automatically.

```js
import "@preline/pin-input";
```

`@preline/pin-input/non-auto` is the manual entry: use it when you want explicit control over when initialization happens, either via `autoInit()` or by creating a specific instance yourself.

```js
import HSPinInput from "@preline/pin-input/non-auto";

HSPinInput.autoInit();

// Or initialize a specific element manually
const el = document.querySelector("#pin-input");
if (el) new HSPinInput(el);
```

### TypeScript

This package ships with TypeScript type definitions. No additional `@types/` package is needed.

## Basic usage

The following example demonstrates the minimal HTML structure required for a PIN input component. This is a base template without custom styling - you can apply your own CSS classes and styles as needed. The component consists of multiple input fields that automatically advance focus.

```html
<div data-hs-pin-input>
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
</div>
```

**Structure Requirements:**
- `data-hs-pin-input`: Required on the container element
- `data-hs-pin-input-item`: Required on each input field
- All input fields should be direct children of the container

**Initial State:**
- Input fields are empty by default
- Focus starts on the first input field (if `autofocus` class is used)

## Configuration Options

### Data Options

Data options are specified in the `data-hs-pin-input` attribute.

| Attribute | Target Element | Type | Default | Description |
| --- | --- | --- | --- | --- |
| `data-hs-pin-input` | Container | - | - | Activate a Pin Input by specifying on an element. Should be added to the container. |
| `:availableCharsRE` | Inside `data-hs-pin-input` | RegExp (string) | `"^[a-zA-Z0-9]+$"` | Regular expression string for available characters. Only characters matching this regex will be accepted. |
| `data-hs-pin-input-item` | Input element | - | - | Determines which element inside the initialized container will be responsible for entering data. Should be added to the input. |

**Example:**
```html
<div data-hs-pin-input='{
  "availableCharsRE": "^[0-9]+$"
}'>
  <!-- Input fields -->
</div>
```

### CSS Classes (Modifiers)

CSS class modifiers use Tailwind-style syntax with `--` prefix to control PIN input behavior.

| Class | Required On | Purpose |
| --- | --- | --- |
| `autofocus` | One of the input fields | If one of the fields has this class, it will be focused when the page loads. |

### Tailwind Modifiers

| Name | Description |
| --- | --- |
| `hs-pin-input-active:*` | The class is added to the input when the PIN has been set up (all fields are filled). |

## JavaScript API

The `HSPinInput` object is available in the global `window` object after the plugin is loaded.

### Instance Methods

These methods are called on a PIN input instance.

| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `destroy()` | None | `void` | Destroys the PIN input instance, removes all generated markup, classes, and event listeners. Use when removing PIN input from DOM. |

### Static Methods

These methods are called directly on the `HSPinInput` class.

| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `HSPinInput.getInstance(target, isInstance)` | `target`: `HTMLElement \| string` (CSS selector)<br>`isInstance`: `boolean` (optional) | `HSPinInput \| { id: string \| number, element: HSPinInput } \| null` | Returns the PIN input instance associated with the target. If `isInstance` is `true`, returns collection item object `{ id, element }` where `element` is the `HSPinInput` instance. If `isInstance` is `false` or omitted, returns the `HSPinInput` instance directly. Returns `null` if PIN input instance is not found. |

### Usage Examples

**Example 1: Destroying PIN input instance**
```javascript
const instance = HSPinInput.getInstance('#hs-pin-input', true);

if (instance) {
  const { element } = instance;
  const destroyBtn = document.querySelector('#hs-destroy-btn');

  destroyBtn.addEventListener('click', () => {
    element.destroy();
  });
}
```

**Example 2: Getting instance and accessing properties**
```javascript
// Get the PIN input instance
const instance = HSPinInput.getInstance('#hs-pin-input', true);

if (instance) {
  const { element } = instance;

  // Access instance properties
  console.log('Current value:', element.currentValue);
  console.log('Items:', element.items);

  // Clean up when removing from DOM
  function removePinInput() {
    element.destroy();
  }
}
```

**Example 3: Destroying PIN input instance**
```javascript
const instance = HSPinInput.getInstance('#hs-pin-input', true);

if (instance) {
  const { element } = instance;
  const removeBtn = document.querySelector('#hs-remove-btn');

  removeBtn.addEventListener('click', () => {
    // Clean up before removing from DOM
    element.destroy();
    // Now safe to remove the element
    document.querySelector('#hs-pin-input').remove();
  });
}
```

## Events

PIN input instances emit events that can be listened to for completion tracking and custom behavior.

| Event Name | When Fired | Callback Parameter | Description |
| --- | --- | --- | --- |
| `on:completed` | When the PIN has been set up (all fields filled) | `{ currentValue: string }` | Fires when all input fields have been filled. Returns an object with the `currentValue` containing the complete PIN string. |

### Event Usage Example

```javascript
// Get PIN input instance
const instance = HSPinInput.getInstance('#hs-pin-input', true);

if (instance) {
  const { element } = instance;

  // Listen to completed event
  element.on('completed', ({ currentValue }) => {
    console.log('PIN completed:', currentValue);
    // Perform actions after PIN is completed
    // e.g., validate PIN, submit form, proceed to next step
  });
}
```

## Common Patterns

### Pattern 1: Numeric PIN Only

Restrict input to numbers only.

```html
<div data-hs-pin-input='{
  "availableCharsRE": "^[0-9]+$"
}'>
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
</div>
```

### Pattern 2: Auto-focus First Field

Automatically focus the first field on page load.

```html
<div data-hs-pin-input>
  <input type="text" data-hs-pin-input-item class="autofocus">
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
</div>
```

### Pattern 3: Handling Completion

Handle PIN completion with event listener.

```html
<div id="hs-pin-input-first" data-hs-pin-input>
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
  <input type="text" data-hs-pin-input-item>
</div>

<script>
  const instance = HSPinInput.getInstance('#hs-pin-input-first', true);
  
  if (instance) {
    const { element } = instance;

    element.on('completed', ({ currentValue }) => {
      console.log('PIN entered:', currentValue);
      // Validate or submit the PIN
    });
  }
</script>
```

## License

Copyright (c) 2026 Preline Labs.

Licensed under the [MIT License](https://opensource.org/licenses/MIT).
