One-time password / PIN input with individual pin cells.
Introduction
Use OtpField for one-time passwords, PINs, and short verification codes. Each character gets its own pin cell with focus management, paste support, and optional masking.
Label, description, and error chrome sit above/below the pin group. Use TextField when you need a single continuous input instead of discrete cells.
Import
import { OtpField } from "@bridge-ui/vue/Components/OtpField";import { OtpField } from "@bridge-ui/react/Components/OtpField";Basic usage
Bind the concatenated string with value/onChange in React or v-model in Vue. Use onComplete (React) or @complete (Vue) when every pin is filled.
<script setup lang="ts">
import { ref } from "vue";
import { OtpField } from "@bridge-ui/vue/Components/OtpField";
const code = ref("");
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-4">
<OtpField
v-model="code"
label="Verification code"
description="Enter the 6-digit code from your email."
/>
</div>
</template>
import { useState } from "react";
import { OtpField } from "@bridge-ui/react/Components/OtpField";
export default function OtpFieldBasic() {
const [code, setCode] = useState("");
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<OtpField
value={code}
onChange={setCode}
label="Verification code"
description="Enter the 6-digit code from your email."
/>
</div>
);
}
Length and type
Control the number of pins with length (default 6). Set type="alphanumeric" to accept letters (normalized to uppercase) as well as digits.
<script setup lang="ts">
import { OtpField } from "@bridge-ui/vue/Components/OtpField";
</script>
<template>
<div class="flex w-full max-w-md flex-col gap-6">
<OtpField :length="4" label="PIN" />
<OtpField :length="8" type="alphanumeric" label="Backup code" />
</div>
</template>
import { OtpField } from "@bridge-ui/react/Components/OtpField";
export default function OtpFieldLengthAndType() {
return (
<div className="flex w-full max-w-md flex-col gap-6">
<OtpField length={4} label="PIN" />
<OtpField length={8} type="alphanumeric" label="Backup code" />
</div>
);
}
Variants
Pin cells support the same visual variants as other form fields: outline (default), filled, underlined, stacked, and notched.
<script setup lang="ts">
import { OtpField } from "@bridge-ui/vue/Components/OtpField";
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-6">
<OtpField :length="4" label="Outline" variant="outline" />
<OtpField :length="4" label="Filled" variant="filled" />
<OtpField :length="4" label="Underlined" variant="underlined" />
<OtpField :length="4" label="Stacked" variant="stacked" />
<OtpField :length="4" label="Notched" variant="notched" />
</div>
</template>
import { OtpField } from "@bridge-ui/react/Components/OtpField";
export default function OtpFieldVariants() {
return (
<div className="flex w-full max-w-sm flex-col gap-6">
<OtpField length={4} label="Outline" variant="outline" />
<OtpField length={4} label="Filled" variant="filled" />
<OtpField length={4} label="Underlined" variant="underlined" />
<OtpField length={4} label="Stacked" variant="stacked" />
<OtpField length={4} label="Notched" variant="notched" />
</div>
);
}
Masked
Set mask to hide pin values with password-style dots.
<script setup lang="ts">
import { OtpField } from "@bridge-ui/vue/Components/OtpField";
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-4">
<OtpField mask label="Secure code" />
</div>
</template>
import { OtpField } from "@bridge-ui/react/Components/OtpField";
export default function OtpFieldMasked() {
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<OtpField mask label="Secure code" />
</div>
);
}
Adornment slots
Use the start and end slots for inline adornments beside the pin group—icons, resend actions, or other controls.
<script setup lang="ts">
import { Lock } from "@lucide/vue";
import { Button } from "@bridge-ui/vue/Components/Button";
import { OtpField } from "@bridge-ui/vue/Components/OtpField";
</script>
<template>
<div class="flex w-full max-w-md flex-col gap-4">
<OtpField label="Code">
<template #start>
<Lock aria-hidden="true" class="size-4 text-dark-500" />
</template>
<template #end>
<Button size="sm" type="button" variant="flat">Resend</Button>
</template>
</OtpField>
</div>
</template>
import { Lock } from "lucide-react";
import { Button } from "@bridge-ui/react/Components/Button";
import { OtpField } from "@bridge-ui/react/Components/OtpField";
export default function OtpFieldAdornments() {
return (
<div className="flex w-full max-w-md flex-col gap-4">
<OtpField
label="Code"
slots={{
start: <Lock aria-hidden className="size-4 text-dark-500" />,
end: (
<Button size="sm" type="button" variant="flat">
Resend
</Button>
),
}}
/>
</div>
);
}
Validation
Set error and errorMessage to show invalid styling and an error message below the pins.
<script setup lang="ts">
import { OtpField } from "@bridge-ui/vue/Components/OtpField";
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-4">
<OtpField error label="Code" error-message="Invalid or expired code." />
</div>
</template>
import { OtpField } from "@bridge-ui/react/Components/OtpField";
export default function OtpFieldValidation() {
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<OtpField error label="Code" errorMessage="Invalid or expired code." />
</div>
);
}
Related components
TextField, NumberField, Slider
Accessibility
OtpField links label, description, and error text through controlId and aria-describedby.
- Focus moves forward as characters are entered and backward on Backspace.
- Pasted values are filtered and spread across pins from the caret.
- When error is true, pins receive invalid styling and the error message is announced.
Anatomy
OtpField (root)
├── Header — label, optional corner text, required indicator
├── Pin row
│ ├── start slot (optional)
│ ├── Pin cells — one input per character
│ └── end slot (optional)
└── Footer — description or error messageAPI
| Prop | Type | Default | Description |
|---|---|---|---|
| v-model | string | — | Two-way binding for the concatenated OTP value. |
| default-value | string | — | Initial value for uncontrolled usage (without v-model). |
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | Concatenated OTP value. Use with onChange for controlled state. |
| defaultValue | string | — | Initial value for uncontrolled usage. |
| onChange | (value: string) => void | — | Called with the full OTP string when it changes. |
| onComplete | (value: string) => void | — | Called when every pin is filled. |
OtpField-specific
| Prop | Type | Default | Description |
|---|---|---|---|
| length | number | 6 | Number of pin slots. |
| type | "numeric" | "alphanumeric" | "numeric" | Character set accepted by each pin. |
| mask | boolean | false | Mask pin values (password-style). |
| autoFocus | boolean | false | Focus the first empty pin on mount. |
| placeholder | string | — | Placeholder character in empty pins. |
| variant | OtpFieldVariant | "outline" | Visual variant applied to each pin cell. |
| classes | OtpFieldClasses | — | Classes for the field chrome and pin cells. |
Events
| Event | Payload | Description |
|---|---|---|
| @change | string | Emitted when the OTP string changes. |
| @complete | string | Emitted when every pin is filled. |
| Prop | Payload | Description |
|---|---|---|
| onChange | string | Called when the OTP string changes. |
| onComplete | string | Called when every pin is filled. |
Field chrome
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | — | Primary label above the pins. |
| description | string | — | Helper text below the pins (hidden when invalid). |
| corner | string | — | Secondary header text. |
| required | boolean | false | Shows a required asterisk on the label. |
| disabled | boolean | false | Disables all pins. |
| readonly | boolean | false | Makes all pins read-only. |
| error | boolean | false | Invalid styling. |
| errorMessage | string | — | Error message below the pins. |
| hideErrorMessage | boolean | false | Hides the error message row. |
| color | OtpFieldColor | "primary" | Focus color on each pin. |
| size | OtpFieldSize | "md" | Pin size and label typography. |
| rounded | OtpFieldRounded | "md" | Border radius of each pin. |
| controlId | string | auto | Id for the pin group and related labels. |
| customProps | object | — | Extra props for internal parts. |
| slots | object | — | label, corner, description, errorMessage, start, end. |