shadcn/ui Field bileşeni, bir form alanının etiketini, kontrolünü, yardım metnini ve hata mesajını tek bir düzende birleştiren yapı taşları ailesidir. Kendisi bir input değildir: Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet gibi parçalarla Input, Select, Switch ya da Checkbox gibi kontrolleri sarar ve erişilebilir, tutarlı bir form düzeni kurmanızı sağlar.

Field, Ekim 2025 değişiklik notunda "tek bileşen, tüm formlarınız" vurgusuyla tanıtıldı ve notta Server Actions, React Hook Form, TanStack Form ve kendi form çözümünüzle çalışacak biçimde tasarlandığı belirtildi. Bu yazıda tüm parçaları, yönlendirme (orientation) seçeneklerini, hata gösterimini, seçim kartlarını ve API tablolarını ele alıyoruz. Seriye genel bakış için shadcn/ui öğrenme rehberi sayfasına bakabilirsiniz.

Kurulum

Kurulum

Field tek komutla eklenir. Depodaki kayıt dosyasına göre bileşenin shadcn/ui içinden label ve separator bileşenlerine bağımlılığı vardır; CLI bunları da getirir.

bash
npx shadcn@latest add field

Elle kurulumda bileşen kaynağını components/ui/field.tsx dosyasına kopyalayıp import yollarını düzeltirsiniz. Kaynağın başında "use client" satırı vardır, çünkü FieldError içinde useMemo kullanılır. Field yalnızca düzen sağlar; içine koyacağınız Input, Textarea, Select, Checkbox gibi kontrolleri ayrıca eklemeniz gerekir (Input yazısı, seçim kontrolleri yazısı).

Anatomi: bir alan nasıl kurulur?

Belgenin anatomi bölümündeki tipik yapı dört satırdan oluşur: sarmalayıcı, etiket, kontrol, yardım metni ve hata. Aşağıda bunu Türkçe metinlerle yazıyoruz:

typescript
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"

export function KullaniciAdiAlani() {
  return (
    <Field>
      <FieldLabel htmlFor="kullanici-adi">Kullanıcı adı</FieldLabel>
      <Input id="kullanici-adi" autoComplete="off" placeholder="ornek_kullanici" />
      <FieldDescription>Profilinizde herkese görünen addır.</FieldDescription>
    </Field>
  )
}

Burada dikkat edilecek ilk nokta, etiketin kontrole htmlFor ile bağlanmasıdır. FieldLabel, shadcn/ui Label bileşenini sarar; etiket ile input arasında otomatik bir kimlik üretimi yoktur, id değerini sizin vermeniz gerekir. Belge ayrıca şu ilkeyi verir: Field tek bir alanın çekirdek sarmalayıcısıdır, FieldContent etiket ile açıklamayı dikey bir sütunda toplar ve açıklama yoksa gerekmez; ilişkili alanlar FieldGroup ile sarılır, anlamsal gruplama için de FieldSet ile FieldLegend kullanılır.

Kaynak koda bakınca Field bileşeninin bir div ürettiğini ve üzerine role="group", data-slot="field" ve data-orientation özniteliklerini koyduğunu görürsünüz. Belgenin erişilebilirlik bölümü de bunu söyler: grup rolü sayesinde iç içe kontroller, FieldLabel ve FieldLegend ile birleştirildiğinde etiketlerini bu gruptan alır. FieldSet gerçek bir fieldset öğesi, FieldLegend de gerçek bir legend öğesi üretir; klavye ve yardımcı teknoloji kullanıcıları için ilgili kontrolleri bir arada tutar.

FieldSet, FieldLegend ve FieldGroup ile bölümler

Çok bölümlü formlarda yapı şöyle kurulur: her bölüm bir FieldSet olur, başlığı FieldLegend ile verilir, bölüm açıklaması FieldDescription ile yazılır ve alanlar bir FieldGroup içinde alt alta dizilir. Bölümler arasına FieldSeparator konabilir. Resmî field-fieldset örneğinin yapısını koruyarak yazalım:

typescript
<FieldSet>
  <FieldLegend>Adres bilgileri</FieldLegend>
  <FieldDescription>Siparişinizi teslim edebilmemiz için adresinize ihtiyacımız var.</FieldDescription>
  <FieldGroup>
    <Field>
      <FieldLabel htmlFor="sokak">Açık adres</FieldLabel>
      <Input id="sokak" type="text" placeholder="Atatürk Cad. No: 1" />
    </Field>
    <div className="grid grid-cols-2 gap-4">
      <Field>
        <FieldLabel htmlFor="sehir">Şehir</FieldLabel>
        <Input id="sehir" type="text" placeholder="İzmir" />
      </Field>
      <Field>
        <FieldLabel htmlFor="posta">Posta kodu</FieldLabel>
        <Input id="posta" type="text" placeholder="35000" />
      </Field>
    </div>
  </FieldGroup>
</FieldSet>

Örnekte iki alanı yan yana koymak için sıradan bir div ve grid sınıfları kullanılmıştır; Field bunu engellemez. FieldLegend için iki varyant vardır: varsayılan legend ve label. Kaynakta legend varyantı text-base, label varyantı text-sm boyut uygular. Belge, label varyantının özellikle iç içe FieldSet kullandığınızda işe yaradığını söyler; örneğin bir bildirim tercihleri bölümünün içindeki onay kutusu listesine başlık verirken.

Onay kutusu ya da radyo listelerinde aralığı daraltmak için kaynak koda gömülü bir kural vardır: FieldGroup üzerine data-slot="checkbox-group" verdiğinizde grup aralığı gap-3 olur. React Hook Form ve TanStack Form belgeleri, onay kutusu dizileri için bu özniteliği eklemeyi özellikle hatırlatır. FieldSet de doğrudan çocuğu bir checkbox ya da radio grubu olduğunda aralığını daraltır.

Yönlendirme: vertical, horizontal ve responsive

Yönlendirme: vertical, horizontal ve responsive: vertical (varsayılan), horizontal, responsive

Field bileşeninin tek gerçek varyant özelliği orientation değeridir ve kaynakta cva ile tanımlanmıştır (varyant mantığı için cva yazısı). Üç değer alır:

DeğerDavranışNe zaman
vertical (varsayılan)Etiket, kontrol ve yardım metni alt alta dizilirMobil öncelikli, klasik formlar
horizontalEtiket ve kontrol aynı satırda yan yanaSwitch, Checkbox, ayar satırları
responsiveDar kapta dikey, geniş kapta yatayAyar sayfaları, iki sütunlu düzenler

horizontal kullanırken açıklamalı alanlarda etiket ile açıklamayı FieldContent içine koymanız önerilir; böylece açıklama etiketin altında hizalı kalır. Kaynakta yatay düzende açıklama metnine text-balance uygulandığını, FieldContent varsa satırın üstten hizalandığını ve onay kutusu ile radyo öğesine bir piksellik üst boşluk verildiğini görebilirsiniz. Belgedeki örnek bir Switch satırıdır:

typescript
<Field orientation="horizontal">
  <Switch id="bulten" />
  <FieldLabel htmlFor="bulten">Bültene abone ol</FieldLabel>
</Field>

responsive değeri kapsayıcı sorgularına (container query) dayanır. FieldGroup kaynakta zaten @container/field-group sınıfını taşır ve responsive varyantı @md/field-group eşiğinde satır düzenine geçer. Yani karar ekran genişliğine göre değil, alanların içinde bulunduğu grubun genişliğine göre verilir. Belge bu nedenle responsive alanların kapsayıcıya duyarlı bir üst öğe içinde kullanılmasını söyler; FieldGroup dışında kullanırsanız beklenen geçiş olmaz. Kapsayıcı sorgularının kendisi Tailwind konusudur (Tailwind v4 yazısı).

typescript
<FieldGroup>
  <Field orientation="responsive">
    <FieldContent>
      <FieldLabel htmlFor="ad">Ad soyad</FieldLabel>
      <FieldDescription>Kimlik doğrulaması için tam adınızı yazın.</FieldDescription>
    </FieldContent>
    <Input id="ad" placeholder="Ayşe Yılmaz" required />
  </Field>
  <FieldSeparator />
  <Field orientation="responsive">
    <Button type="submit">Gönder</Button>
    <Button type="button" variant="outline">Vazgeç</Button>
  </Field>
</FieldGroup>

Doğrulama ve hata gösterimi

Belgenin doğrulama bölümü üç kural verir. Birincisi, alanı hata görünümüne geçirmek için Field üzerine data-invalid eklenir; kaynakta bu durum data-[invalid=true]:text-destructive sınıfıyla metni hata rengine boyar. İkincisi, yardımcı teknolojiler için kontrolün kendisine aria-invalid eklenir; Input ve diğer kontrollerin kaynaklarında kırmızı kenarlık bu özniteliğe bağlıdır. Üçüncüsü, FieldError kontrolün hemen ardından ya da FieldContent içinde çizilir ki mesaj alanla hizalı kalsın.

typescript
<Field data-invalid>
  <FieldLabel htmlFor="eposta">E-posta</FieldLabel>
  <Input id="eposta" type="email" aria-invalid />
  <FieldError>Geçerli bir e-posta adresi girin.</FieldError>
</Field>

FieldError iki şekilde kullanılır. Ya mesajı çocuk olarak yazarsınız, ya da errors özelliğine { message?: string } biçiminde nesnelerden oluşan bir dizi verirsiniz. Kaynak kod bu dizide aynı mesajı taşıyan öğeleri tekilleştirir; tek mesaj kalırsa düz metin, birden fazla kalırsa madde işaretli bir liste çizer. Hiç içerik yoksa bileşen hiçbir şey çizmez. Çizdiğinde de role="alert" taşıyan bir div üretir; bu yüzden hata mesajı belirdiğinde ekran okuyucular tarafından duyurulabilir.

Belge ayrıca FieldError bileşeninin Standard Schema uygulayan doğrulayıcıların ürettiği hata kayıtlarını (issues) da kabul ettiğini belirtir; Zod, Valibot ve ArkType örnek olarak sayılır. Böylece React Hook Form tarafında errors={[fieldState.error]}, TanStack Form tarafında errors={field.state.meta.errors} yazarak aynı bileşeni kullanırsınız. Sunucu eylemi örneğinde ise mesaj doğrudan çocuk olarak verilir: <FieldError>{formState.errors.title[0]}</FieldError>. Bu üç yaklaşım React Hook Form, TanStack Form ve Server Actions yazılarında ayrı ayrı anlatılıyor.

Devre dışı durum için ayrı bir özellik yoktur ama kaynak data-disabled özniteliğine tepki verir: FieldLabel ve FieldTitle sınıflarında group-data-[disabled=true]/field:opacity-50 bulunur. Next.js form belgesi bunu, gönderim sürerken <Field data-disabled={pending}> yazarak kullanır.

Seçim kartları (choice card)

Field ailesinin en göz alıcı kullanımı, bir Field bileşenini FieldLabel içine sarmaktır. Belge bu yöntemin RadioGroup öğeleri, Checkbox ve Switch ile çalıştığını söyler. Etiketin tamamı tıklanabilir bir kart olur; kaynakta etiket, içinde bir Field bulunduğunda kenarlıklı ve yuvarlatılmış bir kutuya dönüşür ve içindeki kontrol işaretlendiğinde (data-state=checked) arka planı ve kenarlığı birincil renge geçer.

typescript
<RadioGroup defaultValue="vps">
  <FieldLabel htmlFor="secim-vps">
    <Field orientation="horizontal">
      <FieldContent>
        <FieldTitle>VPS</FieldTitle>
        <FieldDescription>Kök erişimli sanal sunucu.</FieldDescription>
      </FieldContent>
      <RadioGroupItem value="vps" id="secim-vps" />
    </Field>
  </FieldLabel>
  <FieldLabel htmlFor="secim-paylasimli">
    <Field orientation="horizontal">
      <FieldContent>
        <FieldTitle>Paylaşımlı hosting</FieldTitle>
        <FieldDescription>Yönetimi kolay web barındırma.</FieldDescription>
      </FieldContent>
      <RadioGroupItem value="paylasimli" id="secim-paylasimli" />
    </Field>
  </FieldLabel>
</RadioGroup>

Kartın içinde başlık için FieldLabel değil FieldTitle kullanılır. Bunun nedeni basittir: dışarıda zaten bir etiket vardır ve etiketin içine ikinci bir etiket koymak doğru olmaz. FieldTitle etiket görünümünde ama sıradan bir div üreten bir başlıktır. Radyo grubunun kendisi Checkbox, Radio Group, Switch, Slider yazısında anlatılıyor.

Tam bir form: resmî ödeme örneği nasıl kurulmuş?

Belgenin en üstündeki field-demo örneği, parçaların birlikte nasıl kullanıldığını gösteren iyi bir haritadır. En dışta bir form ve onun içinde tek bir FieldGroup vardır. İlk bölüm "Payment Method" başlıklı bir FieldSet olur; içinde kart sahibinin adı ve kart numarası için iki dikey Field ile ay, yıl ve güvenlik kodu için üç sütunlu bir grid bulunur. Ay ve yıl alanları Select ile, diğerleri Input ile yazılmıştır ve Select için kimlik SelectTrigger üzerine verilir.

İlk bölümden sonra bir FieldSeparator gelir. İkinci bölüm fatura adresidir ve tek bir yatay alan içerir: "Same as shipping address" onay kutusu, defaultChecked ile işaretli başlar ve etiketine font-normal verilerek kalın yazı kaldırılır. Üçüncü bölüm başlıksız bir FieldSet içinde yorum alanıdır (Textarea). En sonda düğmeler yatay bir Field içinde yan yana durur: biri type="submit", diğeri type="button" ve variant="outline". Bu son ayrıntı önemlidir; düğme satırı da bir Field olduğu için aralıklar formun geri kalanıyla tutarlı kalır.

API özeti

Belgedeki API başvurusunu tek tabloda topladık. Tüm parçalar className kabul eder; tabloda yalnızca ek özellikler yazıyor.

ParçaÜrettiği öğeÖzel özellikler
FieldSetfieldsetyok
FieldLegendlegendvariant: "legend" | "label", varsayılan "legend"
FieldGroupdiv (container query kapsayıcısı)yok
Fielddiv, role="group"orientation: "vertical" | "horizontal" | "responsive", varsayılan "vertical"; data-invalid
FieldContentdiv (dikey sütun)yok
FieldLabelLabelasChild, varsayılan false
FieldTitlediv (etiket görünümü)yok
FieldDescriptionpyok
FieldSeparatordiv ve Separatoristeğe bağlı çocuk içerik
FieldErrordiv, role="alert"errors: Array<{ message?: string } | undefined>

FieldSeparator çocuk alırsa çizginin ortasında arka plan renginde bir etiket gösterir; belgedeki örnek "Or continue with" metnidir ve oturum açma formlarında sosyal giriş düğmelerini ayırmak için kullanılır. Belge ayırıcıyı ölçülü kullanmayı önerir ki ekran okuyucular net bölüm sınırlarıyla karşılaşsın.

Field ile eski Form bileşeni arasındaki fark

shadcn/ui içinde React Hook Form için yazılmış eski bir Form bileşeni de vardır (FormField, FormItem, FormControl, FormMessage). Belgenin Form sayfasının başındaki uyarı, bu bileşenin artık aktif olarak geliştirilmediğini ve form kurmak için Field bileşeninin önerildiğini söyler. Temel fark bağımsızlıktır: eski Form bileşeni React Hook Form bağlamına ihtiyaç duyar, Field ise hiçbir form kütüphanesine bağlı değildir ve kimlik, hata, açıklama bağlantılarını sizin açık özniteliklerinizle kurar. Eski bileşenin nasıl çalıştığı React Hook Form yazısında kısaca anlatılıyor.

Sık yapılan hatalar

Sık yapılan hatalar: Etiketi bağlamamak, Yalnızca data-invalid eklemek, responsive değerini FieldGroup dışında kullanmak, Seçim kartında içeride ikinci bir FieldLabel kullanmak
  • Etiketi bağlamamak: FieldLabel üzerinde htmlFor, kontrolde aynı id yoksa etikete tıklamak kontrolü odaklamaz.
  • Yalnızca data-invalid eklemek: görünüm değişir ama kontrolde aria-invalid olmadığı için yardımcı teknolojiler hatayı bilmez. Belge ikisini birlikte ister.
  • responsive değerini FieldGroup dışında kullanmak: geçiş grubun kapsayıcı sorgusuna bağlıdır.
  • Seçim kartında içeride ikinci bir FieldLabel kullanmak: kart başlığı için FieldTitle vardır.
  • Onay kutusu listesinde data-slot="checkbox-group" unutmak: aralıklar diğer gruplarla aynı kalır.
  • Field bileşeninin input ürettiğini sanmak: Field yalnızca düzendir, kontrolleri ayrıca eklemeniz gerekir.

Sık Sorulan Sorular

Field bileşeni form kütüphanesi gerektirir mi?

Hayır. Belgedeki örneklerin çoğu düz bir form öğesi içinde, hiçbir kütüphane olmadan yazılmıştır. React Hook Form, TanStack Form ya da Server Actions ile kullanmak isteğe bağlıdır.

Hata mesajları neden liste olarak görünüyor?

errors dizisinde birbirinden farklı birden fazla mesaj varsa FieldError bunları madde işaretli liste olarak çizer. Aynı mesaj birden çok kez gelirse tek kez gösterilir.

Slider gibi etiketlenemeyen kontrollerde ne yapmalıyım?

Resmî field-slider örneği etiket yerine FieldTitle kullanır ve Slider bileşenine aria-label verir. Böylece görünür başlık ile erişilebilir ad ayrı ayrı sağlanır.

Field ile hazır form blokları var mı?

Evet, depodaki oturum açma ve OTP blokları Field ile yazılmıştır. Blokların nasıl eklendiği Blocks yazısında anlatılıyor.

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 · React Hook Form ve Zod · TanStack Form · Next.js Server Actions formu · Input, Textarea, Input Group · Checkbox, Radio Group, Switch, Slider