Docs
   OTP Field 
 OTP Field
An accessible and customizable OTP Input component.
import {
	OTPField,
	OTPFieldGroup,
	OTPFieldInput,
	OTPFieldSeparator,
	OTPFieldSlot,
} from "@repo/tailwindcss/ui/otp-field";
 
const OtpFieldDemo = () => {
	return (
		<OTPField maxLength={6}>
			<OTPFieldInput />
			<OTPFieldGroup>
				<OTPFieldSlot index={0} />
				<OTPFieldSlot index={1} />
				<OTPFieldSlot index={2} />
			</OTPFieldGroup>
			<OTPFieldSeparator />
			<OTPFieldGroup>
				<OTPFieldSlot index={3} />
				<OTPFieldSlot index={4} />
				<OTPFieldSlot index={5} />
			</OTPFieldGroup>
		</OTPField>
	);
};
 
export default OtpFieldDemo; Installation
npx shadcn-solid@latest add otp-field  Install the following dependencies:
npm install @corvu/otp-field  Copy and paste the following code into your project:
import { cn } from "@/libs/cn";
import type { DynamicProps, RootProps } from "@corvu/otp-field";
import OTPFieldPrimitive from "@corvu/otp-field";
import type { ComponentProps, ValidComponent } from "solid-js";
import { Show, splitProps } from "solid-js";
 
export const OTPFieldInput = OTPFieldPrimitive.Input;
 
type OTPFieldProps<T extends ValidComponent = "div"> = RootProps<T> & {
	class?: string;
};
 
export const OTPField = <T extends ValidComponent = "div">(
	props: DynamicProps<T, OTPFieldProps<T>>,
) => {
	const [local, rest] = splitProps(props, ["class"]);
 
	return (
		<OTPFieldPrimitive
			class={cn(
				"flex items-center gap-2 has-[:disabled]:opacity-50",
				local.class,
			)}
			{...rest}
		/>
	);
};
 
export const OTPFieldGroup = (props: ComponentProps<"div">) => {
	const [local, rest] = splitProps(props, ["class"]);
 
	return <div class={cn("flex items-center", local.class)} {...rest} />;
};
 
export const OTPFieldSeparator = (props: ComponentProps<"div">) => {
	return (
		// biome-ignore lint/a11y/useAriaPropsForRole: []
		<div role="separator" {...props}>
			<svg
				xmlns="http://www.w3.org/2000/svg"
				class="size-4"
				viewBox="0 0 15 15"
			>
				<title>Separator</title>
				<path
					fill="currentColor"
					fill-rule="evenodd"
					d="M5 7.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5"
					clip-rule="evenodd"
				/>
			</svg>
		</div>
	);
};
 
export const OTPFieldSlot = (
	props: ComponentProps<"div"> & { index: number },
) => {
	const [local, rest] = splitProps(props, ["class", "index"]);
	const context = OTPFieldPrimitive.useContext();
	const char = () => context.value()[local.index];
	const hasFakeCaret = () =>
		context.value().length === local.index && context.isInserting();
	const isActive = () => context.activeSlots().includes(local.index);
 
	return (
		<div
			class={cn(
				"relative flex size-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-shadow first:rounded-l-md first:border-l last:rounded-r-md",
				isActive() && "z-10 ring-[1.5px] ring-ring",
				local.class,
			)}
			{...rest}
		>
			{char()}
			<Show when={hasFakeCaret()}>
				<div class="pointer-events-none absolute inset-0 flex items-center justify-center">
					<div class="h-4 w-px animate-caret-blink bg-foreground" />
				</div>
			</Show>
		</div>
	);
}; Update config:
/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    extend: {
      keyframes: {
        "accordion-down": {
          from: { height: 0 },
          to: { height: "var(--kb-accordion-content-height)" }
        },
        "accordion-up": {
          from: { height: "var(--kb-accordion-content-height)" },
          to: { height: 0 }
        }
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out"
      }
    }
  }
}; Install the following dependencies:
npm install @corvu/otp-field  Copy and paste the following code into your project:
import { cn } from "@/libs/cn";
import type { DynamicProps, RootProps } from "@corvu/otp-field";
import OTPFieldPrimitive from "@corvu/otp-field";
import type { ComponentProps, ValidComponent } from "solid-js";
import { Show, splitProps } from "solid-js";
 
export const OTPFieldInput = OTPFieldPrimitive.Input;
 
type OTPFieldProps<T extends ValidComponent = "div"> = RootProps<T> & {
	class?: string;
};
 
export const OTPField = <T extends ValidComponent = "div">(
	props: DynamicProps<T, OTPFieldProps<T>>,
) => {
	const [local, rest] = splitProps(props, ["class"]);
 
	return (
		<OTPFieldPrimitive
			class={cn(
				"flex items-center gap-2 has-[:disabled]:opacity-50",
				local.class,
			)}
			{...rest}
		/>
	);
};
 
export const OTPFieldGroup = (props: ComponentProps<"div">) => {
	const [local, rest] = splitProps(props, ["class"]);
 
	return <div class={cn("flex items-center", local.class)} {...rest} />;
};
 
export const OTPFieldSeparator = (props: ComponentProps<"div">) => {
	return (
		// biome-ignore lint/a11y/useAriaPropsForRole: []
		<div role="separator" {...props}>
			<svg
				xmlns="http://www.w3.org/2000/svg"
				class="size-4"
				viewBox="0 0 15 15"
			>
				<title>Separator</title>
				<path
					fill="currentColor"
					fill-rule="evenodd"
					d="M5 7.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5"
					clip-rule="evenodd"
				/>
			</svg>
		</div>
	);
};
 
export const OTPFieldSlot = (
	props: ComponentProps<"div"> & { index: number },
) => {
	const [local, rest] = splitProps(props, ["class", "index"]);
	const context = OTPFieldPrimitive.useContext();
	const char = () => context.value()[local.index];
	const hasFakeCaret = () =>
		context.value().length === local.index && context.isInserting();
	const isActive = () => context.activeSlots().includes(local.index);
 
	return (
		<div
			class={cn(
				"relative flex size-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:(rounded-l-md border-l) last:rounded-r-md",
				isActive() && "z-10 ring-1.5 ring-ring",
				local.class,
			)}
			{...rest}
		>
			{char()}
			<Show when={hasFakeCaret()}>
				<div class="pointer-events-none absolute inset-0 flex items-center justify-center">
					<div class="h-4 w-px animate-caret-blink bg-foreground" />
				</div>
			</Show>
		</div>
	);
}; Update config:
export default defineConfig({
  themes: {
    animation: {
      keyframes: {
        "caret-blink": "{ 0%,70%,100% { opacity: 1 } 20%,50% { opacity: 0 } }"
      },
      timingFns: {
        "caret-blink": "ease-out"
      },
      durations: {
        "caret-blink": "1.25s"
      },
      counts: {
        "caret-blink": "infinite"
      }
    }
  }
}); Usage
import {
  OTPField,
  OTPFieldGroup,
  OTPFieldInput,
  OTPFieldSeparator,
  OTPFieldSlot
} from "@/components/ui/otp-field"; <OTPField maxLength={6}>
  <OTPFieldInput />
  <OTPFieldGroup>
    <OTPFieldSlot index={0} />
    <OTPFieldSlot index={1} />
    <OTPFieldSlot index={2} />
  </OTPFieldGroup>
  <OTPFieldSeparator />
  <OTPFieldGroup>
    <OTPFieldSlot index={3} />
    <OTPFieldSlot index={4} />
    <OTPFieldSlot index={5} />
  </OTPFieldGroup>
</OTPField> Exmaples
Pattern
 Use the pattern prop to define a custom pattern for the OTP field. 
import {
	OTPField,
	OTPFieldGroup,
	OTPFieldInput,
	OTPFieldSlot,
} from "@repo/tailwindcss/ui/otp-field";
 
const OTPFieldWithPatternDemo = () => {
	return (
		<OTPField maxLength={6}>
			<OTPFieldInput pattern="^[a-zA-Z0-9]*$" />
			<OTPFieldGroup>
				<OTPFieldSlot index={0} />
				<OTPFieldSlot index={1} />
				<OTPFieldSlot index={2} />
				<OTPFieldSlot index={3} />
				<OTPFieldSlot index={4} />
				<OTPFieldSlot index={5} />
			</OTPFieldGroup>
		</OTPField>
	);
};
 
export default OTPFieldWithPatternDemo; Controlled
 You can use the value and onValueChange props to control the input value. 
import {
	OTPField,
	OTPFieldGroup,
	OTPFieldInput,
	OTPFieldSlot,
} from "@repo/tailwindcss/ui/otp-field";
import { Show, createSignal } from "solid-js";
 
const OtpFieldWithControlledDemo = () => {
	const [value, setValue] = createSignal<string>();
 
	return (
		<div class="flex flex-col items-center gap-2">
			<OTPField maxLength={6} value={value()} onValueChange={setValue}>
				<OTPFieldInput />
				<OTPFieldGroup>
					<OTPFieldSlot index={0} />
					<OTPFieldSlot index={1} />
					<OTPFieldSlot index={2} />
					<OTPFieldSlot index={3} />
					<OTPFieldSlot index={4} />
					<OTPFieldSlot index={5} />
				</OTPFieldGroup>
			</OTPField>
			<span class="text-center text-sm">
				<Show fallback="Enter your one-time password." when={value()}>
					You entered: {value()}
				</Show>
			</span>
		</div>
	);
};
 
export default OtpFieldWithControlledDemo;