فیلد
برچسبها، کنترلها، متنهای راهنما و اسلاتهای خطا برای پنلهای تنظیمات، فرمهای تسویهحساب و جریانهای ثبتنام — با رویکرد فرم نئوبروتالیست.
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {Field برچسب، کنترل، متن راهنما و پیام خطا را در یک بلوک همتراز میپیچد و با FieldGroup و FieldSet به بخشهای گروهی مقیاس میشود. primitive زیرین ندارد — semantics سادهٔ fieldset، legend و role="group" ترکیبشده با Label و Separator، با دستور پخت نئوبروتالیستی: تایپ heading پررنگ روی legend و label، در حالی که کنترلهای پیچیدهشده حاشیههای ضخیم و سایههای سخت را میآورند.
برای این موارد از آن استفاده کنید:
- صفحات تنظیمات — بخشهای پروفایل، اعلان و صورتحساب را با legendهای
FieldSetوFieldSeparatorبین گروهها روی هم بچینید. - فرم checkout و ثبتنام — تراز label، توضیح و خطا را در input، select و switch یکسان نگه دارید.
- کارت انتخاب — radio، چکباکس یا switch را در کارتهای قابلانتخاب
FieldLabelبرای انتخاب پلن و پنل ترجیح بپیچید.
نصب#
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
کد زیر را کپی کرده و در پروژهٔ خود جایگذاری کنید.
"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,
}
مسیرهای import را مطابق با پیکربندی پروژهٔ خود بهروزرسانی کنید.
استفاده#
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>ترکیب#
Field#
یک کنترل واحد با برچسب، متن راهنما و اعتبارسنجی.
Field
├── FieldLabel
├── Input / Textarea / Switch / Select
├── FieldDescription
└── FieldErrorFieldGroup#
فیلدهای مرتبط در یک گروه. در صورت نیاز از FieldSeparator بین بخشها استفاده کنید.
FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
├── FieldSeparator
└── Field
├── FieldLabel
└── Input / Textarea / Switch / SelectFieldSet#
گروهبندی معنایی با یک عنوان و توضیحات که معمولاً شامل یک FieldGroup است.
FieldSet
├── FieldLegend
├── FieldDescription
└── FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
└── Field
├── FieldLabel
└── Input / Textarea / Switch / Selectآناتومی#
خانوادهٔ Field برای ساخت فرمهای قابل دسترس طراحی شده است. یک فیلد معمولی به این صورت ساختاربندی میشود:
<Field>
<FieldLabel htmlFor="input-id">Label</FieldLabel>
{/* Input, Select, Switch, etc. */}
<FieldDescription>Optional helper text.</FieldDescription>
<FieldError>Validation message.</FieldError>
</Field>Fieldپوشش اصلی برای یک فیلد واحد است.FieldContentیک ستون flex است که برچسب و توضیحات را گروهبندی میکند. اگر توضیحاتی ندارید، لازم نیست.- فیلدهای مرتبط را با
FieldGroupبپوشانید و برای گروهبندی معنایی ازFieldSetهمراه باFieldLegendاستفاده کنید.
فرم#
برای ساخت فرمها با کامپوننت Field و React Hook Form، Tanstack Form یا Formisch، مستندات Form را ببینید.
نمونهها#
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"
کارت انتخاب#
کامپوننتهای Field را در داخل FieldLabel بپوشانید تا گروههای فیلد قابل انتخاب بسازید. این با کامپوننتهای RadioItem، Checkbox و Switch کار میکند.
import {
Field,
FieldContent,گروه فیلد#
کامپوننتهای Field را با FieldGroup روی هم بچینید. برای جدا کردن آنها FieldSeparator را اضافه کنید.
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,RTL#
برای فعالسازی پشتیبانی RTL در Neobrutalism، راهنمای پیکربندی RTL را ببینید.
"use client"
import * as React from "react"چیدمان واکنشگرا#
- فیلدهای عمودی: جهت پیشفرض برچسب، کنترل و متن راهنما را روی هم میچیند — ایدهآل برای چیدمانهای mobile-first.
- فیلدهای افقی:
orientation="horizontal"را رویFieldتنظیم کنید تا برچسب و کنترل کنار هم تراز شوند. باFieldContentهمراه کنید تا توضیحات تراز باقی بمانند. - فیلدهای واکنشگرا: برای چیدمانهای ستونی خودکار در داخل والدهای آگاه از کانتینر،
orientation="responsive"را تنظیم کنید. کلاسهای@container/field-groupرا رویFieldGroupاعمال کنید تا در نقاط شکست مشخص، جهت را تغییر دهید.
import { Button } from "@/components/ui/button"
import {
Field,اعتبارسنجی و خطاها#
- برای تبدیل کل بلوک به حالت خطا،
data-invalidرا بهFieldاضافه کنید. - برای فناوریهای کمکی،
aria-invalidرا روی خود ورودی اضافه کنید. - برای همتراز نگه داشتن پیامهای خطا با فیلد،
FieldErrorرا بلافاصله بعد از کنترل یا در داخلFieldContentرندر کنید.
<Field data-invalid>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" aria-invalid />
<FieldError>Enter a valid email address.</FieldError>
</Field>قابلیت دسترسی#
FieldSetوFieldLegendکنترلهای مرتبط را برای کاربران صفحهکلید و فناوری کمکی گروهبندیشده نگه میدارند.Fieldخروجیrole="group"میدهد، بنابراین کنترلهای تودرتو هنگام ترکیب، برچسبگذاری را ازFieldLabelوFieldLegendبه ارث میبرند.FieldSeparatorرا با احتیاط اعمال کنید تا مطمئن شوید صفحهخوانها با مرزهای بخش واضح مواجه میشوند.
دسترسیپذیری#
خانوادهٔ field اول semantics است: FieldSet و FieldLegend مقادیر بومی fieldset/legend را رندر میکنند، Field مقدار role="group"، و FieldError مقدار role="alert" تا پیامهای اعتبارسنجی بهمحض ظاهر شدن اعلام شوند.
- هر
FieldLabelرا با کنترلش از طریقhtmlFor/idجفت کنید — کلیک روی برچسب کنترل را فوکوس میکند و صفحهخوان ارتباط را اعلام میکند. - روی ورودی نامعتبر،
aria-invalidرا روی خود کنترل بگذارید؛data-invalidرویFieldفقط استایل خطا را میراند. - رفتار صفحهکلید مال کنترلهای داخل است — wrapperها tab stop یا binding کلید خودشان اضافه نمیکنند.
مرجع API#
FieldSet#
کانتینری که یک fieldset معنایی با پیشتنظیمهای فاصلهگذاری رندر میکند.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldSet>
<FieldLegend>Delivery</FieldLegend>
<FieldGroup>{/* Fields */}</FieldGroup>
</FieldSet>FieldLegend#
عنصر عنوان برای یک FieldSet. برای همتراز شدن با اندازهٔ برچسب به گونهٔ label تغییر دهید.
| Prop | Type | Default |
|---|---|---|
variant | "legend" | "label" | "legend" |
className | string |
<FieldLegend variant="label">Notification Preferences</FieldLegend>FieldLegend دو گونه دارد: legend و label. گونهٔ label اندازه و تراز برچسب را اعمال میکند. اگر FieldSet تودرتو دارید مفید است.
FieldGroup#
پوشش چیدمان که کامپوننتهای Field را روی هم میچیند و کوئریهای کانتینر را برای جهتهای واکنشگرا فعال میکند.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldGroup className="@container/field-group flex flex-col gap-6">
<Field>{/* ... */}</Field>
<Field>{/* ... */}</Field>
</FieldGroup>Field#
پوشش اصلی برای یک فیلد واحد. کنترل جهت، استایلدهی حالت نامعتبر و فاصلهگذاری را فراهم میکند.
| 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#
ستون flex که کنترل و توضیحات را هنگامی که برچسب کنار کنترل قرار میگیرد گروهبندی میکند. اگر توضیحاتی ندارید، لازم نیست.
| 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#
برچسبی که هم برای ورودیهای مستقیم و هم برای فرزندان Field تودرتو استایلدهی شده است.
| Prop | Type | Default |
|---|---|---|
className | string | |
asChild | boolean | false |
<FieldLabel htmlFor="email">Email</FieldLabel>FieldTitle#
یک عنوان را با استایل برچسب در داخل FieldContent رندر میکند.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldContent>
<FieldTitle>Enable Touch ID</FieldTitle>
<FieldDescription>Unlock your device faster.</FieldDescription>
</FieldContent>FieldDescription#
اسلات متن راهنما که خطوط طولانی را بهصورت خودکار در چیدمانهای افقی متعادل میکند.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldDescription>We never share your email with anyone.</FieldDescription>FieldSeparator#
جداکنندهٔ بصری برای جدا کردن بخشها در داخل یک FieldGroup. محتوای درونخطی اختیاری را میپذیرد.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldSeparator>Or continue with</FieldSeparator>FieldError#
کانتینر خطای قابل دسترس که فرزندان یا یک آرایهٔ errors را میپذیرد (برای مثال، از react-hook-form).
| Prop | Type | Default |
|---|---|---|
errors | Array<{ message?: string } | undefined> | |
className | string |
<FieldError errors={errors.username} />هنگامی که آرایهٔ errors شامل چند پیام است، کامپوننت بهصورت خودکار یک فهرست رندر میکند.
FieldError همچنین مشکلاتی را که هر اعتبارسنجی که Standard Schema را پیادهسازی میکند تولید میکند میپذیرد، از جمله Zod، Valibot و ArkType. آرایهٔ issues را از نتیجهٔ اسکیما مستقیماً منتقل کنید تا یک فهرست خطای یکپارچه در سراسر کتابخانهها رندر شود.