Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelog App ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip
ARC UI ARC Radiant Components
v4.2 Docs Components Tokens Synthesizer
Getting StartedFrameworksServer Rendering Design TokensThemingTheme SynthesizerTypographyUtilities All ComponentsAccessibilityBrowser SupportChangelogContributingStats App ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip

Pin Input

One-character-per-box input for PINs, OTPs, and verification codes with auto-advance, paste support, and optional masking.

Components Pin Input
input interactive
<arc-pin-input>

Overview

PinInput renders a row of individual input boxes — one per character — designed for entering PINs, one-time passwords, and verification codes. Each box accepts a single character and automatically advances focus to the next box on entry, creating a fast and fluid typing experience. The component supports backspace navigation (moving back to the previous box when the current one is empty), arrow key movement between boxes, and full clipboard paste that fills multiple boxes at once. The `type` prop controls character validation: `"number"` restricts input to digits 0-9, `"alphanumeric"` allows letters and digits, and `"text"` accepts any single character. When `mask` is enabled, entered characters are obscured with dots (using CSS `-webkit-text-security: disc`) for sensitive codes. An optional `separator` prop inserts a visual dash between groups of boxes — for example, setting `separator="3"` on a 6-digit code renders it as three boxes, a dash, and three more boxes. PinInput fires `arc-input` on every character entry or deletion, providing the current partial value. When all boxes are filled it fires `arc-change` — the commit for a fixed-length code — along with `arc-complete`, the more specific name kept for consumers that auto-submit. Either one makes it easy to trigger form submission or validation at the right moment without polling or length-checking.

Guidelines

When to use

  • Set `length` to match the expected code length — 4 for PINs, 6 for OTPs, etc.
  • Use `type="number"` for numeric-only codes and set `inputmode="numeric"` for mobile keyboards
  • Enable `mask` for sensitive codes like passwords or security PINs
  • Listen for `arc-complete` to auto-submit or validate once the full code is entered
  • Provide a `label` so users understand what code they are entering

When not to use

  • Do not use PinInput for general text entry — it is designed exclusively for fixed-length codes
  • Do not set `length` higher than ~8 — long codes are better handled with a standard text input
  • Do not omit the `label` prop when the pin input is used standalone without surrounding context
  • Do not use `separator` values that produce uneven groups at the end (e.g. `separator="4"` on a 6-digit code)
  • Avoid placing PinInput in very narrow containers — each box needs at least 42px width plus gaps

Features

  • Auto-advance focus to the next box after each valid character entry
  • Backspace navigates to and clears the previous box when the current box is empty
  • Arrow key navigation between boxes without modifying content
  • Clipboard paste support that fills multiple boxes from the cursor position
  • Configurable `type` validation: `"number"`, `"alphanumeric"`, or `"text"`
  • Mask mode via `mask` prop for obscuring sensitive codes with dots
  • Visual separator dashes between groups via the `separator` prop (e.g. every 3 boxes)
  • Split events: `arc-input` on every keystroke, `arc-change` and `arc-complete` when all boxes are filled

Preview

Usage

This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.

<arc-pin-input label="OTP Code" length="6" separator="3"></arc-pin-input>

<script>
  document.querySelector('arc-pin-input')
    .addEventListener('arc-complete', e => {
      console.log('Code entered:', e.detail.value);
    });
</script>
import { PinInput } from '@arclux/arc-ui-react';

export default function Example() {
  return (
    <PinInput
      label="OTP Code"
      length={6}
      separator={3}
      onArcComplete={(e) => console.log('Code:', e.detail.value)}
    />
  );
}
<script setup>
import { PinInput } from '@arclux/arc-ui-vue';
</script>

<template>
  <PinInput
    label="OTP Code"
    :length="6"
    :separator="3"
    @arc-complete="(e) => console.log('Code:', e.detail.value)"
  />
</template>
<script>
  import { PinInput } from '@arclux/arc-ui-svelte';
</script>

<PinInput
  label="OTP Code"
  length={6}
  separator={3}
  on:arc-complete={(e) => console.log('Code:', e.detail.value)}
/>
import { Component } from '@angular/core';
import { PinInput } from '@arclux/arc-ui-angular';

@Component({
  imports: [PinInput],
  template: `
    <arc-pin-input
      label="OTP Code"
      [length]="6"
      [separator]="3"
      (arc-complete)="onComplete($event)"
    ></arc-pin-input>
  `,
})
export class MyComponent {
  onComplete(e: CustomEvent) {
    console.log('Code:', e.detail.value);
  }
}
import { PinInput } from '@arclux/arc-ui-solid';

export default function Example() {
  return (
    <PinInput
      label="OTP Code"
      length={6}
      separator={3}
      onArcComplete={(e) => console.log('Code:', e.detail.value)}
    />
  );
}
import { PinInput } from '@arclux/arc-ui-preact';

export default function Example() {
  return (
    <PinInput
      label="OTP Code"
      length={6}
      separator={3}
      onArcComplete={(e) => console.log('Code:', e.detail.value)}
    />
  );
}

API

value string ''
Current combined value across all boxes. Reflected as an attribute.
name string ''
disabled boolean false
Disables all boxes, reducing opacity to 40% and blocking input.
separator number 0
Inserts a visual dash separator every N boxes. Set to 0 to disable separators.
label string ''
Label text displayed above the input boxes in uppercase accent font.
length number 4
Number of input boxes to render. Determines the expected code length.
type 'number' | 'alphanumeric' | 'text' 'number'
Character validation mode. number allows digits only, alphanumeric allows letters and digits, text allows any character.
mask boolean false
When true, obscures entered characters with dots for sensitive codes.
readonly boolean false
Prevents entering, deleting, or pasting characters while the boxes stay focusable and the value still submits.
size 'sm' | 'md' | 'lg' 'md'
Control size. md is the default; sm and lg scale the digit boxes.
formAssociated boolean true
properties object { // flag(), unlike `disabled`. The exclusion in props.js is specifically // about form-associated *platform* semantics: a `disabled` content // attribute that is merely present makes the element actually disabled // per the HTML spec, and formDisabledCallback assigns the property back, // so no converter can win. Neither of these is platform-mapped — // `required` is enforced by _computeValidity() below and `readonly` by // each component's own interaction handlers — so the stock converter buys // nothing here and costs the usual bug: `required="false"` read as true, // blocking submission of a form the author meant to leave optional. // Finding #48's shape, across all 26 form controls at once. required: flag(false), readonly: flag(false), }
Lit merges static properties up the prototype chain, so every consumer gets these without declaring them. required participates in constraint validation below; readonly reflects for styling and is enforced by each component's interaction handlers (the mixin can't know which gestures mutate state).
autoValidates boolean true
Components that run their own constraint-validation logic (pattern checks, range checks) opt out of the automatic required sync by overriding this to false, and own the whole validity flag set instead.
form
validity
validationMessage
required boolean false

Methods

checkValidity() boolean
Whether the control currently satisfies its constraints, per the native constraint-validation API. Fires invalid on the element when it does not, and reports nothing to the user.
reportValidity() boolean
As checkValidity(), but also shows the browser's validation message against the control when it fails.

Events

arc-input
Fired on every character entry or deletion. event.detail.value contains the current partial value.
arc-change
Fired when the pin is complete — every box filled. That is the commit for a fixed-length value.
arc-complete
Fired alongside arc-change when all boxes are filled. The more specific name, kept for consumers that auto-submit.

See Also