PinInput
Captures a short sequence of characters with one input per position.
Value: —
vue
<script setup lang="ts">
import { ref } from 'vue'
import { PinInputInput, PinInputRoot } from '@typlog/ui'
const value = ref<string[]>([])
</script>
<template>
<div class="flex flex-col items-start gap-3">
<PinInputRoot v-model="value">
<PinInputInput
v-for="index in 6"
:key="index"
:index="index - 1"
/>
</PinInputRoot>
<p class="text-sm text-gray-11">
Value: {{ value.join('') || '—' }}
</p>
</div>
</template>PinInputRoot provides the input state and behavior, while each PinInputInput represents one position. The number of inputs is determined by the number of PinInputInput components you render.
API Reference
PinInputRoot
| Prop | Default | Type |
|---|---|---|
| color | – | Overrides the accent color inherited from ThemeProvider. |
| radius | – | "none""small""medium""large""full" Controls the corner radius. |
| size | "2" | "1""2""3" Controls the component size. |
| variant | "surface" | "soft""surface" Controls the visual treatment. |
PinInputInput
Examples
Size
vue
<script setup lang="ts">
import { PinInputInput, PinInputRoot } from '@typlog/ui'
</script>
<template>
<div class="flex flex-col items-start gap-4">
<PinInputRoot size="1">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot size="2">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot size="3">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
</div>
</template>Variant
vue
<script setup lang="ts">
import { PinInputInput, PinInputRoot } from '@typlog/ui'
</script>
<template>
<div class="flex flex-col items-start gap-4">
<PinInputRoot variant="surface">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot variant="soft">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
</div>
</template>One-time password
Set otp and type="number" for verification codes. Reka UI handles one-time-code autocomplete, sequential focus, and complete-code events.
Enter the six-digit code.
vue
<script setup lang="ts">
import { ref } from 'vue'
import { PinInputInput, PinInputRoot } from '@typlog/ui'
const value = ref<number[]>([])
const completed = ref('')
function onComplete(code: number[]) {
completed.value = code.join('')
}
</script>
<template>
<div class="flex flex-col items-start gap-3">
<PinInputRoot
v-model="value"
type="number"
otp
placeholder="·"
@complete="onComplete"
>
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<p class="text-sm text-gray-11">
{{ completed ? `Completed: ${completed}` : 'Enter the six-digit code.' }}
</p>
</div>
</template>Mask
vue
<script setup lang="ts">
import { PinInputInput, PinInputRoot } from '@typlog/ui'
</script>
<template>
<PinInputRoot mask placeholder="•">
<PinInputInput v-for="index in 4" :key="index" :index="index - 1" />
</PinInputRoot>
</template>States
vue
<script setup lang="ts">
import { PinInputInput, PinInputRoot } from '@typlog/ui'
const filled = ['1', '2', '3']
const disabled = ['1', '2', '3']
</script>
<template>
<div class="flex flex-col items-start gap-4">
<PinInputRoot placeholder="·">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot :model-value="filled">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot disabled :model-value="disabled">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot>
<PinInputInput v-for="index in 3" :key="index" :index="index - 1" />
<PinInputInput :index="3" disabled />
<PinInputInput :index="4" />
<PinInputInput :index="5" />
</PinInputRoot>
</div>
</template>Color
vue
<script setup lang="ts">
import { PinInputInput, PinInputRoot } from '@typlog/ui'
</script>
<template>
<div class="flex flex-col items-start gap-4">
<PinInputRoot color="indigo">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot color="green" variant="soft">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot color="red" variant="surface">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
</div>
</template>Radius
vue
<script setup lang="ts">
import { PinInputInput, PinInputRoot } from '@typlog/ui'
</script>
<template>
<div class="flex flex-col items-start gap-4">
<PinInputRoot radius="none">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot radius="small">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot radius="medium">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot radius="large">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<PinInputRoot radius="full">
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
</div>
</template>Grouped inputs
—
vue
<script setup lang="ts">
import { PinInputInput, PinInputRoot } from '@typlog/ui'
</script>
<template>
<PinInputRoot>
<PinInputInput v-for="index in 3" :key="index" :index="index - 1" />
<span aria-hidden="true" class="px-1 text-gray-10">—</span>
<PinInputInput v-for="index in 3" :key="`second-${index}`" :index="index + 2" />
</PinInputRoot>
</template>Form
name and required are forwarded to the form field represented by the root.
vue
<script setup lang="ts">
import { ref } from 'vue'
import { Button, PinInputInput, PinInputRoot } from '@typlog/ui'
const value = ref<string[]>([])
const submitted = ref('')
function submit(event: Event) {
const form = event.currentTarget as HTMLFormElement
submitted.value = new FormData(form).get('verificationCode')?.toString() ?? ''
}
</script>
<template>
<form class="flex flex-col items-start gap-3" @submit.prevent="submit">
<label for="verification-code">Verification code</label>
<PinInputRoot
id="verification-code"
v-model="value"
name="verificationCode"
required
>
<PinInputInput v-for="index in 6" :key="index" :index="index - 1" />
</PinInputRoot>
<Button type="submit">
Submit
</Button>
<p v-if="submitted" class="text-sm text-gray-11">
Submitted: {{ submitted }}
</p>
</form>
</template> Last updated Sep 15, 2026