PIN Input

Allows users to input a sequence of one-character alphanumeric inputs.

	<script lang="ts">
  import { PinInput, type PinInputRootSnippetProps } from "bits-ui";
  import { toast } from "svelte-sonner";
  import { cn } from "$lib/utils/styles.js";
 
  let value = $state("");
 
  type CellProps = PinInputRootSnippetProps["cells"][0];
 
  function onComplete() {
    toast.success(`Completed with value ${value}`);
    value = "";
  }
</script>
 
<PinInput.Root
  bind:value
  class="group/pininput flex items-center text-foreground has-[:disabled]:opacity-30"
  maxlength={6}
  {onComplete}
>
  {#snippet children({ cells })}
    <div class="flex">
      {#each cells.slice(0, 3) as cell}
        {@render Cell(cell)}
      {/each}
    </div>
 
    <div class="flex w-10 items-center justify-center">
      <div class="h-1 w-3 rounded-full bg-border"></div>
    </div>
 
    <div class="flex">
      {#each cells.slice(3, 6) as cell}
        {@render Cell(cell)}
      {/each}
    </div>
  {/snippet}
</PinInput.Root>
 
{#snippet Cell(cell: CellProps)}
  <PinInput.Cell
    {cell}
    class={cn(
      // Custom class to override global focus styles
      "focus-override",
      "relative h-14 w-10 text-[2rem]",
      "flex items-center justify-center",
      "transition-all duration-200",
      "border-y border-r border-foreground/20 first:rounded-l-md first:border-l last:rounded-r-md",
      "text-foreground group-focus-within/pininput:border-foreground/40 group-hover/pininput:border-foreground/40",
      "outline outline-0",
      "data-[active]:outline-1 data-[active]:outline-white"
    )}
  >
    {#if cell.char !== null}
      <div>
        {cell.char}
      </div>
    {/if}
    {#if cell.hasFakeCaret}
      <div
        class="pointer-events-none absolute inset-0 flex animate-caret-blink items-center justify-center"
      >
        <div class="h-8 w-px bg-white"></div>
      </div>
    {/if}
  </PinInput.Cell>
{/snippet}

This component is derived from and would not have been possible without the work done by Input OTP by Guilherme Rodz.

Structure

	<script lang="ts">
	import { PinInput } from "bits-ui";
</script>
 
<PinInput.Root maxlength={6}>
	{#snippet children({ cells })}
		{#each cells as cell}
			{cell.char !== null ? cell.char : ""}
		{/each}
	{/snippet}
</PinInput.Root>

API Reference

PINInput.Root

The pin input container component.

Property Type Description
value $bindable
string

The value of the input.

Default: undefined
onValueChange
function

A callback function that is called when the value of the input changes.

Default: undefined
disabled
boolean

Whether or not the pin input is disabled.

Default: false
textalign
enum

Where is the text located within the input. Affects click-holding or long-press behavior

Default: 'left'
maxlength
number

The maximum length of the pin input.

Default: 6
onComplete
function

A callback function that is called when the input is completely filled.

Default: undefined
inputId
string

Optionally provide an ID to apply to the hidden input element.

Default: undefined
pushPasswordManagerStrategy
enum

Enabled by default, it's an optional strategy for detecting Password Managers in the page and then shifting their badges to the right side, outside the input.

Default: undefined
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See delegation docs for more information.

Default: undefined
Data Attribute Value Description
data-pin-input-root
''

Present on the root element.

PINInput.Cell

A single cell of the pin input.

Property Type Description
cell
object

The cell object provided by the cells snippet prop from the PinInput.Root component.

Default: undefined
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See delegation docs for more information.

Default: undefined
Data Attribute Value Description
data-active
''

Present when the cell is active.

data-inactive
''

Present when the cell is inactive.

data-pin-input-cell
''

Present on the cell element.