Skip to main content
Urbicon UI

PinInput

A row of single-character cells for entering a short code like a 2FA one-time code or a PIN, with auto-advance, paste-to-fill, and optional masking.

Playground

Length
Type
Size Style variant
Variant Style variant
Tier Style variant
Intent Style variant
<PinInput />

01 Examples

Six-digit OTP with a completion callback

bind:value keeps the concatenated string in sync; onComplete fires each time the row becomes complete — so a corrected code fires it again, which is the normal case for a mistyped one.
One-time code
<script>
  import { PinInput } from '@urbicon-ui/blocks';
  let code = $state('');
  let status = $state('');
</script>

<PinInput
  label="One-time code"
  length={6}
  bind:value={code}
  onComplete={(v) => (status = 'Verifying ' + v + '')}
/>
{#if status}
  <p>{status}</p>
{/if}

Alphanumeric with a grouped separator

type=alphanumeric accepts letters as well as digits, in either case; uppercase normalises them as you type; separator + groupSize break a long code into readable groups, here a 4-4 license key.
License key
<script>
  import { PinInput } from '@urbicon-ui/blocks';
  let licenseKey = $state('');
</script>

<PinInput
  label="License key"
  length={8}
  size="sm"
  type="alphanumeric"
  uppercase
  separator="-"
  groupSize={4}
  bind:value={licenseKey}
/>

Error state

Passing error colours every cell danger, sets aria-invalid, and shows the message via role=alert, overriding any helper text.
Security code
<PinInput label="Security code" length={6} value="12" error="Incorrect code" />

02 Two-factor / OTP

PinInput fits the one-time-code step of a two-factor flow. The first cell carries autocomplete="one-time-code", so iOS offers the code from an incoming SMS as a keyboard suggestion. Send the bound value from onComplete to your verify endpoint — with the auth package, that is createTwoFactorHandlers's verify group behind POST /api/auth/2fa/verify.

An autofilled code lands in the first cell and is distributed across the row, exactly like a paste. Give it a visible label and a helper line so the source of the code (authenticator app vs. SMS) is never ambiguous.

Authenticator verification field

A labelled, six-digit field with helper text for the second login step.
Verification code
Enter the 6-digit code from your authenticator app.
<PinInput
  label="Verification code"
  helper="Enter the 6-digit code from your authenticator app."
  length={6}
  bind:value={code}
  onComplete={(v) => verifyTwoFactor(v)}
/>

03 Customization

Every visible part is a named slot: root (what class also targets), label, group (the cell row), cell, separator, and message. Pass slotClasses to merge classes onto any of them, or unstyled to drop every default class and rebuild from scratch. For a look you reuse across the app, register a preset once on <BlocksProvider> and reference it by name instead of repeating overrides.

Terminal-style cells via slotClasses

Rounded, monospaced cells in a bigger type size, built from semantic tokens. The cell box keeps its own size — text-2xl grows the glyph, not the square.
Access code
<PinInput
  label="Access code"
  length={6}
  value="4711"
  slotClasses={{
    group: 'gap-3',
    cell: 'rounded-lg bg-surface-subtle border-border-default font-mono text-2xl text-primary'
  }}
/>

04 Accessibility

Group semantics

The cell row is a role="group", named by aria-labelledby when a visible label is set, or by aria-label otherwise.

Each cell announces its position

Each cell carries aria-label="Character N of M", so a screen-reader user always knows where the caret sits.

The error reaches every cell

aria-describedby is set on each cell individually, not just on the group — so the message is read wherever the caret sits, not only on entering the field.

Keyboard

Typing a valid character auto-advances to the next cell. Backspace clears the current cell, or — when that one is already empty — steps back and clears the previous one; Delete clears without moving. The arrow keys plus Home / End move between cells, and a paste is distributed across the cells from the caret.

05 API Reference

31 props
31 props
Prop
Type
Default
Description

06 Types

Local type definitions used by this component.

5 types
Name
Kind
Category
Used by
Description

07 Installation

Import

import { PinInput } from '@urbicon-ui/blocks';