KTUIKTUI
KTUIKTUI
ComponentsDocsStudio

Getting Started

IntroductionInstallationApproachThemingJavaScriptTypeScriptDark modeRTLReferencesChangelog - v1.2.2Metronic TemplatePopular

Components

AccordionAvatarAlertBadgeBreadcrumbButtonCardCarouselNewClipboardNewCheckboxCollapseDatatableDismissDrawerDropdownImage InputInputKbdLinkModalPaginationPin inputNewProgressRadio GroupRange SliderNewRatingNewReparentRepeaterNewScrollableScrollspyScrolltoSelectSeparatorSkeletonStepperStickySwitchTabsTextareaTheme SwitchToastTooltipToggleToggle GroupToggle PasswordTooltip
©2026 KtUI. All rights reserved.
A project by Keenthemes
Docs
Pin input

Pin input

Multi-field PIN and OTP entry with KtUI keyboard navigation, paste distribution, optional character filtering, and hidden-field form sync.

Examples

Examples below use Tailwind utility classes for OTP-style layouts.

Basic

<div class="flex max-w-md gap-2" data-kt-pin-input="true">
  <input
    type="text"
    maxlength="1"
    inputmode="numeric"
    autocomplete="one-time-code"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    autocomplete="one-time-code"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    autocomplete="one-time-code"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    autocomplete="one-time-code"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  />
</div>

Placeholder

<div class="flex max-w-md gap-2" data-kt-pin-input="true">
  <input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  />
</div>

Gray variant

<div class="flex max-w-md gap-2" data-kt-pin-input="true">
  <input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 border-border/80 bg-muted/40 px-0 text-center text-sm focus:bg-background"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 border-border/80 bg-muted/40 px-0 text-center text-sm focus:bg-background"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 border-border/80 bg-muted/40 px-0 text-center text-sm focus:bg-background"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 border-border/80 bg-muted/40 px-0 text-center text-sm focus:bg-background"
    data-kt-pin-input-item="true"
  />
</div>

Underline variant

<div class="flex max-w-md gap-2" data-kt-pin-input="true">
  <input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="size-10 shrink-0 border-0 border-b-2 border-border bg-transparent px-0 text-center text-sm shadow-none focus-visible:border-primary focus-visible:ring-0"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="size-10 shrink-0 border-0 border-b-2 border-border bg-transparent px-0 text-center text-sm shadow-none focus-visible:border-primary focus-visible:ring-0"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="size-10 shrink-0 border-0 border-b-2 border-border bg-transparent px-0 text-center text-sm shadow-none focus-visible:border-primary focus-visible:ring-0"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="size-10 shrink-0 border-0 border-b-2 border-border bg-transparent px-0 text-center text-sm shadow-none focus-visible:border-primary focus-visible:ring-0"
    data-kt-pin-input-item="true"
  />
</div>

Focus scale

<div class="flex max-w-md gap-2" data-kt-pin-input="true">
  <input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm transition-transform focus:scale-110"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm transition-transform focus:scale-110"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm transition-transform focus:scale-110"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm transition-transform focus:scale-110"
    data-kt-pin-input-item="true"
  />
</div>

Lengths (3, 5, 7)

<div class="flex max-w-xl flex-col gap-4">
  <div class="flex gap-2" data-kt-pin-input="true">
    <input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    />
  </div>
  <div class="flex gap-2" data-kt-pin-input="true">
    <input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    />
  </div>
  <div class="flex gap-2" data-kt-pin-input="true">
    <input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    />
  </div>
</div>

Regex subset (digits 0–3)

<div
  class="flex max-w-md gap-2"
  data-kt-pin-input="true"
  data-kt-pin-input-available-chars="[0-3]"
>
  <input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  />
</div>

Telephone number

Ten numeric cells with type="tel" and inputMode="numeric" so mobile browsers show the phone-style keypad. Paste distributes digits across cells.

<div class="space-y-2">
  <p class="text-muted-foreground text-start text-xs sm:text-sm">
    Enter a 10-digit number
  </p>
  <div
    class="flex max-w-xl flex-wrap justify-start gap-2"
    data-kt-pin-input="true"
  >
    <input
      type="tel"
      maxlength="1"
      inputmode="numeric"
      autocomplete="off"
      placeholder="○"
      aria-label="Digit 1 of 10"
      class="kt-input size-9 shrink-0 px-0 text-center text-sm sm:size-10"
      data-kt-pin-input-item="true"
    /><input
      type="tel"
      maxlength="1"
      inputmode="numeric"
      autocomplete="off"
      placeholder="○"
      aria-label="Digit 2 of 10"
      class="kt-input size-9 shrink-0 px-0 text-center text-sm sm:size-10"
      data-kt-pin-input-item="true"
    /><input
      type="tel"
      maxlength="1"
      inputmode="numeric"
      autocomplete="off"
      placeholder="○"
      aria-label="Digit 3 of 10"
      class="kt-input size-9 shrink-0 px-0 text-center text-sm sm:size-10"
      data-kt-pin-input-item="true"
    /><input
      type="tel"
      maxlength="1"
      inputmode="numeric"
      autocomplete="off"
      placeholder="○"
      aria-label="Digit 4 of 10"
      class="kt-input size-9 shrink-0 px-0 text-center text-sm sm:size-10"
      data-kt-pin-input-item="true"
    /><input
      type="tel"
      maxlength="1"
      inputmode="numeric"
      autocomplete="off"
      placeholder="○"
      aria-label="Digit 5 of 10"
      class="kt-input size-9 shrink-0 px-0 text-center text-sm sm:size-10"
      data-kt-pin-input-item="true"
    /><input
      type="tel"
      maxlength="1"
      inputmode="numeric"
      autocomplete="off"
      placeholder="○"
      aria-label="Digit 6 of 10"
      class="kt-input size-9 shrink-0 px-0 text-center text-sm sm:size-10"
      data-kt-pin-input-item="true"
    /><input
      type="tel"
      maxlength="1"
      inputmode="numeric"
      autocomplete="off"
      placeholder="○"
      aria-label="Digit 7 of 10"
      class="kt-input size-9 shrink-0 px-0 text-center text-sm sm:size-10"
      data-kt-pin-input-item="true"
    /><input
      type="tel"
      maxlength="1"
      inputmode="numeric"
      autocomplete="off"
      placeholder="○"
      aria-label="Digit 8 of 10"
      class="kt-input size-9 shrink-0 px-0 text-center text-sm sm:size-10"
      data-kt-pin-input-item="true"
    /><input
      type="tel"
      maxlength="1"
      inputmode="numeric"
      autocomplete="off"
      placeholder="○"
      aria-label="Digit 9 of 10"
      class="kt-input size-9 shrink-0 px-0 text-center text-sm sm:size-10"
      data-kt-pin-input-item="true"
    /><input
      type="tel"
      maxlength="1"
      inputmode="numeric"
      autocomplete="off"
      placeholder="○"
      aria-label="Digit 10 of 10"
      class="kt-input size-9 shrink-0 px-0 text-center text-sm sm:size-10"
      data-kt-pin-input-item="true"
    />
  </div>
</div>

Alphanumeric (10 characters)

Ten cells with data-kt-pin-input-available-chars="[0-9a-zA-Z]" and inputMode="text" for mixed codes (e.g. product keys). Letters are normalized to uppercase in this demo via CSS.

<div
  class="flex max-w-2xl flex-wrap justify-start gap-2"
  data-kt-pin-input="true"
  data-kt-pin-input-available-chars="[0-9a-zA-Z]"
>
  <input
    type="text"
    maxlength="1"
    inputmode="text"
    placeholder="○"
    aria-label="Character 1 of 10"
    class="kt-input size-9 shrink-0 px-0 text-center text-sm uppercase sm:size-10"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="text"
    placeholder="○"
    aria-label="Character 2 of 10"
    class="kt-input size-9 shrink-0 px-0 text-center text-sm uppercase sm:size-10"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="text"
    placeholder="○"
    aria-label="Character 3 of 10"
    class="kt-input size-9 shrink-0 px-0 text-center text-sm uppercase sm:size-10"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="text"
    placeholder="○"
    aria-label="Character 4 of 10"
    class="kt-input size-9 shrink-0 px-0 text-center text-sm uppercase sm:size-10"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="text"
    placeholder="○"
    aria-label="Character 5 of 10"
    class="kt-input size-9 shrink-0 px-0 text-center text-sm uppercase sm:size-10"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="text"
    placeholder="○"
    aria-label="Character 6 of 10"
    class="kt-input size-9 shrink-0 px-0 text-center text-sm uppercase sm:size-10"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="text"
    placeholder="○"
    aria-label="Character 7 of 10"
    class="kt-input size-9 shrink-0 px-0 text-center text-sm uppercase sm:size-10"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="text"
    placeholder="○"
    aria-label="Character 8 of 10"
    class="kt-input size-9 shrink-0 px-0 text-center text-sm uppercase sm:size-10"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="text"
    placeholder="○"
    aria-label="Character 9 of 10"
    class="kt-input size-9 shrink-0 px-0 text-center text-sm uppercase sm:size-10"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="text"
    placeholder="○"
    aria-label="Character 10 of 10"
    class="kt-input size-9 shrink-0 px-0 text-center text-sm uppercase sm:size-10"
    data-kt-pin-input-item="true"
  />
</div>

Masked (type="password")

<div class="flex max-w-md gap-2" data-kt-pin-input="true">
  <input
    type="password"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="password"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="password"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="password"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  />
</div>

iOS OTP hint

<div class="flex max-w-md gap-2" data-kt-pin-input="true">
  <input
    type="tel"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    autocomplete="one-time-code"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="tel"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    autocomplete="off"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="tel"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    autocomplete="off"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="tel"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    autocomplete="off"
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  />
</div>

Disabled

<div class="flex max-w-md gap-2" data-kt-pin-input="true">
  <input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    disabled=""
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    disabled=""
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    disabled=""
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  /><input
    type="text"
    maxlength="1"
    inputmode="numeric"
    placeholder="○"
    disabled=""
    class="kt-input size-10 shrink-0 px-0 text-center text-sm"
    data-kt-pin-input-item="true"
  />
</div>

Sizes

<div class="flex max-w-xl flex-col gap-5">
  <div class="flex gap-2" data-kt-pin-input="true">
    <input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-9 shrink-0 px-0 text-center text-xs"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-9 shrink-0 px-0 text-center text-xs"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-9 shrink-0 px-0 text-center text-xs"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-9 shrink-0 px-0 text-center text-xs"
      data-kt-pin-input-item="true"
    />
  </div>
  <div class="flex gap-2" data-kt-pin-input="true">
    <input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    />
  </div>
  <div class="flex gap-2" data-kt-pin-input="true">
    <input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-12 shrink-0 px-0 text-center text-base"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-12 shrink-0 px-0 text-center text-base"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-12 shrink-0 px-0 text-center text-base"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      placeholder="○"
      class="kt-input size-12 shrink-0 px-0 text-center text-base"
      data-kt-pin-input-item="true"
    />
  </div>
</div>

Hidden field (forms)

<form class="max-w-md space-y-3">
  <div class="flex gap-2" data-kt-pin-input="true" data-kt-pin-input-name="otp">
    <input
      type="text"
      maxlength="1"
      inputmode="numeric"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    /><input
      type="text"
      maxlength="1"
      inputmode="numeric"
      class="kt-input size-10 shrink-0 px-0 text-center text-sm"
      data-kt-pin-input-item="true"
    />
  </div>
  <p class="text-muted-foreground text-sm">
    A hidden field named <code class="text-foreground">otp</code> is kept in
    sync for native form posts.
  </p>
</form>

In modal

<div>
  <button
    type="button"
    class="kt-btn kt-btn-primary kt-btn-sm"
    data-kt-modal-toggle="#ktui-pin-modal"
  >
    Open verification
  </button>
  <div class="kt-modal" data-kt-modal="true" id="ktui-pin-modal">
    <div class="kt-modal-content max-w-[400px] top-[10%]">
      <div class="kt-modal-header">
        <h3 class="kt-modal-title">Enter code</h3>
        <button
          type="button"
          class="kt-modal-close"
          aria-label="Close modal"
          data-kt-modal-dismiss="#ktui-pin-modal"
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="lucide lucide-x"
            aria-hidden="true"
          >
            <path d="M18 6 6 18"></path>
            <path d="m6 6 12 12"></path>
          </svg>
        </button>
      </div>
      <div class="kt-modal-body flex justify-center py-2">
        <div class="flex gap-2" data-kt-pin-input="true">
          <input
            type="text"
            maxlength="1"
            inputmode="numeric"
            placeholder="○"
            class="kt-input size-11 shrink-0 px-0 text-center text-sm"
            data-kt-pin-input-item="true"
          /><input
            type="text"
            maxlength="1"
            inputmode="numeric"
            placeholder="○"
            class="kt-input size-11 shrink-0 px-0 text-center text-sm"
            data-kt-pin-input-item="true"
          /><input
            type="text"
            maxlength="1"
            inputmode="numeric"
            placeholder="○"
            class="kt-input size-11 shrink-0 px-0 text-center text-sm"
            data-kt-pin-input-item="true"
          /><input
            type="text"
            maxlength="1"
            inputmode="numeric"
            placeholder="○"
            class="kt-input size-11 shrink-0 px-0 text-center text-sm"
            data-kt-pin-input-item="true"
          />
        </div>
      </div>
      <div class="kt-modal-footer">
        <button
          type="button"
          class="kt-btn kt-btn-outline"
          data-kt-modal-dismiss="#ktui-pin-modal"
        >
          Close
        </button>
      </div>
    </div>
  </div>
</div>

Keyboard and mobile keypads

KtUI does not draw a custom keyboard; behavior comes from native <input> hints plus the component’s focus and filtering logic.

HintEffect
inputMode="numeric"Mobile: numeric keypad (digits). Desktop: standard keyboard; the component enforces allowed characters.
inputMode="text"Standard keyboard for alphanumeric patterns.
type="tel"Use with inputMode="numeric" for phone numbers; signals a telephony field to the platform.

Paste: Long strings are split and filtered; only characters matching the active pattern fill successive enabled cells.

Physical keyboard: Arrow keys move between cells; Home / End jump to the first / last enabled cell; Backspace clears or moves backward when empty.

Usage

Put data-kt-pin-input on a wrapper and mark each cell with data-kt-pin-input-item. Cells are native <input> elements (type="text", tel, or password as needed). The default allowed character class is digits ([0-9]); override with data-kt-pin-input-available-chars using a RegExp body that works with new RegExp(pattern).test(char) for each typed or pasted character.

<div data-kt-pin-input>
  <input type="text" inputmode="numeric" data-kt-pin-input-item />
  <input type="text" inputmode="numeric" data-kt-pin-input-item />
  <input type="text" inputmode="numeric" data-kt-pin-input-item />
  <input type="text" inputmode="numeric" data-kt-pin-input-item />
</div>

Component API

Options

NameTypeDefaultDescription
data-kt-pin-inputflag—Enables auto-init on the root wrapper.
data-kt-pin-input-itemflag—

Disabled cells are skipped for focus navigation and paste distribution. Set maxlength="1" on cells if you want a hard browser cap (the component sets 1 when missing).

Methods

MethodDescription
KTPinInput.init()Initializes all non-lazy roots in the document.
KTPinInput.createInstances()Initializes newly inserted roots.
KTPinInput.getInstance(root)Returns the instance or null.
import {
  KTPinInput,
  type KTPinInputConfigInterface,
  type KTPinInputInterface,
} from '@keenthemes/ktui';
 
const el = document.querySelector<HTMLElement>('[data-kt-pin-input]');
if (!el) throw new Error('Missing pin root');
 
const pin: KTPinInputInterface | null = KTPinInput.getOrCreateInstance(el, {
  availableChars: '[0-9]',
} satisfies KTPinInputConfigInterface);
 
pin?.setValue('');
console.log(pin?.

Events

Events bubble from the root element. Payload is in event.detail.payload.

EventDescription
kt.pin-input.inputFired when the combined value changes.
kt.pin-input.changeSame timing as input in v1 (mirrors common “value updated” hooks).
kt.pin-input.completeFired once when all enabled cells become non-empty (not repeated until incomplete again).

Payload fields:

FieldDescription
valueFull string from cells.
completetrue when every enabled cell has a character.
cellCountNumber of enabled cells.
const root = document.querySelector('[data-kt-pin-input]');
 
root?.addEventListener('kt.pin-input.complete', (e) => {
  console.log('OTP complete:', e.detail.payload.value);
});

Accessibility

  • Keep visible labels or aria-label / aria-labelledby on the group; consider role="group" with a single label for the set (markup depends on your form layout).
  • For SMS OTP on iOS, use autocomplete="one-time-code" on the first cell and type="tel" with inputmode="numeric" (see iOS OTP example).
  • Arrow keys move between cells; Home / End jump to first / last enabled cell.
PreviousePaginationNextProgress

On This Page

  • Examples
    • Basic
    • Placeholder
    • Gray variant
    • Underline variant
    • Focus scale
    • Lengths (3, 5, 7)
    • Regex subset (digits 0–3)
    • Telephone number
    • Alphanumeric (10 characters)
    • Masked (type="password")
    • iOS OTP hint
    • Disabled
    • Sizes
    • Hidden field (forms)
    • In modal
  • Keyboard and mobile keypads
  • Usage
  • Component API
    • Options
    • Methods
    • Events
    • Accessibility
type="password"Masks characters; filtering follows data-kt-pin-input-available-chars.
Marks each input cell (document order).
data-kt-pin-input-lazybooleanfalseSkip auto-init; use getOrCreateInstance / new KTPinInput(...).
data-kt-pin-input-available-charsstring[0-9]RegExp pattern tested per character (e.g. [0-9a-fA-F], [0-3]).
data-kt-pin-input-namestring—If set, creates or reuses a hidden input[type="hidden"] with this name and keeps it synced with the combined value.
KTPinInput.getOrCreateInstance(root, config?)
Returns or creates an instance.
getValue()Concatenated string for all cells (disabled cells contribute empty slots).
setValue(value)Fills enabled cells from the string after filtering; clears remaining cells.
dispose()Removes listeners and clears the instance from the root.
getElement()Root wrapper element.
getOption / on / offSame patterns as other KtUI components.
getValue
());
filledCount
Enabled cells with a value.
cellIndexOptional index of the cell that triggered the update.