shadcn/ui Input OTP, tek kullanımlık şifre ve doğrulama kodu girişi için kutucuklu bir giriş bileşenidir. npx shadcn@latest add input-otp ile eklenir; InputOTP kök bileşenine maxLength verilir, kutucuklar InputOTPGroup içinde InputOTPSlot index={...} olarak dizilir ve gruplar arasına isteğe bağlı InputOTPSeparator konur. Belge bileşeni kopyala yapıştır desteği olan erişilebilir bir OTP bileşeni olarak tanımlar.

Bileşen, guilhermerodz tarafından geliştirilen input-otp paketinin üzerine kurulmuştur. Bu yazıda kurulumu, kaynak kodun nasıl çalıştığını, pattern ile karakter kısıtlamayı, ayırıcıları, kontrollü kullanımı, formlarla bağlantıyı, hata ve devre dışı görünümünü ve depodaki OTP bloklarını anlatıyoruz. Seri genel bakışı için shadcn/ui öğrenme rehberi.

Kurulum

Kurulum
bash
npx shadcn@latest add input-otp

Elle kurulumda belge önce npm install input-otp komutunu, ardından bileşen kaynağını components/ui/input-otp.tsx dosyasına kopyalamayı söyler. Kayıt dosyasına göre bileşenin tek paket bağımlılığı input-otp paketidir; ayırıcıdaki çizgi ikonu lucide-react paketinden gelir. Belgenin React 19 uyumluluk tablosunda input-otp sorunsuz çalışan paketler arasında listelenir (React 19 yazısı).

Kullanım için dört parça import edilir:

typescript
import {
  InputOTP,
  InputOTPGroup,
  InputOTPSeparator,
  InputOTPSlot,
} from "@/components/ui/input-otp"

Temel kullanım: altı haneli kod

Belgedeki temel örnek altı kutucuğu üçerli iki gruba böler ve araya bir ayırıcı koyar:

typescript
<InputOTP maxLength={6}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
  </InputOTPGroup>
  <InputOTPSeparator />
  <InputOTPGroup>
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>

Bu yapıda dikkat edilecek iki kural vardır. maxLength kaç karakter girileceğini belirler ve kutucuk sayısıyla eşleşmelidir. Her InputOTPSlot bir index alır ve bu indeks sıfırdan başlar; altı haneli bir kodda 0 ile 5 arasıdır. Gruplar yalnızca görseldir: altı kutuyu tek grupta da, üç ayrı grupta da yazabilirsiniz, girilen kod yine tek bir değerdir.

Kaynak kod nasıl çalışıyor?

components/ui/input-otp.tsx dosyası kısa ama öğreticidir. InputOTP, input-otp paketindeki OTPInput bileşenini sarar ve iki sınıf alanı sunar: containerClassName kutucukları saran kaba, className ise gerçek input öğesine uygulanır. Kaba varsayılan olarak flex items-center gap-2 has-disabled:opacity-50 verilir; yani içindeki input devre dışıysa tüm bileşen soluklaşır. Diğer bütün özellikler OTPInput bileşenine aktarılır; maxLength, pattern, value, onChange, disabled gibi değerler bu yolla paketin kendisine gider.

InputOTPSlot kendi başına bir input değildir; bir div çizer. Hangi karakteri göstereceğini OTPInputContext bağlamından, kendi index değerine karşılık gelen kayıttan okur: char kutudaki karakter, isActive kutunun o an etkin olup olmadığı, hasFakeCaret ise yanıp sönen sahte imlecin gösterilip gösterilmeyeceğidir. Etkin kutu data-active özniteliğini alır ve sınıflarda bu duruma halka (ring) ve kenarlık rengi bağlanmıştır. Sahte imleç animate-caret-blink sınıfıyla çizilir. İlk ve son kutunun köşeleri first:rounded-l-md ve last:rounded-r-md ile yuvarlatılır; bu yüzden bir gruptaki kutular bitişik görünür.

InputOTPSeparator ise role="separator" taşıyan ve içinde lucide MinusIcon bulunan bir div üretir. Her parça bir data-slot özniteliği taşır: input-otp, input-otp-group, input-otp-slot, input-otp-separator. Bu öznitelikler aşağıda göreceğimiz stil özelleştirmelerinde işe yarar (data-slot ve cn() yazısı).

ParçaÜrettiğiÖnemli özellikler
InputOTPOTPInput (input-otp paketi)maxLength, pattern, value, onChange, disabled, containerClassName
InputOTPGroupdivclassName
InputOTPSlotdiv (karakter kutusu)index (zorunlu), aria-invalid, className
InputOTPSeparatordiv, role="separator"div özellikleri

pattern ile karakter kısıtlamak

Varsayılan davranışın ötesinde hangi karakterlerin kabul edileceğini pattern özelliği belirler. Belge, harf ve rakamları birlikte kabul eden bir örnek gösterir; sabit input-otp paketinden import edilir:

typescript
"use client"

import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"

import {
  InputOTP,
  InputOTPGroup,
  InputOTPSlot,
} from "@/components/ui/input-otp"

export function HarfRakamKodu() {
  return (
    <InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
      <InputOTPGroup>
        <InputOTPSlot index={0} />
        <InputOTPSlot index={1} />
        <InputOTPSlot index={2} />
        <InputOTPSlot index={3} />
        <InputOTPSlot index={4} />
        <InputOTPSlot index={5} />
      </InputOTPGroup>
    </InputOTP>
  )
}

Depodaki yeni stil örneklerinde (registry/bases/radix/examples/input-otp-example.tsx) yalnızca rakam kabul eden REGEXP_ONLY_DIGITS sabiti de kullanılır; örneğin dört haneli bir PIN alanı maxLength={4} pattern={REGEXP_ONLY_DIGITS} ile yazılmıştır. Bu iki sabitin dışındaki seçenekler ve tam davranış için input-otp paketinin kendi belgesine bakın. pattern istemci tarafındaki bir giriş kısıtıdır; kodun doğruluğunu yine sunucuda denetlemeniz gerekir.

Ayırıcılar ve farklı uzunluklar

Ayırıcı istediğiniz sayıda kullanılabilir. Belgedeki ayırıcı örneği altı kutuyu ikişerli üç gruba böler; metin içindeki kısa örnek ise dört haneli kodu iki gruba ayırır:

typescript
<InputOTP maxLength={4}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
  </InputOTPGroup>
  <InputOTPSeparator />
  <InputOTPGroup>
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
  </InputOTPGroup>
</InputOTP>

Ayırıcı, uzun bir kodu göz için parçalara bölen görsel bir öğedir; kaynakta yalnızca bir ikon çizer ve girilen değere herhangi bir karakter eklemez. maxLength değerini değiştirdiğinizde kutucuk sayısını ve indeksleri de güncellemeyi unutmayın.

Kontrollü kullanım

Kontrollü kullanım

Değeri kendi durumunuzda tutmak için value ve onChange birlikte verilir. Resmî kontrollü örnek, kod girildikçe altında bir metin gösterir:

typescript
"use client"

import * as React from "react"

import {
  InputOTP,
  InputOTPGroup,
  InputOTPSlot,
} from "@/components/ui/input-otp"

export function KontrolluKod() {
  const [value, setValue] = React.useState("")

  return (
    <div className="space-y-2">
      <InputOTP
        maxLength={6}
        value={value}
        onChange={(value) => setValue(value)}
      >
        <InputOTPGroup>
          <InputOTPSlot index={0} />
          <InputOTPSlot index={1} />
          <InputOTPSlot index={2} />
          <InputOTPSlot index={3} />
          <InputOTPSlot index={4} />
          <InputOTPSlot index={5} />
        </InputOTPGroup>
      </InputOTP>
      <div className="text-center text-sm">
        {value === "" ? (
          <>Tek kullanımlık şifrenizi girin.</>
        ) : (
          <>Girdiğiniz kod: {value}</>
        )}
      </div>
    </div>
  )
}

onChange bir olay nesnesi değil, doğrudan güncel metni verir; bu yüzden setValue fonksiyonuna olduğu gibi aktarılabilir. Depodaki başka bir örnek bunu onChange={setValue} kısaltmasıyla yazar. Değer her zaman kutulardaki karakterlerin birleşimi olan tek bir metindir; altı haneli bir kod tamamlandığında uzunluğu altıdır. Kod tamamlanınca otomatik gönderim gibi davranışlar istiyorsanız value.length değerini kendi kodunuzda izleyebilirsiniz; belge bunun için hazır bir örnek vermez.

Kontrolsüz kullanımda da bileşen bir form içinde çalışır. Depodaki OTP blokları alanı id="otp" ve required özellikleriyle düz bir form içinde kullanır ve etiketini FieldLabel htmlFor="otp" ile bağlar.

Etiketleme ve erişilebilirlik

Kutucuklar div olduğu için etiket kutulara değil, bileşenin kendisine bağlanır. Depodaki OTP bloklarında InputOTP bileşenine id="otp" verilir ve FieldLabel htmlFor="otp" ile eşleştirilir; kaynakta id de diğer özelliklerle birlikte OTPInput bileşenine aktarılır. Sayfada zaten büyük bir "Enter verification code" başlığı bulunan bloklarda etikete className="sr-only" verilir: etiket görünmez ama ekran okuyucular için alanın adı korunur.

Alanın altına FieldDescription ile kodun nereye gönderildiğini yazmak da bloklarda tekrar eden bir kalıptır; örneğin altı haneli kodun e-posta adresinize gönderildiğini söyleyen bir cümle. Hata durumunda FieldError kullanırsanız bileşen role="alert" ile çizildiği için mesaj duyurulabilir. Erişilebilirlik kavramlarının genel çerçevesi için WCAG temelleri yazısına bakabilirsiniz.

Formda kullanmak

Belgenin form örneği (input-otp-form) eski Form bileşeni ve React Hook Form ile yazılmıştır. Şema, pin değerinin altı karakter olmasını ister ve bileşen FormControl içinde {...field} yayılımıyla bağlanır:

typescript
const FormSchema = z.object({
  pin: z.string().min(6, {
    message: "Tek kullanımlık şifreniz 6 karakter olmalı.",
  }),
})

// ...
<FormField
  control={form.control}
  name="pin"
  render={({ field }) => (
    <FormItem>
      <FormLabel>Tek kullanımlık şifre</FormLabel>
      <FormControl>
        <InputOTP maxLength={6} {...field}>
          <InputOTPGroup>
            <InputOTPSlot index={0} />
            <InputOTPSlot index={1} />
            <InputOTPSlot index={2} />
            <InputOTPSlot index={3} />
            <InputOTPSlot index={4} />
            <InputOTPSlot index={5} />
          </InputOTPGroup>
        </InputOTP>
      </FormControl>
      <FormDescription>Telefonunuza gönderilen kodu girin.</FormDescription>
      <FormMessage />
    </FormItem>
  )}
/>

Yayılımın çalışmasının nedeni, bileşenin onChange fonksiyonunun doğrudan metin vermesidir; React Hook Form da field.onChange fonksiyonuna değeri doğrudan kabul eder. Belgenin Form sayfası bu bileşenin artık aktif geliştirilmediğini ve Field bileşeninin önerildiğini söyler. Yeni bir formda aynı bağlantıyı Controller ve Field ile kurabilirsiniz; kalıp React Hook Form yazısında, TanStack karşılığı (value={field.state.value} ve onChange={field.handleChange}) ise TanStack Form yazısında anlatılıyor.

Hata ve devre dışı görünümü

Kutucuk sınıflarında aria-invalid durumuna bağlı kırmızı kenarlık ve halka tanımlıdır. Depodaki yeni stil örneği hata durumunu, her kutuya aria-invalid vererek ve altına FieldError koyarak gösterir:

typescript
<Field>
  <FieldLabel htmlFor="kod-hatali">Doğrulama kodu</FieldLabel>
  <InputOTP id="kod-hatali" maxLength={6} value={value} onChange={setValue}>
    <InputOTPGroup>
      <InputOTPSlot index={0} aria-invalid />
      <InputOTPSlot index={1} aria-invalid />
      <InputOTPSlot index={2} aria-invalid />
    </InputOTPGroup>
    <InputOTPSeparator />
    <InputOTPGroup>
      <InputOTPSlot index={3} aria-invalid />
      <InputOTPSlot index={4} aria-invalid />
      <InputOTPSlot index={5} aria-invalid />
    </InputOTPGroup>
  </InputOTP>
  <FieldError errors={[{ message: "Kod geçersiz. Lütfen tekrar deneyin." }]} />
</Field>

Devre dışı durum için disabled özelliği InputOTP bileşenine verilir. Kaynakta kabın has-disabled:opacity-50 sınıfı tüm bileşeni soluklaştırır, input da disabled:cursor-not-allowed alır. Belgenin değişiklik geçmişindeki 19 Mart 2024 tarihli "Disabled" notu, eski forwardRef sürümlü dosyaya bu sınıfların nasıl ekleneceğini gösterir; depodaki güncel dosyada bunlar zaten vardır.

Görünümü özelleştirmek

Bileşen dosyası sizin projenizde olduğu için doğrudan düzenleyebilirsiniz, ama çoğu durumda dışarıdan sınıf vermek yeterlidir. Depodaki OTP blokları iki yöntem kullanır. Birincisi, kutuları birbirinden ayırmak için gruba bir aralık ve her kutuya ayrı kenarlık veren sınıflar eklemektir: *:data-[slot=input-otp-slot]:rounded-md *:data-[slot=input-otp-slot]:border ve gap-2.5. Bu seçici, grubun doğrudan çocukları arasındaki data-slot="input-otp-slot" öğelerini hedefler. İkincisi, gruplar arasındaki boşluğu containerClassName="gap-4" ile büyütmektir.

typescript
<InputOTP maxLength={6} id="otp" required containerClassName="gap-4">
  <InputOTPGroup className="gap-2.5 *:data-[slot=input-otp-slot]:rounded-md *:data-[slot=input-otp-slot]:border">
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>

Kutu boyutu kaynakta h-9 w-9 olarak tanımlıdır; tek bir kutuyu büyütmek için InputOTPSlot bileşenine className verebilirsiniz, cn() çakışan sınıfı sizinkiyle değiştirir. Renkler tema değişkenlerinden (border-input, ring-ring, destructive) gelir; tema düzeni tema yazısında anlatılıyor.

Hazır OTP blokları

Depoda otp-01 ile otp-05 arasında beş doğrulama sayfası bloğu bulunur. Blok listesindeki açıklamalara göre bunlar basit bir OTP formu, kapak görselli iki sütunlu sayfa, soluk arka planlı sayfa, form ve görselli sayfa ve sosyal sağlayıcılı basit bir formdur. Hepsi Card, Field ve Input OTP bileşenleriyle yazılmıştır. Blokların CLI ile nasıl eklendiği Blocks yazısında anlatılıyor.

Sürüm notu: render prop yerine kompozisyon

Belgedeki değişiklik geçmişine göre 19 Mart 2024 tarihinde bileşen, render prop kalıbından bugün kullanılan kompozisyon kalıbına geçirildi. Eski sürümde kutular bir render fonksiyonu içinde çiziliyordu; yeni sürümde InputOTPSlot kendi verisini OTPInputContext üzerinden index ile okur. Belge, render özelliğini kullanan kodu güncellemek zorunda olmadığınızı ve bu özelliğin hâlâ desteklendiğini not eder. Güncellemek isterseniz önce paketi son sürüme yükseltirsiniz:

bash
npm install input-otp@latest

Input OTP, Mart 2024 değişiklik notunda Breadcrumb ile birlikte duyurulmuş ve sayısal ile alfasayısal kodları, özel uzunluğu, kopyala yapıştırı ve erişilebilirliği destekleyen bir bileşen olarak tanıtılmıştır. Sürüm geçmişinin tamamı yenilikler yazısında.

Sık yapılan hatalar

Sık yapılan hatalar: maxLength ile kutucuk sayısını eşleştirmemek, index değerini 1 den başlatmak, onChange içinde e.target.value aramak, REGEXP_ONLY_DIGITS_AND_CHARS sabitini...
  • maxLength ile kutucuk sayısını eşleştirmemek: her kutu yalnızca kendi indeksindeki karakteri gösterdiği için kutu sayısı azsa son karakterler görünmez, fazlaysa artan kutular hep boş kalır.
  • index değerini 1 den başlatmak: indeksler sıfırdan başlar.
  • onChange içinde e.target.value aramak: bileşen doğrudan metin verir.
  • REGEXP_ONLY_DIGITS_AND_CHARS sabitini shadcn/ui dosyasından import etmeye çalışmak: sabit input-otp paketinden gelir.
  • Hata görünümü için aria-invalid değerini yalnızca köke vermek: kırmızı kenarlık sınıfları kutucuklarda tanımlıdır.
  • pattern kısıtını güvenlik önlemi sanmak: yalnızca giriş kolaylığıdır, kodu sunucuda doğrulayın.

Sık Sorulan Sorular

Kullanıcı kodu yapıştırabilir mi?

Evet. Belge bileşeni kopyala yapıştır desteği olan bir OTP bileşeni olarak tanımlar ve Mart 2024 notunda da bu özellik sayılır.

Kod tamamlanınca formu otomatik gönderebilir miyim?

shadcn/ui belgesi buna ait bir örnek içermez. Kontrollü kullanımda value.length === 6 gibi bir koşulu kendiniz izleyebilir ya da input-otp paketinin kendi belgesindeki seçeneklere bakabilirsiniz.

Kodun gönderilmesi ve doğrulanması da bu bileşenin işi mi?

Hayır. Bileşen yalnızca giriş arayüzüdür. Kodu SMS ya da e-posta ile göndermek ve sunucuda doğrulamak uygulamanızın işidir; sunucu tarafında form işleme için Server Actions yazısına bakabilirsiniz.

Kaç haneli kod kullanmalıyım?

Bileşen herhangi bir uzunluğu destekler; belgedeki ve depodaki örnekler altı haneli kodu ve dört haneli kodu gösterir. Depodaki yeni stil örneği dört haneli düzen için "PIN kodları için yaygın bir kalıp" açıklamasını kullanır. Uzunluğu kodu üreten sisteminiz belirler; maxLength ve kutu sayısını ona göre ayarlayın.

Ayırıcıdaki çizgiyi başka bir işaretle değiştirebilir miyim?

Evet. InputOTPSeparator sizin dosyanızda duran küçük bir bileşendir ve içinde MinusIcon vardır; ikonu değiştirebilir ya da tamamen kendi işaretinizi yazabilirsiniz.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: shadcn/ui öğrenme rehberi · Field bileşeni · React Hook Form ve Zod · Input ve Input Group · Hazır bloklar · Server Actions formu