- Components
Input OTP
A code split into boxes that behave as one field: paste fills them all, Backspace goes back, autofill works.
npx shadcn add @klarhimmel/input-otpWhen to use
Use for
A short code that was sent to the person: six digits by email or text, a booking reference.
Not for
A long secret or a password. Use Input.
Pairs with
Button, a resend link, a status line.
Preview
Enter the six-digit code we emailed you
0 of 6 digits.
input-otp.tsx
import { Theme } from '@/components/klarhimmel/theme/style';
import { InputOtp } from '@/components/klarhimmel/ui/input-otp';
export default function Example() {
return (
<Theme theme="brand">
<InputOtp aria-label="Verification code" onComplete={(code) => console.log(code)} />
</Theme>
);
}Install
Import
import { InputOtp } from '@/components/klarhimmel/ui/input-otp';- Lands in
- components/klarhimmel/ui/input-otp.tsx, plus cn() and the theme item if they are missing. Import theme.css after Tailwind.
- Render in
- Render it inside <Theme>, once near the root of your app. Tokens and fonts are scoped to it, so outside it the component is unstyled.
Examples
States
Wrong code
That code is not right or has expired. Ask for a new one.
Verifying
import { InputOtp } from '@/components/klarhimmel/ui/input-otp'; export default function InputOtpStates() { return ( <div className="flex w-[min(24rem,calc(100cqw-3rem))] flex-col gap-6"> <div className="flex flex-col gap-2"> <p id="otp-err" className="text-kh-small font-medium">Wrong code</p> <InputOtp aria-labelledby="otp-err" aria-describedby="otp-err-msg" defaultValue="123456" invalid /> <p id="otp-err-msg" className="text-kh-small text-kh-ink">That code is not right or has expired. Ask for a new one.</p> </div> <div className="flex flex-col gap-2"> <p id="otp-off" className="text-kh-small font-medium">Verifying</p> <InputOtp aria-labelledby="otp-off" defaultValue="482913" disabled /> </div> </div> ); }Form
import { useState } from 'react'; import { Button } from '@/components/klarhimmel/ui/button'; import { InputOtp } from '@/components/klarhimmel/ui/input-otp'; export default function InputOtpForm() { const [code, setCode] = useState(''); const [sent, setSent] = useState(''); return ( <form onSubmit={(e) => { e.preventDefault(); setSent(code); }} className="flex w-[min(24rem,calc(100cqw-3rem))] flex-col gap-3" > <p id="otp-form" className="text-kh-small font-medium">Booking reference</p> <InputOtp aria-labelledby="otp-form" type="alphanumeric" length={5} name="reference" value={code} onValueChange={(v) => setCode(v.toUpperCase())} /> <Button type="submit" disabled={code.length < 5}>Find booking</Button> <p role="status" className="text-kh-small text-kh-muted">{sent ? `Looking for ${sent}.` : 'Letters and digits. Paste works too.'}</p> </form> ); }
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | 6 | How many boxes. |
value / defaultValue / onValueChange | string / (value: string) => void | - | Controlled or uncontrolled. The value is one string without gaps. |
onComplete | (value: string) => void | - | Called once when every box is filled. |
type | 'numeric' | 'alphanumeric' | 'numeric' | Digits only (with the numeric keypad), or letters and digits. |
name | string | - | Submits the whole code with a form (a hidden input). |
invalid / disabled / readOnly | boolean | false | Error styling on every box; switches them off. |
aria-label / aria-labelledby | string | - | Required: names the group. |
boxLabel | (n: number, of: number) => string | 'Digit n of N' | The name of each box. Pass it translated. |
className | string | - | Extra classes, merged after the component's own. |
Accessibility
| Key | Does |
|---|---|
Type | Fills the box and moves to the next; letters are ignored in numeric mode |
Paste | Fills from the box you pasted into, ignoring spaces |
Backspace | Clears the box; in an empty box removes the one before and moves back |
Delete | Removes the character in the box |
Left / Right / Home / End | Move between boxes |
The first box carries autocomplete one-time-code, so phones offer the code from a message. Each box is named "Digit n of N".
Accessibility