Components
Pin input

Pin Input

For pin or verification codes with auto-focus transfer and masking options.

Usage

import { PinInput, type PinInputProps } from '~/components/ui/pin-input'

export const Demo = (props: PinInputProps) => {
  return (
    <PinInput placeholder="0" onValueComplete={(e) => alert(e.valueAsString)} {...props}>
      Pin Input
    </PinInput>
  )
}

Installation

1

Add Component

Insert code snippet into your project. Update import paths as needed.

import { PinInput as ArkPinInput, type PinInputRootProps } from '@ark-ui/react/pin-input'
import { type ReactNode, forwardRef } from 'react'
import { css, cx } from 'styled-system/css'
import { splitCssProps } from 'styled-system/jsx'
import { type PinInputVariantProps, pinInput } from 'styled-system/recipes'
import type { Assign, JsxStyleProps } from 'styled-system/types'
import { Input } from '~/components/ui/input'

export interface PinInputProps
  extends Assign<JsxStyleProps, PinInputRootProps>,
    PinInputVariantProps {
  children?: ReactNode
  /**
   * The number of inputs to render.
   * @default 4
   */
  length?: number
}

export const PinInput = forwardRef<HTMLDivElement, PinInputProps>((props, ref) => {
  const [variantProps, pinInputProps] = pinInput.splitVariantProps(props)
  const [cssProps, localProps] = splitCssProps(pinInputProps)
  const { children, className, length = 4, ...rootProps } = localProps
  const styles = pinInput(variantProps)

  return (
    <ArkPinInput.Root
      className={cx(styles.root, css(cssProps), className)}
      ref={ref}
      {...rootProps}
    >
      {children && <ArkPinInput.Label className={styles.label}>{children}</ArkPinInput.Label>}
      <ArkPinInput.Control className={styles.control}>
        {Array.from({ length }, (_, index) => index).map((id, index) => (
          <ArkPinInput.Input className={styles.input} key={id} index={index} asChild>
            <Input size={variantProps.size} />
          </ArkPinInput.Input>
        ))}
      </ArkPinInput.Control>
    </ArkPinInput.Root>
  )
})

PinInput.displayName = 'PinInput'
2

Add Recipe

This step is necessary only if you do not use any of the Park UI plugins.

import { pinInputAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'

export const pinInput = defineSlotRecipe({
  className: 'pinInput',
  slots: pinInputAnatomy.keys(),
  base: {
    root: {
      display: 'flex',
      flexDirection: 'column',
      gap: '1.5',
    },
    control: {
      display: 'flex',
      gap: '2',
    },
    label: {
      color: 'fg.default',
      fontWeight: 'medium',
    },
    input: {
      px: '0!',
      textAlign: 'center',
    },
  },
  defaultVariants: {
    size: 'md',
  },
  variants: {
    size: {
      xs: {
        label: {
          textStyle: 'sm',
        },
        input: {
          width: '8',
        },
      },
      sm: {
        label: {
          textStyle: 'sm',
        },
        input: {
          width: '9',
        },
      },
      md: {
        label: {
          textStyle: 'sm',
        },
        input: {
          width: '10',
        },
      },
      lg: {
        label: {
          textStyle: 'sm',
        },
        input: {
          width: '11',
        },
      },
      xl: {
        label: {
          textStyle: 'md',
        },
        input: {
          width: '12',
        },
      },
      '2xl': {
        label: {
          textStyle: 'md',
        },
        input: {
          width: '16',
        },
      },
    },
  },
})

On this page