Alan
Ayar panelleri, ödeme formları ve kayıt akışları için etiket, kontrol, yardım metni ve hata alanları — neobrütalist form tasarımıyla.
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {Field bileşeni; etiketi, kontrolü, yardım metnini ve hata mesajını hizalı tek bir blokta toplar ve FieldGroup ile FieldSet sayesinde gruplanmış bölümlere ölçeklenir. Altta bir primitif yoktur — Label ve Separator ile birleştirilmiş düz fieldset, legend ve role="group" semantiği, neobrütalist reçeteye göre stillendirilmiştir: legend ve etiketlerde vurgulu başlık tipografisi; kalın kenarlıkları ve keskin gölgeleri ise sarmalanan kontroller getirir.
Şu durumlarda tercih edin:
- Ayar sayfaları — profil, bildirim ve faturalama bölümlerini
FieldSetlegend'larıyla üst üste dizin, grupların arasınaFieldSeparatorkoyun. - Ödeme ve kayıt formları — etiket, açıklama ve hata hizalamasını giriş alanları, seçim kutuları ve anahtarlar arasında tutarlı tutun.
- Seçim kartları — plan seçiciler ve tercih panelleri için radyo düğmelerini, onay kutularını veya anahtarları seçilebilir
FieldLabelkartlarıyla sarın.
Kurulum#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/field.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/field.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/field.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/field.json
Aşağıdaki kodu kopyalayıp projenize yapıştırın.
"use client"
import { useMemo } from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return (
<fieldset
data-slot="field-set"
className={cn(
"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
className
)}
{...props}
/>
)
}
function FieldLegend({
className,
variant = "legend",
...props
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
return (
<legend
data-slot="field-legend"
data-variant={variant}
className={cn(
"mb-1.5 font-head font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
className
)}
{...props}
/>
)
}
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-group"
className={cn(
"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
className
)}
{...props}
/>
)
}
const fieldVariants = cva(
"group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
{
variants: {
orientation: {
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
horizontal:
"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
responsive:
"flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
},
},
defaultVariants: {
orientation: "vertical",
},
}
)
function Field({
className,
orientation = "vertical",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
return (
<div
role="group"
data-slot="field"
data-orientation={orientation}
className={cn(fieldVariants({ orientation }), className)}
{...props}
/>
)
}
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-content"
className={cn(
"group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
className
)}
{...props}
/>
)
}
function FieldLabel({
className,
...props
}: React.ComponentProps<typeof Label>) {
return (
<Label
data-slot="field-label"
className={cn(
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded has-[>[data-slot=field]]:border-2 *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
className
)}
{...props}
/>
)
}
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-label"
className={cn(
"flex w-fit items-center gap-2 text-sm font-head font-medium group-data-[disabled=true]/field:opacity-50",
className
)}
{...props}
/>
)
}
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="field-description"
className={cn(
"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
"last:mt-0 nth-last-2:-mt-1",
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className
)}
{...props}
/>
)
}
function FieldSeparator({
children,
className,
...props
}: React.ComponentProps<"div"> & {
children?: React.ReactNode
}) {
return (
<div
data-slot="field-separator"
data-content={!!children}
className={cn(
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
className
)}
{...props}
>
<Separator className="absolute inset-0 top-1/2" />
{children && (
<span
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
data-slot="field-separator-content"
>
{children}
</span>
)}
</div>
)
}
function FieldError({
className,
children,
errors,
...props
}: React.ComponentProps<"div"> & {
errors?: Array<{ message?: string } | undefined>
}) {
const content = useMemo(() => {
if (children) {
return children
}
if (!errors?.length) {
return null
}
const uniqueErrors = [
...new Map(errors.map((error) => [error?.message, error])).values(),
]
if (uniqueErrors?.length == 1) {
return uniqueErrors[0]?.message
}
return (
<ul className="ml-4 flex list-disc flex-col gap-1">
{uniqueErrors.map(
(error, index) =>
error?.message && <li key={index}>{error.message}</li>
)}
</ul>
)
}, [children, errors])
if (!content) {
return null
}
return (
<div
role="alert"
data-slot="field-error"
className={cn("text-sm font-normal text-destructive", className)}
{...props}
>
{content}
</div>
)
}
export {
Field,
FieldLabel,
FieldDescription,
FieldError,
FieldGroup,
FieldLegend,
FieldSeparator,
FieldSet,
FieldContent,
FieldTitle,
}
İçe aktarma yollarını proje kurulumunuza uyacak şekilde güncelleyin.
Kullanım#
import {
Field,
FieldContent,
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/components/ui/field"<FieldSet>
<FieldLegend>Profile</FieldLegend>
<FieldDescription>This appears on invoices and emails.</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel htmlFor="name">Full name</FieldLabel>
<Input id="name" autoComplete="off" placeholder="Evil Rabbit" />
<FieldDescription>This appears on invoices and emails.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="username">Username</FieldLabel>
<Input id="username" autoComplete="off" aria-invalid />
<FieldError>Choose another username.</FieldError>
</Field>
<Field orientation="horizontal">
<Switch id="newsletter" />
<FieldLabel htmlFor="newsletter">Subscribe to the newsletter</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>Kompozisyon#
Field#
Etiket, yardımcı metin ve doğrulama içeren tek bir kontrol.
Field
├── FieldLabel
├── Input / Textarea / Switch / Select
├── FieldDescription
└── FieldErrorFieldGroup#
Tek bir grupta ilgili alanlar. Gerektiğinde bölümler arasında FieldSeparator kullanın.
FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
├── FieldSeparator
└── Field
├── FieldLabel
└── Input / Textarea / Switch / SelectFieldSet#
Genellikle bir FieldGroup içeren, bir başlık ve açıklama içeren anlamsal gruplama.
FieldSet
├── FieldLegend
├── FieldDescription
└── FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
└── Field
├── FieldLabel
└── Input / Textarea / Switch / SelectAnatomi#
Field ailesi, erişilebilir formlar oluşturmak için tasarlanmıştır. Tipik bir alan aşağıdaki gibi yapılandırılır:
<Field>
<FieldLabel htmlFor="input-id">Label</FieldLabel>
{/* Input, Select, Switch, etc. */}
<FieldDescription>Optional helper text.</FieldDescription>
<FieldError>Validation message.</FieldError>
</Field>Field, tek bir alan için temel sarmalayıcıdır.FieldContent, etiket ve açıklamayı gruplayan bir flex sütunudur. Açıklamanız yoksa gerekli değildir.- İlgili alanları
FieldGroupile sarmalayın ve anlamsal gruplama içinFieldSet'iFieldLegendile kullanın.
Form#
Field bileşeni ve React Hook Form, Tanstack Form veya Formisch ile form oluşturmak için Form belgelerine bakın.
Örnekler#
Input#
import {
Field,
FieldDescription,Textarea#
import {
Field,
FieldDescription,Select#
import {
Field,
FieldDescription,Slider#
"use client"
import * as React from "react"Fieldset#
import {
Field,
FieldDescription,Checkbox#
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,Radio#
import {
Field,
FieldDescription,Switch#
import { Field, FieldLabel } from "@/components/ui/field"
import { Switch } from "@/components/ui/switch"
Seçim Kartı#
Seçilebilir alan grupları oluşturmak için Field bileşenlerini FieldLabel içine sarmalayın. Bu, RadioItem, Checkbox ve Switch bileşenleriyle çalışır.
import {
Field,
FieldContent,Alan Grubu#
Field bileşenlerini FieldGroup ile üst üste yerleştirin. Bunları ayırmak için FieldSeparator ekleyin.
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,RTL#
Neobrutalism'de RTL desteğini etkinleştirmek için RTL yapılandırma kılavuzuna bakın.
"use client"
import * as React from "react"Duyarlı Düzen#
- Dikey alanlar: Varsayılan yönlendirme etiketi, kontrolü ve yardımcı metni üst üste yerleştirir — mobil öncelikli düzenler için idealdir.
- Yatay alanlar: Etiketi ve kontrolü yan yana hizalamak için
Fieldüzerindeorientation="horizontal"ayarlayın. Açıklamaları hizalı tutmak içinFieldContentile birlikte kullanın. - Duyarlı alanlar: Konteyner farkındalıklı üst öğeler içinde otomatik sütun düzenleri için
orientation="responsive"ayarlayın. Belirli kesme noktalarında yönlendirmeleri değiştirmek içinFieldGroupüzerinde@container/field-groupsınıflarını uygulayın.
import { Button } from "@/components/ui/button"
import {
Field,Doğrulama ve Hatalar#
- Tüm bloğu bir hata durumuna geçirmek için
Field'edata-invalidekleyin. - Yardımcı teknolojiler için girişin kendisine
aria-invalidekleyin. - Hata mesajlarını alanla hizalı tutmak için
FieldError'ı kontrolden hemen sonra veyaFieldContentiçinde render edin.
<Field data-invalid>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" aria-invalid />
<FieldError>Enter a valid email address.</FieldError>
</Field>Erişilebilirlik#
Field ailesi semantiği önceler: FieldSet ve FieldLegend yerel fieldset/legend öğelerini işler, Field role="group" olarak, FieldError ise role="alert" olarak işlenir; böylece doğrulama mesajları göründükleri anda duyurulur.
- Her
FieldLabelbileşeninihtmlFor/idile kendi kontrolüne bağlayın — etikete tıklamak kontrolü odaklar ve ekran okuyucular bu ilişkiyi duyurur. - Geçersiz girişte
aria-invalidözniteliğini kontrolün kendisine ekleyin;Fieldüzerindekidata-invalidyalnızca hata stilini yönetir. - Klavye davranışı içerideki kontrollere aittir — sarmalayıcılar kendilerine ait odak durağı veya tuş ataması eklemez.
API Referansı#
FieldSet#
Boşluk ön ayarlarıyla anlamsal bir fieldset render eden konteyner.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldSet>
<FieldLegend>Delivery</FieldLegend>
<FieldGroup>{/* Fields */}</FieldGroup>
</FieldSet>FieldLegend#
Bir FieldSet için başlık öğesi. Etiket boyutlandırmasıyla hizalamak için label varyantına geçin.
| Prop | Type | Default |
|---|---|---|
variant | "legend" | "label" | "legend" |
className | string |
<FieldLegend variant="label">Notification Preferences</FieldLegend>FieldLegend'in iki varyantı vardır: legend ve label. label varyantı, etiket boyutlandırması ve hizalamasını uygular. İç içe FieldSet varsa kullanışlıdır.
FieldGroup#
Field bileşenlerini üst üste yerleştiren ve duyarlı yönlendirmeler için konteyner sorgularını etkinleştiren düzen sarmalayıcısı.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldGroup className="@container/field-group flex flex-col gap-6">
<Field>{/* ... */}</Field>
<Field>{/* ... */}</Field>
</FieldGroup>Field#
Tek bir alan için temel sarmalayıcı. Yönlendirme kontrolü, geçersiz durum stili ve boşluk sağlar.
| Prop | Type | Default |
|---|---|---|
orientation | "vertical" | "horizontal" | "responsive" | "vertical" |
className | string | |
data-invalid | boolean |
<Field orientation="horizontal">
<FieldLabel htmlFor="remember">Remember me</FieldLabel>
<Switch id="remember" />
</Field>FieldContent#
Etiket kontrolün yanında olduğunda kontrolü ve açıklamaları gruplayan flex sütunu. Açıklamanız yoksa gerekli değildir.
| Prop | Type | Default |
|---|---|---|
className | string |
<Field>
<Checkbox id="notifications" />
<FieldContent>
<FieldLabel htmlFor="notifications">Notifications</FieldLabel>
<FieldDescription>Email, SMS, and push options.</FieldDescription>
</FieldContent>
</Field>FieldLabel#
Hem doğrudan girişler hem de iç içe Field alt öğeleri için stillendirilmiş etiket.
| Prop | Type | Default |
|---|---|---|
className | string | |
asChild | boolean | false |
<FieldLabel htmlFor="email">Email</FieldLabel>FieldTitle#
FieldContent içinde etiket stiliyle bir başlık render eder.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldContent>
<FieldTitle>Enable Touch ID</FieldTitle>
<FieldDescription>Unlock your device faster.</FieldDescription>
</FieldContent>FieldDescription#
Yatay düzenlerde uzun satırları otomatik olarak dengeleyen yardımcı metin yuvası.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldDescription>We never share your email with anyone.</FieldDescription>FieldSeparator#
Bir FieldGroup içindeki bölümleri ayırmak için görsel ayırıcı. İsteğe bağlı satır içi içerik kabul eder.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldSeparator>Or continue with</FieldSeparator>FieldError#
Alt öğeleri veya bir errors dizisini (örneğin react-hook-form'dan) kabul eden erişilebilir hata konteyneri.
| Prop | Type | Default |
|---|---|---|
errors | Array<{ message?: string } | undefined> | |
className | string |
<FieldError errors={errors.username} />errors dizisi birden fazla mesaj içerdiğinde, bileşen otomatik olarak bir liste render eder.
FieldError ayrıca Zod, Valibot ve ArkType dahil olmak üzere Standard Schema'yı uygulayan herhangi bir doğrulayıcı tarafından üretilen sorunları da kabul eder. Kitaplıklar arasında birleşik bir hata listesi render etmek için şema sonucundaki issues dizisini doğrudan iletin.