Jack UI logoJackUI
ComponentsOTP Input

Underlined OTP Input

A sleek, minimalist bottom-border verification input featuring an expanding focus accent line, sliding character spring physics, and mobile SMS authentication styling.

Open in

SMS Authentication

Enter your code or test with

Request new code in 30s

Component Overview & Selection Guide

Use the Underlined OTP Input for mobile-first auth flows, lightweight SMS OTP verification screens, and minimalist web applications that favor understated, modern line aesthetics.

Primary Use Cases:

  • Mobile SMS Verification: Minimalist phone number verification cards with crisp typography.
  • Minimal Auth Modals: Lightweight slide-over panels or dialogs where borderless inputs reduce visual clutter.
  • Quick 4 or 6-Digit Passcodes: Streamlined authentication screens.

Features

  • Expanding Accent Underline: Floating focus underline smoothly stretches under the active digit slot.
  • Subtle Drop Physics: Numbers slide into place from above with spring physics.
  • Dynamic Colored Underlines: Instantly turns red on error or emerald green on completion.
  • Mobile Autofill Ready: Fully compatible with mobile SMS verification code auto-detection.

Installation

bunx shadcn@latest add https://jackui.com/r/underlined-otp-input.json
npx shadcn@latest add https://jackui.com/r/underlined-otp-input.json
pnpm dlx shadcn@latest add https://jackui.com/r/underlined-otp-input.json

The CLI will automatically install the motion and lucide-react dependencies and copy the component into your project at:

components/jackui/otp-input/underlined-otp-input.tsx

Usage

"use client";

import React, { useState } from "react";
import { UnderlinedOtpInput } from "@/components/jackui/otp-input/underlined-otp-input";

export default function UnderlinedOtpVerification() {
  const [code, setCode] = useState("");
  const [isError, setIsError] = useState(false);
  const [isSuccess, setIsSuccess] = useState(false);

  return (
    <div className="flex flex-col items-center justify-center p-8">
      <UnderlinedOtpInput
        length={6}
        value={code}
        onChange={setCode}
        onComplete={(val) => {
          if (val === "789012") setIsSuccess(true);
          else setIsError(true);
        }}
        isError={isError}
        isSuccess={isSuccess}
        autoFocus
      />
    </div>
  );
}

Props

PropTypeDescription
lengthnumberOptional. Total number of digit slots (typically 4, 6, or 8). Default: 6
valuestringOptional. Controlled input value string.
onChange(value: string) => voidOptional. Callback fired on input value change.
onComplete(value: string) => voidOptional. Callback fired when all slots are filled.
type"number" | "text" | "password"Optional. Input character filter type. Default: "number"
maskbooleanOptional. Whether to mask characters with bullet dots. Default: false
disabledbooleanOptional. Disables input interaction and dims slots. Default: false
autoFocusbooleanOptional. Automatically focuses the first slot on mount. Default: false
isErrorbooleanOptional. Displays error state with shake physics and red underline. Default: false
isSuccessbooleanOptional. Displays emerald success underline feedback. Default: false
size"sm" | "md" | "lg"Optional. Size scale of the input slots. Default: "md"
placeholderstringOptional. Character placeholder for empty slots. Default: ""
classNamestringOptional. Additional outer container CSS class names.
slotClassNamestringOptional. Additional individual slot CSS class names.
refReact.Ref<UnderlinedOtpInputRef>Optional. Imperative ref handle exposing focus(), blur(), and clear() methods.