feat: add input-otp

This commit is contained in:
shadcn
2026-01-19 15:34:34 +04:00
parent 52a4b1d466
commit 587c76f46f
24 changed files with 278 additions and 1014 deletions

View File

@@ -293,4 +293,3 @@ All other props are passed through to the underlying `<Textarea />` component.
</InputGroupAddon>
</InputGroup>
```

View File

@@ -7,11 +7,7 @@ links:
doc: https://input-otp.rodz.dev
---
<ComponentPreview
styleName="base-nova"
name="input-otp-demo"
description="An 6 digits input OTP."
/>
<ComponentPreview styleName="base-nova" name="input-otp-demo" />
## About
@@ -27,16 +23,10 @@ Input OTP is built on top of [input-otp](https://github.com/guilhermerodz/input-
</TabsList>
<TabsContent value="cli">
<Steps className="mb-0 pt-2">
<Step>Run the following command:</Step>
```bash
npx shadcn@latest add input-otp
```
</Steps>
</TabsContent>
<TabsContent value="manual">
@@ -92,150 +82,66 @@ import {
</InputOTP>
```
## Examples
### Pattern
## Pattern
Use the `pattern` prop to define a custom pattern for the OTP input.
<ComponentPreview
styleName="base-nova"
name="input-otp-pattern"
description="An input OTP with alphanumeric pattern."
/>
```tsx showLineNumbers {1,7}
```tsx showLineNumbers {1,5}
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
...
<InputOTP
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
{/* ... */}
</InputOTPGroup>
;<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
...
</InputOTP>
```
<ComponentPreview styleName="base-nova" name="input-otp-pattern" />
## Examples
### Separator
You can use the `<InputOTPSeparator />` component to add a separator between the input groups.
Use the `<InputOTPSeparator />` component to add a separator between input groups.
<ComponentPreview
styleName="base-nova"
name="input-otp-separator"
description="An input OTP with custom separator."
/>
<ComponentPreview styleName="base-nova" name="input-otp-separator" />
```tsx showLineNumbers {4,15}
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"
### Disabled
...
Use the `disabled` prop to disable the input.
<InputOTP maxLength={4}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
```
<ComponentPreview styleName="base-nova" name="input-otp-disabled" />
### Controlled
You can use the `value` and `onChange` props to control the input value.
Use the `value` and `onChange` props to control the input value.
<ComponentPreview styleName="base-nova" name="input-otp-controlled" />
### Invalid
Use `aria-invalid` on the slots to show an error state.
<ComponentPreview styleName="base-nova" name="input-otp-invalid" />
### Four Digits
A common pattern for PIN codes. This uses the `pattern={REGEXP_ONLY_DIGITS}` prop.
<ComponentPreview styleName="base-nova" name="input-otp-four-digits" />
### Alphanumeric
Use `REGEXP_ONLY_DIGITS_AND_CHARS` to accept both letters and numbers.
<ComponentPreview styleName="base-nova" name="input-otp-alphanumeric" />
### Form
<ComponentPreview styleName="base-nova" name="input-otp-form" />
<ComponentPreview
styleName="base-nova"
name="input-otp-form"
previewClassName="h-[30rem]"
/>
## Changelog
## API Reference
### 2024-03-19 Composition
We've made some updates and replaced the render props pattern with composition. Here's how to update your code if you prefer the composition pattern.
<Callout className="mt-6">
**Note:** You are not required to update your code if you are using the
`render` prop. It is still supported.
</Callout>
<Steps className="mb-0 pt-2">
<Step>Update to the latest version of `input-otp`.</Step>
```bash
npm install input-otp@latest
```
<Step>Update `input-otp.tsx`</Step>
```diff showLineNumbers title="input-otp.tsx" {2,8-11}
- import { OTPInput, SlotProps } from "input-otp"
+ import { OTPInput, OTPInputContext } from "input-otp"
const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">,
- SlotProps & React.ComponentPropsWithoutRef<"div">
- >(({ char, hasFakeCaret, isActive, className, ...props }, ref) => {
+ React.ComponentPropsWithoutRef<"div"> & { index: number }
+ >(({ index, className, ...props }, ref) => {
+ const inputOTPContext = React.useContext(OTPInputContext)
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
```
<Step>Then replace the `render` prop in your code.</Step>
```diff showLineNumbers {2-12}
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
```
</Steps>
### 2024-03-19 Disabled
To add a disabled state to the input, update `<InputOTP />` as follows:
```tsx showLineNumbers title="input-otp.tsx" {4,7-11}
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
"flex items-center gap-2 has-[:disabled]:opacity-50",
containerClassName
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
))
InputOTP.displayName = "InputOTP"
```
See the [input-otp](https://input-otp.rodz.dev) documentation for more information.

View File

@@ -7,11 +7,7 @@ links:
doc: https://input-otp.rodz.dev
---
<ComponentPreview
styleName="radix-nova"
name="input-otp-demo"
description="An 6 digits input OTP."
/>
<ComponentPreview styleName="radix-nova" name="input-otp-demo" />
## About
@@ -27,16 +23,10 @@ Input OTP is built on top of [input-otp](https://github.com/guilhermerodz/input-
</TabsList>
<TabsContent value="cli">
<Steps className="mb-0 pt-2">
<Step>Run the following command:</Step>
```bash
npx shadcn@latest add input-otp
```
</Steps>
</TabsContent>
<TabsContent value="manual">
@@ -92,150 +82,66 @@ import {
</InputOTP>
```
## Examples
### Pattern
## Pattern
Use the `pattern` prop to define a custom pattern for the OTP input.
<ComponentPreview
styleName="radix-nova"
name="input-otp-pattern"
description="An input OTP with alphanumeric pattern."
/>
```tsx showLineNumbers {1,7}
```tsx showLineNumbers {1,5}
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
...
<InputOTP
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
{/* ... */}
</InputOTPGroup>
;<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
...
</InputOTP>
```
<ComponentPreview styleName="radix-nova" name="input-otp-pattern" />
## Examples
### Separator
You can use the `<InputOTPSeparator />` component to add a separator between the input groups.
Use the `<InputOTPSeparator />` component to add a separator between input groups.
<ComponentPreview
styleName="radix-nova"
name="input-otp-separator"
description="An input OTP with custom separator."
/>
<ComponentPreview styleName="radix-nova" name="input-otp-separator" />
```tsx showLineNumbers {4,15}
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"
### Disabled
...
Use the `disabled` prop to disable the input.
<InputOTP maxLength={4}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
```
<ComponentPreview styleName="radix-nova" name="input-otp-disabled" />
### Controlled
You can use the `value` and `onChange` props to control the input value.
Use the `value` and `onChange` props to control the input value.
<ComponentPreview styleName="radix-nova" name="input-otp-controlled" />
### Invalid
Use `aria-invalid` on the slots to show an error state.
<ComponentPreview styleName="radix-nova" name="input-otp-invalid" />
### Four Digits
A common pattern for PIN codes. This uses the `pattern={REGEXP_ONLY_DIGITS}` prop.
<ComponentPreview styleName="radix-nova" name="input-otp-four-digits" />
### Alphanumeric
Use `REGEXP_ONLY_DIGITS_AND_CHARS` to accept both letters and numbers.
<ComponentPreview styleName="radix-nova" name="input-otp-alphanumeric" />
### Form
<ComponentPreview styleName="radix-nova" name="input-otp-form" />
<ComponentPreview
styleName="radix-nova"
name="input-otp-form"
previewClassName="h-[30rem ]"
/>
## Changelog
## API Reference
### 2024-03-19 Composition
We've made some updates and replaced the render props pattern with composition. Here's how to update your code if you prefer the composition pattern.
<Callout className="mt-6">
**Note:** You are not required to update your code if you are using the
`render` prop. It is still supported.
</Callout>
<Steps className="mb-0 pt-2">
<Step>Update to the latest version of `input-otp`.</Step>
```bash
npm install input-otp@latest
```
<Step>Update `input-otp.tsx`</Step>
```diff showLineNumbers title="input-otp.tsx" {2,8-11}
- import { OTPInput, SlotProps } from "input-otp"
+ import { OTPInput, OTPInputContext } from "input-otp"
const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">,
- SlotProps & React.ComponentPropsWithoutRef<"div">
- >(({ char, hasFakeCaret, isActive, className, ...props }, ref) => {
+ React.ComponentPropsWithoutRef<"div"> & { index: number }
+ >(({ index, className, ...props }, ref) => {
+ const inputOTPContext = React.useContext(OTPInputContext)
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
```
<Step>Then replace the `render` prop in your code.</Step>
```diff showLineNumbers {2-12}
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
```
</Steps>
### 2024-03-19 Disabled
To add a disabled state to the input, update `<InputOTP />` as follows:
```tsx showLineNumbers title="input-otp.tsx" {4,7-11}
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
"flex items-center gap-2 has-[:disabled]:opacity-50",
containerClassName
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
))
InputOTP.displayName = "InputOTP"
```
See the [input-otp](https://input-otp.rodz.dev) documentation for more information.

View File

@@ -2930,19 +2930,6 @@ export const ExamplesIndex: Record<string, Record<string, any>> = {
return { default: mod.default || mod[exportName] }
}),
},
"input-otp-fields": {
name: "input-otp-fields",
filePath: "examples/radix/input-otp-fields.tsx",
component: React.lazy(async () => {
const mod = await import("./radix/input-otp-fields")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-otp-fields"
return { default: mod.default || mod[exportName] }
}),
},
"input-otp-form": {
name: "input-otp-form",
filePath: "examples/radix/input-otp-form.tsx",
@@ -3008,32 +2995,6 @@ export const ExamplesIndex: Record<string, Record<string, any>> = {
return { default: mod.default || mod[exportName] }
}),
},
"input-otp-simple": {
name: "input-otp-simple",
filePath: "examples/radix/input-otp-simple.tsx",
component: React.lazy(async () => {
const mod = await import("./radix/input-otp-simple")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-otp-simple"
return { default: mod.default || mod[exportName] }
}),
},
"input-otp-with-separator": {
name: "input-otp-with-separator",
filePath: "examples/radix/input-otp-with-separator.tsx",
component: React.lazy(async () => {
const mod = await import("./radix/input-otp-with-separator")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-otp-with-separator"
return { default: mod.default || mod[exportName] }
}),
},
"input-required": {
name: "input-required",
filePath: "examples/radix/input-required.tsx",
@@ -9304,19 +9265,6 @@ export const ExamplesIndex: Record<string, Record<string, any>> = {
return { default: mod.default || mod[exportName] }
}),
},
"input-otp-fields": {
name: "input-otp-fields",
filePath: "examples/base/input-otp-fields.tsx",
component: React.lazy(async () => {
const mod = await import("./base/input-otp-fields")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-otp-fields"
return { default: mod.default || mod[exportName] }
}),
},
"input-otp-form": {
name: "input-otp-form",
filePath: "examples/base/input-otp-form.tsx",
@@ -9382,32 +9330,6 @@ export const ExamplesIndex: Record<string, Record<string, any>> = {
return { default: mod.default || mod[exportName] }
}),
},
"input-otp-simple": {
name: "input-otp-simple",
filePath: "examples/base/input-otp-simple.tsx",
component: React.lazy(async () => {
const mod = await import("./base/input-otp-simple")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-otp-simple"
return { default: mod.default || mod[exportName] }
}),
},
"input-otp-with-separator": {
name: "input-otp-with-separator",
filePath: "examples/base/input-otp-with-separator.tsx",
component: React.lazy(async () => {
const mod = await import("./base/input-otp-with-separator")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-otp-with-separator"
return { default: mod.default || mod[exportName] }
}),
},
"input-required": {
name: "input-required",
filePath: "examples/base/input-required.tsx",

View File

@@ -1,4 +1,5 @@
import { Field, FieldDescription, FieldLabel } from "@/examples/base/ui/field"
"use client"
import {
InputOTP,
InputOTPGroup,
@@ -9,26 +10,18 @@ import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
export function InputOTPAlphanumeric() {
return (
<Field>
<FieldLabel htmlFor="alphanumeric">Alphanumeric</FieldLabel>
<FieldDescription>Accepts both letters and numbers.</FieldDescription>
<InputOTP
id="alphanumeric"
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
)
}

View File

@@ -1,20 +1,16 @@
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/examples/base/ui/input-otp"
export default function InputOTPDemo() {
export function InputOTPDemo() {
return (
<InputOTP maxLength={6}>
<InputOTP maxLength={6} defaultValue="123456">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />

View File

@@ -8,21 +8,18 @@ import {
export function InputOTPDisabled() {
return (
<Field>
<FieldLabel htmlFor="disabled">Disabled</FieldLabel>
<InputOTP id="disabled" maxLength={6} disabled value="123456">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
<InputOTP id="disabled" maxLength={6} disabled value="123456">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
)
}

View File

@@ -1,135 +0,0 @@
"use client"
import * as React from "react"
import { useState } from "react"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/examples/base/ui/field"
import { Input } from "@/examples/base/ui/input"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/examples/base/ui/input-otp"
import { REGEXP_ONLY_DIGITS } from "input-otp"
export function InputOTPFields() {
const [value, setValue] = useState("")
const [pinValue, setPinValue] = useState("")
return (
<FieldGroup>
<Field>
<FieldLabel htmlFor="otp-basic">Verification Code</FieldLabel>
<InputOTP id="otp-basic" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
<Field>
<FieldLabel htmlFor="otp-with-desc">Enter OTP</FieldLabel>
<InputOTP
id="otp-with-desc"
maxLength={6}
value={value}
onChange={setValue}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
Enter the 6-digit code sent to your email.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="otp-separator">
Two-Factor Authentication
</FieldLabel>
<InputOTP id="otp-separator" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
Enter the code from your authenticator app.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="otp-pin">PIN Code</FieldLabel>
<InputOTP
id="otp-pin"
maxLength={4}
pattern={REGEXP_ONLY_DIGITS}
value={pinValue}
onChange={setPinValue}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
Enter your 4-digit PIN (numbers only).
</FieldDescription>
</Field>
<Field data-invalid>
<FieldLabel htmlFor="otp-invalid">Invalid OTP</FieldLabel>
<InputOTP id="otp-invalid" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} aria-invalid />
<InputOTPSlot index={1} aria-invalid />
<InputOTPSlot index={2} aria-invalid />
<InputOTPSlot index={3} aria-invalid />
<InputOTPSlot index={4} aria-invalid />
<InputOTPSlot index={5} aria-invalid />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
This OTP field contains validation errors.
</FieldDescription>
</Field>
<Field data-disabled>
<FieldLabel htmlFor="otp-disabled-field">Disabled OTP</FieldLabel>
<InputOTP id="otp-disabled-field" maxLength={6} disabled>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
This OTP field is currently disabled.
</FieldDescription>
</Field>
</FieldGroup>
)
}

View File

@@ -27,49 +27,49 @@ export function InputOTPForm() {
</CardDescription>
</CardHeader>
<CardContent>
<form>
<Field>
<div className="flex items-center justify-between">
<FieldLabel htmlFor="otp-verification">
Verification code
</FieldLabel>
<Button variant="outline" size="xs">
<RefreshCwIcon />
Resend Code
</Button>
</div>
<InputOTP maxLength={6} id="otp-verification" required>
<InputOTPGroup className="style-nova:*:data-[slot=input-otp-slot]:h-12 style-nova:*:data-[slot=input-otp-slot]:w-11 style-vega:*:data-[slot=input-otp-slot]:h-16 style-maia:*:data-[slot=input-otp-slot]:h-16 style-vega:*:data-[slot=input-otp-slot]:w-12 style-maia:*:data-[slot=input-otp-slot]:w-12 style-mira:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:w-11 style-mira:*:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup className="style-nova:*:data-[slot=input-otp-slot]:h-12 style-nova:*:data-[slot=input-otp-slot]:w-11 style-vega:*:data-[slot=input-otp-slot]:h-16 style-maia:*:data-[slot=input-otp-slot]:h-16 style-vega:*:data-[slot=input-otp-slot]:w-12 style-maia:*:data-[slot=input-otp-slot]:w-12 style-mira:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:w-11 style-mira:*:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
<a href="#">I no longer have access to this email address.</a>
</FieldDescription>
</Field>
</form>
<Field>
<div className="flex items-center justify-between">
<FieldLabel htmlFor="otp-verification">
Verification code
</FieldLabel>
<Button variant="outline" size="xs">
<RefreshCwIcon />
Resend Code
</Button>
</div>
<InputOTP maxLength={6} id="otp-verification" required>
<InputOTPGroup className="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator className="mx-2" />
<InputOTPGroup className="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
<a href="#">I no longer have access to this email address.</a>
</FieldDescription>
</Field>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button type="submit" className="w-full">
Verify
</Button>
<div className="text-muted-foreground text-sm">
Having trouble signing in?{" "}
<a
href="#"
className="hover:text-primary underline underline-offset-4 transition-colors"
>
Contact support
</a>
</div>
<CardFooter>
<Field>
<Button type="submit" className="w-full">
Verify
</Button>
<div className="text-muted-foreground text-sm">
Having trouble signing in?{" "}
<a
href="#"
className="hover:text-primary underline underline-offset-4 transition-colors"
>
Contact support
</a>
</div>
</Field>
</CardFooter>
</Card>
)

View File

@@ -1,4 +1,5 @@
import { Field, FieldDescription, FieldLabel } from "@/examples/base/ui/field"
"use client"
import {
InputOTP,
InputOTPGroup,
@@ -8,17 +9,13 @@ import { REGEXP_ONLY_DIGITS } from "input-otp"
export function InputOTPFourDigits() {
return (
<Field>
<FieldLabel htmlFor="four-digits">4 Digits</FieldLabel>
<FieldDescription>Common pattern for PIN codes.</FieldDescription>
<InputOTP id="four-digits" maxLength={4} pattern={REGEXP_ONLY_DIGITS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
</Field>
<InputOTP maxLength={4} pattern={REGEXP_ONLY_DIGITS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
)
}

View File

@@ -1,12 +1,6 @@
"use client"
import * as React from "react"
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/examples/base/ui/field"
import {
InputOTP,
InputOTPGroup,
@@ -18,28 +12,21 @@ export function InputOTPInvalid() {
const [value, setValue] = React.useState("000000")
return (
<Field>
<FieldLabel htmlFor="invalid">Invalid State</FieldLabel>
<FieldDescription>
Example showing the invalid error state.
</FieldDescription>
<InputOTP id="invalid" maxLength={6} value={value} onChange={setValue}>
<InputOTPGroup>
<InputOTPSlot index={0} aria-invalid />
<InputOTPSlot index={1} aria-invalid />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} aria-invalid />
<InputOTPSlot index={3} aria-invalid />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={4} aria-invalid />
<InputOTPSlot index={5} aria-invalid />
</InputOTPGroup>
</InputOTP>
<FieldError errors={[{ message: "Invalid code. Please try again." }]} />
</Field>
<InputOTP maxLength={6} value={value} onChange={setValue}>
<InputOTPGroup>
<InputOTPSlot index={0} aria-invalid />
<InputOTPSlot index={1} aria-invalid />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} aria-invalid />
<InputOTPSlot index={3} aria-invalid />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={4} aria-invalid />
<InputOTPSlot index={5} aria-invalid />
</InputOTPGroup>
</InputOTP>
)
}

View File

@@ -10,7 +10,7 @@ import { REGEXP_ONLY_DIGITS } from "input-otp"
export function InputOTPPattern() {
return (
<Field>
<Field className="w-fit">
<FieldLabel htmlFor="digits-only">Digits Only</FieldLabel>
<InputOTP id="digits-only" maxLength={6} pattern={REGEXP_ONLY_DIGITS}>
<InputOTPGroup>

View File

@@ -1,28 +0,0 @@
import { Field, FieldLabel } from "@/examples/base/ui/field"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/examples/base/ui/input-otp"
export function InputOTPSimple() {
return (
<Field>
<FieldLabel htmlFor="simple">Simple</FieldLabel>
<InputOTP id="simple" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
)
}

View File

@@ -1,41 +0,0 @@
"use client"
import * as React from "react"
import { Field, FieldLabel } from "@/examples/base/ui/field"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/examples/base/ui/input-otp"
export function InputOTPWithSeparator() {
const [value, setValue] = React.useState("123456")
return (
<Field>
<FieldLabel htmlFor="with-separator">With Separator</FieldLabel>
<InputOTP
id="with-separator"
maxLength={6}
value={value}
onChange={setValue}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
)
}

View File

@@ -1,4 +1,5 @@
import { Field, FieldDescription, FieldLabel } from "@/examples/radix/ui/field"
"use client"
import {
InputOTP,
InputOTPGroup,
@@ -9,26 +10,18 @@ import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
export function InputOTPAlphanumeric() {
return (
<Field>
<FieldLabel htmlFor="alphanumeric">Alphanumeric</FieldLabel>
<FieldDescription>Accepts both letters and numbers.</FieldDescription>
<InputOTP
id="alphanumeric"
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
)
}

View File

@@ -1,20 +1,16 @@
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/examples/radix/ui/input-otp"
export default function InputOTPDemo() {
export function InputOTPDemo() {
return (
<InputOTP maxLength={6}>
<InputOTP maxLength={6} defaultValue="123456">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />

View File

@@ -1,4 +1,3 @@
import { Field, FieldLabel } from "@/examples/radix/ui/field"
import {
InputOTP,
InputOTPGroup,
@@ -8,21 +7,18 @@ import {
export function InputOTPDisabled() {
return (
<Field>
<FieldLabel htmlFor="disabled">Disabled</FieldLabel>
<InputOTP id="disabled" maxLength={6} disabled value="123456">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
<InputOTP id="disabled" maxLength={6} disabled value="123456">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
)
}

View File

@@ -1,135 +0,0 @@
"use client"
import * as React from "react"
import { useState } from "react"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/examples/radix/ui/field"
import { Input } from "@/examples/radix/ui/input"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/examples/radix/ui/input-otp"
import { REGEXP_ONLY_DIGITS } from "input-otp"
export function InputOTPFields() {
const [value, setValue] = useState("")
const [pinValue, setPinValue] = useState("")
return (
<FieldGroup>
<Field>
<FieldLabel htmlFor="otp-basic">Verification Code</FieldLabel>
<InputOTP id="otp-basic" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
<Field>
<FieldLabel htmlFor="otp-with-desc">Enter OTP</FieldLabel>
<InputOTP
id="otp-with-desc"
maxLength={6}
value={value}
onChange={setValue}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
Enter the 6-digit code sent to your email.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="otp-separator">
Two-Factor Authentication
</FieldLabel>
<InputOTP id="otp-separator" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
Enter the code from your authenticator app.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="otp-pin">PIN Code</FieldLabel>
<InputOTP
id="otp-pin"
maxLength={4}
pattern={REGEXP_ONLY_DIGITS}
value={pinValue}
onChange={setPinValue}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
Enter your 4-digit PIN (numbers only).
</FieldDescription>
</Field>
<Field data-invalid>
<FieldLabel htmlFor="otp-invalid">Invalid OTP</FieldLabel>
<InputOTP id="otp-invalid" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} aria-invalid />
<InputOTPSlot index={1} aria-invalid />
<InputOTPSlot index={2} aria-invalid />
<InputOTPSlot index={3} aria-invalid />
<InputOTPSlot index={4} aria-invalid />
<InputOTPSlot index={5} aria-invalid />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
This OTP field contains validation errors.
</FieldDescription>
</Field>
<Field data-disabled>
<FieldLabel htmlFor="otp-disabled-field">Disabled OTP</FieldLabel>
<InputOTP id="otp-disabled-field" maxLength={6} disabled>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
This OTP field is currently disabled.
</FieldDescription>
</Field>
</FieldGroup>
)
}

View File

@@ -27,49 +27,49 @@ export function InputOTPForm() {
</CardDescription>
</CardHeader>
<CardContent>
<form>
<Field>
<div className="flex items-center justify-between">
<FieldLabel htmlFor="otp-verification">
Verification code
</FieldLabel>
<Button variant="outline" size="xs">
<RefreshCwIcon />
Resend Code
</Button>
</div>
<InputOTP maxLength={6} id="otp-verification" required>
<InputOTPGroup className="style-nova:*:data-[slot=input-otp-slot]:h-12 style-nova:*:data-[slot=input-otp-slot]:w-11 style-vega:*:data-[slot=input-otp-slot]:h-16 style-maia:*:data-[slot=input-otp-slot]:h-16 style-vega:*:data-[slot=input-otp-slot]:w-12 style-maia:*:data-[slot=input-otp-slot]:w-12 style-mira:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:w-11 style-mira:*:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup className="style-nova:*:data-[slot=input-otp-slot]:h-12 style-nova:*:data-[slot=input-otp-slot]:w-11 style-vega:*:data-[slot=input-otp-slot]:h-16 style-maia:*:data-[slot=input-otp-slot]:h-16 style-vega:*:data-[slot=input-otp-slot]:w-12 style-maia:*:data-[slot=input-otp-slot]:w-12 style-mira:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:w-11 style-mira:*:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
<a href="#">I no longer have access to this email address.</a>
</FieldDescription>
</Field>
</form>
<Field>
<div className="flex items-center justify-between">
<FieldLabel htmlFor="otp-verification">
Verification code
</FieldLabel>
<Button variant="outline" size="xs">
<RefreshCwIcon />
Resend Code
</Button>
</div>
<InputOTP maxLength={6} id="otp-verification" required>
<InputOTPGroup className="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator className="mx-2" />
<InputOTPGroup className="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
<a href="#">I no longer have access to this email address.</a>
</FieldDescription>
</Field>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button type="submit" className="w-full">
Verify
</Button>
<div className="text-muted-foreground text-sm">
Having trouble signing in?{" "}
<a
href="#"
className="hover:text-primary underline underline-offset-4 transition-colors"
>
Contact support
</a>
</div>
<CardFooter>
<Field>
<Button type="submit" className="w-full">
Verify
</Button>
<div className="text-muted-foreground text-sm">
Having trouble signing in?{" "}
<a
href="#"
className="hover:text-primary underline underline-offset-4 transition-colors"
>
Contact support
</a>
</div>
</Field>
</CardFooter>
</Card>
)

View File

@@ -1,4 +1,5 @@
import { Field, FieldDescription, FieldLabel } from "@/examples/radix/ui/field"
"use client"
import {
InputOTP,
InputOTPGroup,
@@ -8,17 +9,13 @@ import { REGEXP_ONLY_DIGITS } from "input-otp"
export function InputOTPFourDigits() {
return (
<Field>
<FieldLabel htmlFor="four-digits">4 Digits</FieldLabel>
<FieldDescription>Common pattern for PIN codes.</FieldDescription>
<InputOTP id="four-digits" maxLength={4} pattern={REGEXP_ONLY_DIGITS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
</Field>
<InputOTP maxLength={4} pattern={REGEXP_ONLY_DIGITS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
)
}

View File

@@ -1,12 +1,6 @@
"use client"
import * as React from "react"
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/examples/radix/ui/field"
import {
InputOTP,
InputOTPGroup,
@@ -18,28 +12,21 @@ export function InputOTPInvalid() {
const [value, setValue] = React.useState("000000")
return (
<Field>
<FieldLabel htmlFor="invalid">Invalid State</FieldLabel>
<FieldDescription>
Example showing the invalid error state.
</FieldDescription>
<InputOTP id="invalid" maxLength={6} value={value} onChange={setValue}>
<InputOTPGroup>
<InputOTPSlot index={0} aria-invalid />
<InputOTPSlot index={1} aria-invalid />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} aria-invalid />
<InputOTPSlot index={3} aria-invalid />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={4} aria-invalid />
<InputOTPSlot index={5} aria-invalid />
</InputOTPGroup>
</InputOTP>
<FieldError errors={[{ message: "Invalid code. Please try again." }]} />
</Field>
<InputOTP maxLength={6} value={value} onChange={setValue}>
<InputOTPGroup>
<InputOTPSlot index={0} aria-invalid />
<InputOTPSlot index={1} aria-invalid />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} aria-invalid />
<InputOTPSlot index={3} aria-invalid />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={4} aria-invalid />
<InputOTPSlot index={5} aria-invalid />
</InputOTPGroup>
</InputOTP>
)
}

View File

@@ -10,7 +10,7 @@ import { REGEXP_ONLY_DIGITS } from "input-otp"
export function InputOTPPattern() {
return (
<Field>
<Field className="w-fit">
<FieldLabel htmlFor="digits-only">Digits Only</FieldLabel>
<InputOTP id="digits-only" maxLength={6} pattern={REGEXP_ONLY_DIGITS}>
<InputOTPGroup>

View File

@@ -1,28 +0,0 @@
import { Field, FieldLabel } from "@/examples/radix/ui/field"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/examples/radix/ui/input-otp"
export function InputOTPSimple() {
return (
<Field>
<FieldLabel htmlFor="simple">Simple</FieldLabel>
<InputOTP id="simple" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
)
}

View File

@@ -1,41 +0,0 @@
"use client"
import * as React from "react"
import { Field, FieldLabel } from "@/examples/radix/ui/field"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/examples/radix/ui/input-otp"
export function InputOTPWithSeparator() {
const [value, setValue] = React.useState("123456")
return (
<Field>
<FieldLabel htmlFor="with-separator">With Separator</FieldLabel>
<InputOTP
id="with-separator"
maxLength={6}
value={value}
onChange={setValue}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
)
}