Pin Input
For pin or verification codes with auto-focus transfer and masking options.
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>
  )
}
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>
  )
}
Usage
import { PinInput } from '~/components/ui/pin-input'Installation
npx @park-ui/cli components add pin-input1
Add Styled Primitive
Copy the code snippet below into ~/components/ui/styled/pin-input.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { PinInput } from '@ark-ui/react/pin-input'
import { type PinInputVariantProps, pinInput } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(pinInput)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
  HTMLDivElement,
  Assign<Assign<HTMLStyledProps<'div'>, PinInput.RootProviderBaseProps>, PinInputVariantProps>
>(PinInput.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
  HTMLDivElement,
  Assign<Assign<HTMLStyledProps<'div'>, PinInput.RootBaseProps>, PinInputVariantProps>
>(PinInput.Root, 'root', { forwardProps: ['mask'] })
export const Control = withContext<
  HTMLDivElement,
  Assign<HTMLStyledProps<'div'>, PinInput.ControlBaseProps>
>(PinInput.Control, 'control')
export const Input = withContext<
  HTMLInputElement,
  Assign<HTMLStyledProps<'input'>, PinInput.InputBaseProps>
>(PinInput.Input, 'input')
export const Label = withContext<
  HTMLLabelElement,
  Assign<HTMLStyledProps<'label'>, PinInput.LabelBaseProps>
>(PinInput.Label, 'label')
export {
  PinInputContext as Context,
  PinInputHiddenInput as HiddenInput,
} from '@ark-ui/react/pin-input'
import { type Assign, PinInput } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type PinInputVariantProps, pinInput } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(pinInput)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
  Assign<Assign<HTMLStyledProps<'div'>, PinInput.RootProviderBaseProps>, PinInputVariantProps>
>(PinInput.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
  Assign<Assign<HTMLStyledProps<'div'>, PinInput.RootBaseProps>, PinInputVariantProps>
>(PinInput.Root, 'root', { forwardProps: ['mask'] })
export const Control = withContext<Assign<HTMLStyledProps<'div'>, PinInput.ControlBaseProps>>(
  PinInput.Control,
  'control',
)
export const Input = withContext<Assign<HTMLStyledProps<'input'>, PinInput.InputBaseProps>>(
  PinInput.Input,
  'input',
)
export const Label = withContext<Assign<HTMLStyledProps<'label'>, PinInput.LabelBaseProps>>(
  PinInput.Label,
  'label',
)
export {
  PinInputContext as Context,
  PinInputHiddenInput as HiddenInput,
} from '@ark-ui/solid'
No snippet found2
Add Composition
Copy the composition snippet below into ~/components/ui/pin-input.tsx
import { forwardRef } from 'react'
import { Input } from './input'
import * as StyledPinInput from './styled/pin-input'
export interface PinInputProps extends StyledPinInput.RootProps {
  /**
   * The number of inputs to render.
   * @default 4
   */
  length?: number
}
export const PinInput = forwardRef<HTMLDivElement, PinInputProps>((props, ref) => {
  const { children, length = 4, ...rootProps } = props
  return (
    <StyledPinInput.Root ref={ref} {...rootProps}>
      {children && <StyledPinInput.Label>{children}</StyledPinInput.Label>}
      <StyledPinInput.Control>
        {Array.from({ length }, (_, index) => index).map((id, index) => (
          <StyledPinInput.Input key={id} index={index} asChild>
            <Input size={rootProps.size} />
          </StyledPinInput.Input>
        ))}
      </StyledPinInput.Control>
      <StyledPinInput.HiddenInput />
    </StyledPinInput.Root>
  )
})
PinInput.displayName = 'PinInput'
import { Index, Show, children } from 'solid-js'
import { Input } from './input'
import * as StyledPinInput from './styled/pin-input'
export interface PinInputProps extends StyledPinInput.RootProps {
  /**
   * The number of inputs to render.
   * @default 4
   */
  length?: number
}
export const PinInput = (props: PinInputProps) => {
  const getChildren = children(() => props.children)
  return (
    <StyledPinInput.Root {...props}>
      <Show when={getChildren()}>
        <StyledPinInput.Label>{getChildren()}</StyledPinInput.Label>
      </Show>
      <StyledPinInput.Control>
        <Index each={Array.from({ length: props.length ?? 4 }, (_, index) => index)}>
          {(index) => (
            <StyledPinInput.Input
              index={index()}
              asChild={(inputProps) => <Input {...inputProps()} size={props.size} />}
            />
          )}
        </Index>
      </StyledPinInput.Control>
      <StyledPinInput.HiddenInput />
    </StyledPinInput.Root>
  )
}
3
Integrate Recipe
If you're not using @park-ui/preset, add the following recipe to yourpanda.config.ts:
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',
        },
      },
    },
  },
})