Input OTP
One-time code entry with grouped slots. Paste fills all slots.
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "@/components/ui/input-otp";import { REGEXP_ONLY_DIGITS } from "input-otp";
export default function Demo() { return ( <InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> );}<script setup lang="ts">import { REGEXP_ONLY_DIGITS } from "vue-input-otp";import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "@/components/ui/input-otp";</script>
<template> <InputOTP :maxlength="6" :pattern="REGEXP_ONLY_DIGITS"> <template #default="{ slots }"> <InputOTPGroup> <InputOTPSlot v-for="(_, i) in slots.slice(0, 3)" :key="i" :index="i" /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot v-for="(_, i) in slots.slice(3)" :key="i + 3" :index="i + 3" /> </InputOTPGroup> </template> </InputOTP></template><script lang="ts"> import { REGEXP_ONLY_DIGITS } from "bits-ui"; import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, } from "$lib/components/ui/input-otp";
let value = $state("");</script>
<InputOTP maxlength={6} pattern={REGEXP_ONLY_DIGITS} bind:value> {#snippet children({ cells })} <InputOTPGroup> {#each cells.slice(0, 3) as cell, i (i)} <InputOTPSlot {cell} /> {/each} </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> {#each cells.slice(3, 6) as cell, i (i)} <InputOTPSlot {cell} /> {/each} </InputOTPGroup> {/snippet}</InputOTP>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/input-otppnpm dlx shadcn@latest add @edmi-ui/input-otpyarn dlx shadcn@latest add @edmi-ui/input-otpbunx shadcn@latest add @edmi-ui/input-otpPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install input-otpTerminal window pnpm add input-otpTerminal window yarn add input-otpTerminal window bun add input-otpAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/input-otppnpm dlx shadcn-vue@latest add @edmi-ui/input-otpyarn dlx shadcn-vue@latest add @edmi-ui/input-otpbunx shadcn-vue@latest add @edmi-ui/input-otpPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install vue-input-otp reka-ui @vueuse/coreTerminal window pnpm add vue-input-otp reka-ui @vueuse/coreTerminal window yarn add vue-input-otp reka-ui @vueuse/coreTerminal window bun add vue-input-otp reka-ui @vueuse/coreAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/input-otp.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/input-otp.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/input-otp.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/input-otp.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "@/components/ui/input-otp";
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup></InputOTP><script setup lang="ts">import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "@/components/ui/input-otp";</script>
<template> <InputOTP :maxlength="6" :pattern="REGEXP_ONLY_DIGITS"> <template #default="{ slots }"> <InputOTPGroup> <InputOTPSlot v-for="(_, i) in slots.slice(0, 3)" :key="i" :index="i" /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot v-for="(_, i) in slots.slice(3)" :key="i + 3" :index="i + 3" /> </InputOTPGroup> </template> </InputOTP></template><script lang="ts"> import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, } from "$lib/components/ui/input-otp";</script>
<InputOTP maxlength={6} pattern={REGEXP_ONLY_DIGITS} bind:value> {#snippet children({ cells })} <InputOTPGroup> {#each cells.slice(0, 3) as cell, i (i)} <InputOTPSlot {cell} /> {/each} </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> {#each cells.slice(3, 6) as cell, i (i)} <InputOTPSlot {cell} /> {/each} </InputOTPGroup> {/snippet}</InputOTP>Component source
Section titled “Component source”"use client";
import { cn } from "cn";import { OTPInput, OTPInputContext } from "input-otp";import * as React from "react";import { MinusIcon } from "@phosphor-icons/react";
import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
// ✦ depth (v4): fields sink (-1) in layered mode; focus swaps the edge for the ringconst fieldElevation = { sunken: "rounded-none border-0 border-l border-border bg-transparent shadow-none first:border-l-0 data-[active=true]:rounded-md data-[active=true]:border-transparent data-[active=true]:shadow-[0_0_0_3px_var(--ring-soft),inset_0_0_0_1px_var(--ring)]", flat: "", raised: "rounded-none border-0 border-l border-border bg-transparent shadow-none first:border-l-0 data-[active=true]:rounded-md data-[active=true]:border-transparent data-[active=true]:shadow-[0_0_0_3px_var(--ring-soft),inset_0_0_0_1px_var(--ring)]", floating: "rounded-none border-0 border-l border-border bg-transparent shadow-none first:border-l-0 data-[active=true]:rounded-md data-[active=true]:border-transparent data-[active=true]:shadow-[0_0_0_3px_var(--ring-soft),inset_0_0_0_1px_var(--ring)]",};
const InputOTPElevationContext = React.createContext<Elevation | undefined>( undefined,);
function InputOTP({ className, containerClassName, elevation, ...props}: React.ComponentProps<typeof OTPInput> & { containerClassName?: string; /** ✦ depth for every slot: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation;}) { return ( <InputOTPElevationContext.Provider value={elevation}> <OTPInput data-slot="input-otp" containerClassName={cn( "flex items-center gap-2 has-disabled:opacity-50", containerClassName, )} spellCheck={false} className={cn("disabled:cursor-not-allowed", className)} {...props} /> </InputOTPElevationContext.Provider> );}
// ✦ depth (v6): at sunken/raised/floating the group is one plate; slots turn transparent with 1px separatorsconst groupElevation = { sunken: "rounded-lg border border-sk-bd bg-sk-bg shadow-sunken", flat: "", raised: "rounded-lg border border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-raised", floating: "rounded-lg border border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-floating",};
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) { const group = React.useContext(InputOTPElevationContext); const level = useElevation(group, "field"); return ( <div data-slot="input-otp-group" className={cn( "flex items-center gap-1.5", level !== "flat" && "inline-flex gap-0", groupElevation[level], className, )} {...props} /> );}
function InputOTPSlot({ index, className, elevation, ...props}: React.ComponentProps<"div"> & { index: number; /** ✦ depth: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation;}) { const group = React.useContext(InputOTPElevationContext); const level = useElevation(elevation ?? group, "field"); const inputOTPContext = React.useContext(OTPInputContext); const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
return ( <div data-slot="input-otp-slot" data-active={isActive} className={cn( "relative flex size-10 items-center justify-center rounded-md border border-input bg-card font-mono text-sm text-foreground outline-none aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:shadow-ring data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:shadow-ring-error", fieldElevation[level], className, )} {...props} > {char} {hasFakeCaret && ( <div className="pointer-events-none absolute inset-0 flex items-center justify-center"> <div className="h-4 w-px animate-pulse bg-foreground duration-1000" /> </div> )} </div> );}
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) { return ( // biome-ignore lint/a11y/useSemanticElements: stock markup; div keeps layout predictable <div data-slot="input-otp-separator" className="flex items-center text-muted-foreground [&_svg:not([class*='size-'])]:size-4" role="separator" {...props} > <MinusIcon /> </div> );}
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot };<script setup lang="ts">import type { HTMLAttributes } from 'vue'import type { OTPInputEmits, OTPInputProps } from 'vue-input-otp'import { reactiveOmit } from '@vueuse/core'import { useForwardPropsEmits } from 'reka-ui'import { provide } from 'vue'import { OTPInput } from 'vue-input-otp'import { cn } from '@/registry/edmi/lib/utils'import type { Elevation } from '@/registry/edmi/ui/elevation'
const props = withDefaults(defineProps<OTPInputProps & { class?: HTMLAttributes['class'] /** ✦ depth for every slot: sunken -1, flat 0, raised +1, floating +2 */ elevation?: Elevation}>(), { elevation: undefined })
provide('inputOTPElevation', { get value() { return props.elevation } })
const emits = defineEmits<OTPInputEmits>()
const delegatedProps = reactiveOmit(props, 'class', 'elevation')
const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>
<template> <OTPInput v-slot="slotProps" v-bind="forwarded" :container-class="cn('flex items-center gap-2 has-disabled:opacity-50', props.class)" data-slot="input-otp" :spellcheck="false" class="disabled:cursor-not-allowed" > <slot v-bind="slotProps" /> </OTPInput></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { reactiveOmit } from '@vueuse/core'import { useForwardProps } from 'reka-ui'import { inject } from 'vue'import { cn } from '@/registry/edmi/lib/utils'import { type Elevation, useElevation } from '@/registry/edmi/ui/elevation'
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardProps(delegatedProps)
const group = inject<{ readonly value: Elevation | undefined } | undefined>('inputOTPElevation', undefined)const level = useElevation(() => group?.value, 'field')
// ✦ depth (v6): at sunken/raised/floating the group is one plate; slots turn transparent with 1px separatorsconst groupElevation = { sunken: 'rounded-lg border border-sk-bd bg-sk-bg shadow-sunken', flat: '', raised: 'rounded-lg border border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-raised', floating: 'rounded-lg border border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-floating',}</script>
<template> <div data-slot="input-otp-group" v-bind="forwarded" :class="cn('flex items-center gap-1.5', level !== 'flat' && 'inline-flex gap-0', groupElevation[level], props.class)" > <slot /> </div></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { MinusIcon } from '@lucide/vue'import { useForwardProps } from 'reka-ui'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
const forwarded = useForwardProps(props)</script>
<template> <div data-slot="input-otp-separator" role="separator" v-bind="forwarded" :class="cn('flex items-center text-muted-foreground [&_svg:not([class*=\'size-\'])]:size-4', props.class)" > <slot> <MinusIcon /> </slot> </div></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { reactiveOmit } from '@vueuse/core'import { useForwardProps } from 'reka-ui'import { computed, inject } from 'vue'import { useVueOTPContext } from 'vue-input-otp'import { cn } from '@/registry/edmi/lib/utils'import { type Elevation, useElevation } from '@/registry/edmi/ui/elevation'
const props = withDefaults(defineProps<{ index: number class?: HTMLAttributes['class'] /** ✦ depth: sunken -1, flat 0, raised +1, floating +2 */ elevation?: Elevation}>(), { elevation: undefined })
const delegatedProps = reactiveOmit(props, 'class', 'elevation')
const forwarded = useForwardProps(delegatedProps)
const context = useVueOTPContext()
const group = inject<{ readonly value: Elevation | undefined } | undefined>('inputOTPElevation', undefined)const level = useElevation(() => props.elevation ?? group?.value, 'field')
// ✦ depth (v4): slots sink (-1) in layered mode; the active slot swaps the edge for the ringconst fieldElevation = { sunken: 'rounded-none border-0 border-l border-border bg-transparent shadow-none first:border-l-0 data-[active=true]:rounded-md data-[active=true]:border-transparent data-[active=true]:shadow-[0_0_0_3px_var(--ring-soft),inset_0_0_0_1px_var(--ring)]', flat: '', raised: 'rounded-none border-0 border-l border-border bg-transparent shadow-none first:border-l-0 data-[active=true]:rounded-md data-[active=true]:border-transparent data-[active=true]:shadow-[0_0_0_3px_var(--ring-soft),inset_0_0_0_1px_var(--ring)]', floating: 'rounded-none border-0 border-l border-border bg-transparent shadow-none first:border-l-0 data-[active=true]:rounded-md data-[active=true]:border-transparent data-[active=true]:shadow-[0_0_0_3px_var(--ring-soft),inset_0_0_0_1px_var(--ring)]',}
const slot = computed(() => context?.value.slots[props.index])</script>
<template> <div v-bind="forwarded" data-slot="input-otp-slot" :data-active="slot?.isActive" :class="cn('relative flex size-10 items-center justify-center rounded-md border border-input bg-card font-mono text-sm text-foreground outline-none aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:shadow-ring data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:shadow-ring-error', fieldElevation[level], props.class)" > {{ slot?.char }} <div v-if="slot?.hasFakeCaret" class="pointer-events-none absolute inset-0 flex items-center justify-center"> <div class="h-4 w-px animate-pulse bg-foreground duration-1000" /> </div> </div></template>export { default as InputOTP } from "./InputOTP.vue";export { default as InputOTPGroup } from "./InputOTPGroup.vue";export { default as InputOTPSeparator } from "./InputOTPSeparator.vue";export { default as InputOTPSlot } from "./InputOTPSlot.vue";<script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements"; import { getContext } from "svelte"; import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js";
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
const group = getContext<(() => Elevation) | undefined>("inputOTPElevation"); const level = useElevation(() => group?.(), "field");
// ✦ depth (v6): at sunken/raised/floating the group is one plate; slots turn transparent with 1px separators const groupElevation = { sunken: "rounded-lg border border-sk-bd bg-sk-bg shadow-sunken", flat: "", raised: "rounded-lg border border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-raised", floating: "rounded-lg border border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-floating", };</script>
<div bind:this={ref} data-slot="input-otp-group" class={cn( "flex items-center gap-1.5", level.current !== "flat" && "inline-flex gap-0", groupElevation[level.current], className )} {...restProps}> {@render children?.()}</div><script lang="ts"> import MinusIcon from "phosphor-svelte/lib/Minus"; import type { WithElementRef } from "$lib/utils.js"; import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();</script>
<div bind:this={ref} data-slot="input-otp-separator" role="separator" class={cn("flex items-center text-muted-foreground [&_svg:not([class*='size-'])]:size-4", className)} {...restProps}> {#if children} {@render children?.()} {:else} <MinusIcon /> {/if}</div><script lang="ts"> import { PinInput as InputOTPPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js"; import { getContext } from "svelte"; import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js";
let { ref = $bindable(null), cell, class: className, elevation, ...restProps }: InputOTPPrimitive.CellProps & { /** ✦ depth: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation; } = $props();
const group = getContext<(() => Elevation) | undefined>("inputOTPElevation"); const level = useElevation(() => elevation ?? group?.(), "field");
// ✦ depth (v4): slots sink (-1) in layered mode; the active slot swaps the edge for the ring const fieldElevation = { sunken: "rounded-none border-0 border-l border-border bg-transparent shadow-none first:border-l-0 data-[active=true]:rounded-md data-[active=true]:border-transparent data-[active=true]:shadow-[0_0_0_3px_var(--ring-soft),inset_0_0_0_1px_var(--ring)]", flat: "", raised: "rounded-none border-0 border-l border-border bg-transparent shadow-none first:border-l-0 data-[active=true]:rounded-md data-[active=true]:border-transparent data-[active=true]:shadow-[0_0_0_3px_var(--ring-soft),inset_0_0_0_1px_var(--ring)]", floating: "rounded-none border-0 border-l border-border bg-transparent shadow-none first:border-l-0 data-[active=true]:rounded-md data-[active=true]:border-transparent data-[active=true]:shadow-[0_0_0_3px_var(--ring-soft),inset_0_0_0_1px_var(--ring)]", };</script>
<InputOTPPrimitive.Cell {cell} bind:ref data-slot="input-otp-slot" class={cn( "relative flex size-10 items-center justify-center rounded-md border border-input bg-card font-mono text-sm text-foreground outline-none aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:shadow-ring data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:shadow-ring-error", fieldElevation[level.current], className )} {...restProps}> {cell.char} {#if cell.hasFakeCaret} <div class="pointer-events-none absolute inset-0 flex items-center justify-center" > <div class="h-4 w-px animate-pulse bg-foreground duration-1000"></div> </div> {/if}</InputOTPPrimitive.Cell><script lang="ts"> import { PinInput as InputOTPPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js"; import { type Elevation } from "$lib/registry/ui/elevation/index.js"; import { setContext } from "svelte";
let { ref = $bindable(null), class: className, value = $bindable(""), elevation = "auto", ...restProps }: InputOTPPrimitive.RootProps & { /** ✦ depth for every slot: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation; } = $props();
setContext("inputOTPElevation", () => elevation);</script>
<InputOTPPrimitive.Root bind:ref bind:value data-slot="input-otp" spellcheck={false} class={cn( "flex items-center gap-2 has-disabled:opacity-50", "disabled:cursor-not-allowed", className )} {...restProps}/>import Root from "./input-otp.svelte";import Group from "./input-otp-group.svelte";import Separator from "./input-otp-separator.svelte";import Slot from "./input-otp-slot.svelte";
export { Group, Group as InputOTPGroup, Root, Root as InputOTP, Separator, Separator as InputOTPSeparator, Slot, Slot as InputOTPSlot,};Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Set elevation on InputOTP for every slot, or on a single InputOTPSlot. sunken is the layered default; the active slot swaps its edge for the ring.
Sunken (-1)
Flat (0)
Raised (+1)
Floating (+2)
Sunken (-1)
Flat (0)
Raised (+1)
Floating (+2)
Sunken (-1)
Flat (0)
Raised (+1)
Floating (+2)
import { InputOTP, InputOTPGroup, InputOTPSlot,} from "@/components/ui/input-otp";import { REGEXP_ONLY_DIGITS } from "input-otp";
const levels = [ { value: "sunken", label: "Sunken (-1)" }, { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" },] as const;
export default function Demo() { return ( <div className="flex flex-col gap-5"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <InputOTP maxLength={4} pattern={REGEXP_ONLY_DIGITS} elevation={value} > <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> <InputOTPSlot index={3} /> </InputOTPGroup> </InputOTP> </div> ))} </div> );}<script setup lang="ts">import { REGEXP_ONLY_DIGITS } from "vue-input-otp";import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
const levels = [{ value: "sunken", label: "Sunken (-1)" }, { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }];</script>
<template> <div class="flex flex-col gap-5"> <div v-for="level in levels" :key="level.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ level.label }}</p> <InputOTP :maxlength="4" :pattern="REGEXP_ONLY_DIGITS" :elevation="level.value"> <template #default="{ slots }"> <InputOTPGroup> <InputOTPSlot v-for="(_, i) in slots" :key="i" :index="i" /> </InputOTPGroup> </template> </InputOTP> </div> </div></template><script lang="ts"> import { REGEXP_ONLY_DIGITS } from "bits-ui"; import { InputOTP, InputOTPGroup, InputOTPSlot } from "$lib/components/ui/input-otp";
const levels = [{ value: "sunken", label: "Sunken (-1)" }, { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }] as const;</script>
<div class="flex flex-col gap-5"> {#each levels as level (level.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{level.label}</p> <InputOTP maxlength={4} pattern={REGEXP_ONLY_DIGITS} elevation={level.value}> {#snippet children({ cells })} <InputOTPGroup> {#each cells as cell, i (i)} <InputOTPSlot {cell} /> {/each} </InputOTPGroup> {/snippet} </InputOTP> </div> {/each}</div>| Prop | Type | Default |
|---|---|---|
| maxLength | number |
required |
| pattern | string |
none |
| InputOTPSlot index | number |
required |
elevation |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
✦ Edmi additions
Section titled “✦ Edmi additions”- Slots are separate boxes; the active slot gets the focus halo.