A digits-only PIN in input-otp
The pattern prop restricts what a code may contain, which is what you want for numeric PINs and SMS codes.
How it works
- Use a ready-made pattern.
REGEXP_ONLY_DIGITSis imported frominput-otpand passed aspattern, so no regex literal is needed in the JSX. [1] - Invalid input is dropped. Typing a letter does nothing: the change fails the pattern and is rejected as a whole. [1]
- Four separate boxes. With
maxLength={4}the render prop draws four cells with gaps, the PIN shape from the docs' examples, and the value is mirrored below the field. [2]
Run and edit it
It runs in your browser, in a sandboxed frame that cannot see this site; the packages load from esm.sh only after you press Run.
import { useState } from "react";
import { OTPInput, REGEXP_ONLY_DIGITS } from "input-otp";
const CSS = `
.pin-row { display: flex; gap: 12px; }
.pin-cell { position: relative; width: 56px; height: 56px; border-radius: 12px; display: flex; align-items: center;
justify-content: center; border: 1px solid #3a3f4b; background: #1c1f26; color: #f2f4f8;
font: 600 26px system-ui, sans-serif; transition: all .15s; }
.pin-cell.active { border-color: #6c8cff; box-shadow: 0 0 0 3px rgba(108,140,255,.25); }
.pin-caret { position: absolute; width: 2px; height: 24px; background: #f2f4f8; animation: blink 1s step-end infinite; }
@keyframes blink { 50% { opacity: 0; } }
`;
export default function App() {
const [pin, setPin] = useState("");
return (
<div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", background: "#111", fontFamily: "system-ui, sans-serif" }}>
<style>{CSS}</style>
<div style={{ background: "#16181d", padding: "26px 32px", borderRadius: 14, color: "#e8eaf0", boxShadow: "0 10px 30px rgba(0,0,0,.5)" }}>
<h3 style={{ margin: "0 0 6px", fontSize: 18 }}>Enter your PIN</h3>
<p style={{ margin: "0 0 18px", fontSize: 13, color: "#a9b0c0" }}>Try typing letters: only digits get in.</p>
<OTPInput
maxLength={4}
pattern={REGEXP_ONLY_DIGITS}
value={pin}
onChange={setPin}
autoFocus
render={({ slots }) => (
<div className="pin-row">
{slots.map((slot, idx) => (
<div key={idx} className={slot.isActive ? "pin-cell active" : "pin-cell"}>
{slot.char}
{slot.hasFakeCaret && <div className="pin-caret" />}
</div>
))}
</div>
)}
/>
<p style={{ margin: "16px 0 0", fontSize: 13, color: "#a9b0c0" }}>
Value: <b style={{ color: "#9fe6a0", letterSpacing: 2 }}>{pin.padEnd(4, "_")}</b>
</p>
</div>
</div>
);
}
Type digits and letters; only the digits appear, and the value line updates as you go. Code written by AgentGid [1].
Type digits and letters; only the digits appear, and the value line updates as you go. Code written by AgentGid [1].Checked Oct 12, 2026: it starts without errors in Chromium with input-otp 1.5.0, react 19.3.0, react-dom 19.3.0. Needs WebGL.
More input-otp examples
A basic OTP input with input-otp Auto-submit a code with onComplete Two slot groups with a dash in input-otp Alphanumeric codes and paste cleanup in input-otp
Sources
Explanations written by AgentGid from the official docs; the numbers in brackets point here. The code marked "written by AgentGid" is ours and free to copy.