Pin Input
For pin or verification codes with auto-focus transfer and masking options.
Anatomy
To set up the pin input correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
Examples
Learn how to use the PinInput
component in your project. Let's take a look at the most basic
example:
import { PinInput } from '@ark-ui/react/pin-input'
export const Basic = () => (
<PinInput.Root onValueComplete={(e) => alert(e.valueAsString)}>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
{[0, 1, 2].map((id, index) => (
<PinInput.Input key={id} index={index} />
))}
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
import { PinInput } from '@ark-ui/solid/pin-input'
import { Index } from 'solid-js'
export const Basic = () => (
<PinInput.Root onValueComplete={(e) => alert(e.valueAsString)}>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<Index each={[0, 1, 2]}>{(id) => <PinInput.Input index={id()} />}</Index>
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
<script setup lang="ts">
import { PinInput } from '@ark-ui/vue/pin-input'
</script>
<template>
<PinInput.Root>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<PinInput.Input v-for="id in [0, 1, 2]" :key="id" :index="id" />
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
</template>
Setting a default value
To set the initial value of the pin input, set the defaultValue
prop.
import { PinInput } from '@ark-ui/react/pin-input'
export const InitialValue = () => (
<PinInput.Root defaultValue={['1', '2', '3']}>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
{[0, 1, 2].map((id, index) => (
<PinInput.Input key={id} index={index} />
))}
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
import { PinInput } from '@ark-ui/solid/pin-input'
import { Index } from 'solid-js'
export const InitialValue = () => (
<PinInput.Root value={['1', '2', '3']}>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<Index each={[0, 1, 2]}>{(id) => <PinInput.Input index={id()} />}</Index>
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
<script setup lang="ts">
import { PinInput } from '@ark-ui/vue/pin-input'
</script>
<template>
<PinInput.Root :model-value="['1', '2', '3']">
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<PinInput.Input v-for="id in [0, 1, 2]" :key="id" :index="id" />
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
</template>
Changing the placeholder
To customize the default pin input placeholder ○
for each input, pass the placeholder prop and set
it to your desired value.
import { PinInput } from '@ark-ui/react/pin-input'
export const Customized = () => (
<PinInput.Root placeholder="*">
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
{[0, 1, 2].map((id, index) => (
<PinInput.Input key={id} index={index} />
))}
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
import { PinInput } from '@ark-ui/solid/pin-input'
import { Index } from 'solid-js'
export const Customized = () => (
<PinInput.Root placeholder="*">
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<Index each={[0, 1, 2]}>{(id) => <PinInput.Input index={id()} />}</Index>
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
<script setup lang="ts">
import { PinInput } from '@ark-ui/vue/pin-input'
</script>
<template>
<PinInput.Root placeholder="*">
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<PinInput.Input v-for="id in [0, 1, 2]" :key="id" :index="id" />
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
</template>
Blur on complete
By default, the last input maintains focus when filled, and we invoke the onValueComplete
callback. To blur the last input when the user completes the input, set the prop blurOnComplete
to
true
.
import { PinInput } from '@ark-ui/react/pin-input'
export const Blurred = () => (
<PinInput.Root blurOnComplete>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
{[0, 1, 2].map((id, index) => (
<PinInput.Input key={id} index={index} />
))}
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
import { PinInput } from '@ark-ui/solid/pin-input'
import { Index } from 'solid-js'
export const Blurred = () => (
<PinInput.Root blurOnComplete>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<Index each={[0, 1, 2]}>{(id) => <PinInput.Input index={id()} />}</Index>
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
<script setup lang="ts">
import { PinInput } from '@ark-ui/vue/pin-input'
</script>
<template>
<PinInput.Root blurOnComplete>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<PinInput.Input v-for="id in [0, 1, 2]" :key="id" :index="id" />
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
</template>
Using OTP mode
To trigger smartphone OTP auto-suggestion, it is recommended to set the autocomplete
attribute to
"one-time-code". The pin input component provides support for this automatically when you set the
otp
prop to true.
import { PinInput } from '@ark-ui/react/pin-input'
export const OTPMode = () => (
<PinInput.Root otp>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
{[0, 1, 2].map((id, index) => (
<PinInput.Input key={id} index={index} />
))}
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
import { PinInput } from '@ark-ui/solid/pin-input'
import { Index } from 'solid-js'
export const OTPMode = () => (
<PinInput.Root otp>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<Index each={[0, 1, 2]}>{(id) => <PinInput.Input index={id()} />}</Index>
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
<script setup lang="ts">
import { PinInput } from '@ark-ui/vue/pin-input'
</script>
<template>
<PinInput.Root otp>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<PinInput.Input v-for="id in [0, 1, 2]" :key="id" :index="id" />
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
</template>
Securing the text input
When collecting private or sensitive information using the pin input, you might need to mask the
value entered, similar to <input type="password"/>
. Pass the mask
prop to true
.
import { PinInput } from '@ark-ui/react/pin-input'
export const WithMask = () => (
<PinInput.Root mask>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
{[0, 1, 2].map((id, index) => (
<PinInput.Input key={id} index={index} />
))}
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
import { PinInput } from '@ark-ui/solid/pin-input'
import { Index } from 'solid-js'
export const WithMask = () => (
<PinInput.Root mask>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<Index each={[0, 1, 2]}>{(id) => <PinInput.Input index={id()} />}</Index>
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
)
<script setup lang="ts">
import { PinInput } from '@ark-ui/vue/pin-input'
</script>
<template>
<PinInput.Root mask>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<PinInput.Input v-for="id in [0, 1, 2]" :key="id" :index="id" />
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
</template>
Listening for changes
The pin input component invokes several callback functions when the user enters:
onValueChange
— Callback invoked when the value is changed.onValueComplete
— Callback invoked when all fields have been completed (by typing or pasting).onValueInvalid
— Callback invoked when an invalid value is entered into the input. An invalid value is any value that doesn't match the specified "type".
Using the Field Component
The Field
component helps manage form-related state and accessibility attributes of a pin input.
It includes handling ARIA labels, helper text, and error text to ensure proper accessibility.
import { Field } from '@ark-ui/react/field'
import { PinInput } from '@ark-ui/react/pin-input'
export const WithField = (props: Field.RootProps) => (
<Field.Root {...props}>
<PinInput.Root>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
{[0, 1, 2].map((id, index) => (
<PinInput.Input key={id} index={index} />
))}
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
import { Field } from '@ark-ui/solid/field'
import { PinInput } from '@ark-ui/solid/pin-input'
import { Index } from 'solid-js'
export const WithField = (props: Field.RootProps) => (
<Field.Root {...props}>
<PinInput.Root>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<Index each={[0, 1, 2]}>{(id) => <PinInput.Input index={id()} />}</Index>
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
<script setup lang="ts">
import { Field } from '@ark-ui/vue/field'
import { PinInput } from '@ark-ui/vue/pin-input'
</script>
<template>
<Field.Root>
<PinInput.Root>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<PinInput.Input v-for="id in [0, 1, 2]" :key="id" :index="id" />
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
</template>
Using the Root Provider
The RootProvider
component provides a context for the pin-input. It accepts the value of the usePin-input
hook.
You can leverage it to access the component state and methods from outside the pin-input.
import { PinInput, usePinInput } from '@ark-ui/react/pin-input'
export const RootProvider = () => {
const pinInput = usePinInput({ onValueComplete: (e) => alert(e.valueAsString) })
return (
<>
<button onClick={() => pinInput.focus()}>Focus</button>
<PinInput.RootProvider value={pinInput}>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
{[0, 1, 2].map((id, index) => (
<PinInput.Input key={id} index={index} />
))}
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.RootProvider>
</>
)
}
import { PinInput, usePinInput } from '@ark-ui/solid/pin-input'
import { Index } from 'solid-js'
export const RootProvider = () => {
const pinInput = usePinInput({ onValueComplete: (e) => alert(e.valueAsString) })
return (
<>
<button onClick={() => pinInput().focus()}>Focus</button>
<PinInput.RootProvider value={pinInput}>
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<Index each={[0, 1, 2]}>{(id) => <PinInput.Input index={id()} />}</Index>
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.RootProvider>
</>
)
}
<script setup lang="ts">
import { PinInput, usePinInput } from '@ark-ui/vue/pin-input'
const pinInput = usePinInput()
</script>
<template>
<button @click="pinInput.focus()">Focus</button>
<PinInput.RootProvider :value="pinInput">
<PinInput.Label>Label</PinInput.Label>
<PinInput.Control>
<PinInput.Input v-for="id in [0, 1, 2]" :key="id" :index="id" />
</PinInput.Control>
<PinInput.HiddenInput />
</PinInput.RootProvider>
</template>
If you're using the
RootProvider
component, you don't need to use theRoot
component.
API Reference
Root
Prop | Default | Type |
---|---|---|
asChild | (props: ParentProps<'div'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
autoFocus | boolean Whether to auto-focus the first input. | |
blurOnComplete | boolean Whether to blur the input when the value is complete | |
defaultValue | string[] The initial value of the pin input when it is first rendered. Use when you do not need to control the state of the pin input | |
disabled | boolean Whether the inputs are disabled | |
form | string The associate form of the underlying input element. | |
ids | Partial<{
root: string
hiddenInput: string
label: string
control: string
input(id: string): string
}> The ids of the elements in the pin input. Useful for composition. | |
invalid | boolean Whether the pin input is in the invalid state | |
mask | boolean If `true`, the input's value will be masked just like `type=password` | |
name | string The name of the input element. Useful for form submission. | |
onValueChange | (details: ValueChangeDetails) => void Function called on input change | |
onValueComplete | (details: ValueChangeDetails) => void Function called when all inputs have valid values | |
onValueInvalid | (details: ValueInvalidDetails) => void Function called when an invalid value is entered | |
otp | boolean If `true`, the pin input component signals to its fields that they should use `autocomplete="one-time-code"`. | |
pattern | string The regular expression that the user-entered input value is checked against. | |
placeholder | '○' | string The placeholder text for the input |
readOnly | boolean Whether the pin input is in the valid state | |
required | boolean Whether the pin input is required | |
selectOnFocus | boolean Whether to select input value when input is focused | |
translations | IntlTranslations Specifies the localized strings that identifies the accessibility elements and their states | |
type | 'numeric' | 'numeric' | 'alphabetic' | 'alphanumeric' The type of value the pin-input should allow |
value | string[] The value of the the pin input. |
Data Attribute | Value |
---|---|
[data-scope] | pin-input |
[data-part] | root |
[data-invalid] | Present when invalid |
[data-disabled] | Present when disabled |
[data-complete] | Present when the pin-input value is complete |
[data-readonly] | Present when read-only |
Control
Prop | Default | Type |
---|---|---|
asChild | (props: ParentProps<'div'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
HiddenInput
Prop | Default | Type |
---|---|---|
asChild | (props: ParentProps<'input'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Input
Prop | Default | Type |
---|---|---|
index | number | |
asChild | (props: ParentProps<'input'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | pin-input |
[data-part] | input |
[data-disabled] | Present when disabled |
[data-complete] | Present when the input value is complete |
[data-invalid] | Present when invalid |
Label
Prop | Default | Type |
---|---|---|
asChild | (props: ParentProps<'label'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | pin-input |
[data-part] | label |
[data-invalid] | Present when invalid |
[data-disabled] | Present when disabled |
[data-complete] | Present when the label value is complete |
[data-readonly] | Present when read-only |
RootProvider
Prop | Default | Type |
---|---|---|
value | UsePinInputReturn | |
asChild | (props: ParentProps<'div'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Accessibility
Keyboard Support
Key | Description |
---|---|
ArrowLeft | Moves focus to the previous input |
ArrowRight | Moves focus to the next input |
Backspace | Deletes the value in the current input and moves focus to the previous input |
Delete | Deletes the value in the current input |
Control + V | Pastes the value into the input fields |