AniBTUI
✦ 组件@anibt/fieldregistry JSON

Field

AniBT 的表单版式:标签在上、标签旁的小签和右端的元信息、控件下的说明或错误、短字段两两成行、一组字段一张卡。导出名与 shadcn 的 field 相同。

基本信息

短字段两两成行
手动
Demo Admin 接管 · 2 天前
已锁定

四位数年份。

只读
请输入以 https:// 开头的网址

安装

npx shadcn@latest add @anibt/field

第一次用之前:在 components.json 里登记 @anibt,再单独运行一次 npx shadcn@latest add @anibt/theme(见快速开始)。只有单独安装主题时,CLI 才会把 shadcn 的标准颜色换成白樱 / 夜樱;随组件带进来的主题只补上 AniBT 自己的变量和配方。 CLI 会一并装好 @anibt/label。

手动安装
  1. 1. 先装主题组件只读 @anibt/theme 的 token 和配方,没有主题就没有样子。
    npx shadcn@latest add @anibt/theme
  2. 2. 安装依赖
    npm install class-variance-authority lucide-react radix-ui
  3. 3. 它还用到
    npx shadcn@latest add @anibt/label
  4. 4. 把下面的文件放进项目
    components/ui/field.tsx
    "use client"
    
    import * as React from "react"
    import { cva, type VariantProps } from "class-variance-authority"
    import { AlertCircleIcon, LockIcon } from "lucide-react"
    import { Slot } from "radix-ui"
    
    import { cn } from "@/lib/utils"
    import { Label } from "@/components/ui/label"
    
    /**
     * Field — AniBT's form grammar (A 「贴纸手账」), on shadcn's field names.
     *
     * The rules, the same in every form:
     * - The label sits above its control: 13/600 `--anibt-ink2`, 6px over it.
     *   Required is a pink `*` (read as 「必填」). Beside the label only small
     *   facts (`FieldTag`: a source, 已锁定, 只读); the label row's far end holds
     *   meta (a count, a time) and wraps under the label on a narrow field.
     * - Under the control: a 12px hint in `--muted-foreground`, or a 12px error
     *   in `--destructive` behind its glyph (`aria-live`) — never both.
     * - 16px between fields. A group of fields is one card (`FieldGroup` with a
     *   `title`: paper, a stitched 14/700 head, 16px padding); 20px between cards.
     * - Two or three fields side by side only for short values, and only when
     *   the row itself is wide enough (`FieldRow`, a container query).
     * - Option text beside a checkbox or a switch is body copy, 13/400 ink
     *   (`orientation="horizontal"`).
     *
     * Two ways to write it:
     * - the one-call form: `<Field label="标题" hint="…" error={…}>` with the
     *   control inside `FieldControl`, which gives it the field's id, its
     *   `aria-describedby` and `aria-invalid`;
     * - shadcn's composition (`FieldSet`, `FieldLegend`, `FieldLabel`,
     *   `FieldContent`, `FieldTitle`, `FieldDescription`, `FieldError`,
     *   `FieldSeparator`), with the same exports and props as shadcn's field so
     *   code written for it keeps compiling.
     */
    
    type FieldContextValue = {
      controlId: string
      labelId: string
      hintId: string
      errorId: string
      hasHint: boolean
      hasError: boolean
      labelFor: "control" | "group"
    }
    
    const FieldContext = React.createContext<FieldContextValue | null>(null)
    
    /** Groups of fields: a plain stack, or — with a `title` — one A card. */
    function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
      return (
        <fieldset
          data-slot="field-set"
          className={cn(
            "flex min-w-0 flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
            className
          )}
          {...props}
        />
      )
    }
    
    /** A fieldset's caption: `legend` is the 14/700 head, `label` the 13/600 label tier. */
    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 data-[variant=label]:text-[13px] data-[variant=label]:leading-5 data-[variant=label]:font-semibold data-[variant=label]:text-anibt-ink2 data-[variant=legend]:text-[14px] data-[variant=legend]:leading-[1.4] data-[variant=legend]:font-bold data-[variant=legend]:text-foreground",
            className
          )}
          {...props}
        />
      )
    }
    
    type FieldGroupProps = Omit<React.ComponentProps<"div">, "title"> & {
      /** Turns the group into an A card with this stitched 14/700 head. */
      title?: React.ReactNode
      /** A 12px note beside the title (the card form only). */
      note?: React.ReactNode
      /** Tools at the head's far end (the card form only). */
      actions?: React.ReactNode
      /** Classes for the card's body, where the fields stack. */
      bodyClassName?: string
    }
    
    function FieldGroup({ title, note, actions, bodyClassName, className, children, ...props }: FieldGroupProps) {
      const headingId = React.useId()
      const stack =
        "group/field-group @container/field-group flex w-full min-w-0 flex-col gap-4 has-[>[data-slot=field-group]]:gap-5 data-[slot=checkbox-group]:gap-3"
    
      if (title === undefined || title === null) {
        return (
          <div data-slot="field-group" className={cn(stack, className)} {...props}>
            {children}
          </div>
        )
      }
    
      return (
        <section
          data-slot="field-group"
          data-variant="card"
          aria-labelledby={headingId}
          className={cn("anibt-sheet min-w-0 scroll-mt-3 overflow-clip", className)}
          {...(props as React.ComponentProps<"section">)}
        >
          <div className="anibt-stitch-under flex min-h-11 min-w-0 items-center gap-2 px-4 py-2">
            <h3 id={headingId} className="anibt-ell text-[14px] leading-[1.4] font-bold text-foreground">
              {title}
            </h3>
            {note ? <span className="anibt-ell text-[12px] text-muted-foreground">{note}</span> : null}
            {actions ? <div className="ml-auto flex shrink-0 items-center gap-2">{actions}</div> : null}
          </div>
          <div className={cn(stack, "p-4", bodyClassName)}>{children}</div>
        </section>
      )
    }
    
    const fieldVariants = cva("group/field w-full min-w-0", {
      variants: {
        orientation: {
          // Label row, control, hint: 6px apart. A select trigger takes the width.
          vertical: "grid content-start gap-1.5 *:data-[slot=select-trigger]:w-full",
          // A choice row: the control, then its option text (13/400 ink).
          horizontal:
            "flex flex-row items-center gap-2 has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto *:data-[slot=field-label]:font-normal *:data-[slot=field-label]:text-foreground has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>:disabled]:*:data-[slot=field-label]:text-muted-foreground data-[disabled=true]:*:data-[slot=field-label]:text-muted-foreground",
          // Stacked in a narrow group, label beside the control from 28rem.
          responsive:
            "flex flex-col gap-1.5 *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:gap-3 @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",
        },
      },
      defaultVariants: {
        orientation: "vertical",
      },
    })
    
    type FieldProps = Omit<React.ComponentProps<"div">, "children"> &
      VariantProps<typeof fieldVariants> & {
        /** The one-call form: the field's label, drawn above the control. */
        label?: React.ReactNode
        /** The control's id; generated when absent. The label points at it. */
        id?: string
        /** A pink `*` after the label, read as `requiredLabel`. */
        required?: boolean
        /** Shown but not editable here: a 只读 tag beside the label. */
        readOnly?: boolean
        /** Small tags beside the label: a source, a lock (`FieldTag`). */
        tags?: React.ReactNode
        /** The label row's far end: a count, a time, a disclosure. */
        meta?: React.ReactNode
        /** 12px help under the control. Hidden while there is an error. */
        hint?: React.ReactNode
        /** The error under the control; also marks the control invalid. */
        error?: React.ReactNode
        /**
         * `group` for a set of controls (a segmented control, a chip row) that
         * is named with `aria-labelledby` instead of `for`.
         */
        labelFor?: "control" | "group"
        /** What a screen reader says for the `*`. */
        requiredLabel?: string
        /** The read-only tag's word. */
        readOnlyLabel?: string
        children?: React.ReactNode
      }
    
    function Field({
      className,
      orientation = "vertical",
      label,
      id,
      required,
      readOnly,
      tags,
      meta,
      hint,
      error,
      labelFor = "control",
      requiredLabel = "必填",
      readOnlyLabel = "只读",
      children,
      ...props
    }: FieldProps) {
      const reactId = React.useId()
      const controlId = id ?? `${reactId}-control`
      const hasHint = Boolean(hint)
      const hasError = Boolean(error)
      const context = React.useMemo<FieldContextValue>(
        () => ({
          controlId,
          labelId: `${controlId}-label`,
          hintId: `${controlId}-hint`,
          errorId: `${controlId}-error`,
          hasHint,
          hasError,
          labelFor,
        }),
        [controlId, hasHint, hasError, labelFor]
      )
    
      // The one-call form puts the label above the control (or, horizontal,
      // beside a choice); "responsive" is for shadcn's composition only.
      const layout = label !== undefined && label !== null && orientation === "responsive" ? "vertical" : orientation
    
      const shell = (body: React.ReactNode) => (
        <FieldContext.Provider value={context}>
          <div
            role="group"
            data-slot="field"
            data-orientation={layout}
            data-invalid={hasError || undefined}
            className={cn(fieldVariants({ orientation: layout }), className)}
            {...props}
          >
            {body}
          </div>
        </FieldContext.Provider>
      )
    
      if (label === undefined || label === null) return shell(children)
    
      const labelText = (
        <>
          {label}
          {required ? (
            <>
              <span className="text-accent-foreground" aria-hidden="true">
                *
              </span>
              <span className="sr-only">{requiredLabel}</span>
            </>
          ) : null}
        </>
      )
      const under = hasError ? (
        <FieldError id={context.errorId}>{error}</FieldError>
      ) : hasHint ? (
        <FieldDescription id={context.hintId}>{hint}</FieldDescription>
      ) : null
    
      if (orientation === "horizontal") {
        return shell(
          <>
            {children}
            <FieldContent>
              <FieldLabel
                id={context.labelId}
                htmlFor={labelFor === "control" ? controlId : undefined}
                className="font-normal text-foreground"
              >
                {labelText}
              </FieldLabel>
              {under}
            </FieldContent>
          </>
        )
      }
    
      return shell(
        <>
          {/* The label row wraps: on a narrow field the meta drops under the
              label instead of squeezing it. */}
          <div className="flex min-h-6 min-w-0 flex-wrap items-center gap-x-1.5 gap-y-1">
            <FieldLabel id={context.labelId} htmlFor={labelFor === "control" ? controlId : undefined} className="min-w-0 shrink">
              {labelText}
            </FieldLabel>
            {readOnly ? <FieldTag icon={LockIcon}>{readOnlyLabel}</FieldTag> : null}
            {tags}
            {meta ? (
              <div className="ml-auto flex max-w-full min-w-0 items-center gap-1.5 text-[12px] text-muted-foreground">{meta}</div>
            ) : null}
          </div>
          {children}
          {under}
        </>
      )
    }
    
    /**
     * Gives the single child the field's id (or, for `labelFor="group"`, its
     * `aria-labelledby`), the hint or error as its description and its invalid
     * state. Outside a `Field` it passes the child through.
     */
    function FieldControl(props: React.ComponentProps<typeof Slot.Root>) {
      const field = React.useContext(FieldContext)
      if (!field) return <Slot.Root {...props} />
      const describedBy = field.hasError ? field.errorId : field.hasHint ? field.hintId : undefined
      return (
        <Slot.Root
          id={field.controlId}
          aria-labelledby={field.labelFor === "group" ? field.labelId : undefined}
          aria-describedby={describedBy}
          aria-invalid={field.hasError || undefined}
          {...props}
        />
      )
    }
    
    /** Title and description stacked beside a control in a horizontal field. */
    function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
      return (
        <div
          data-slot="field-content"
          className={cn("group/field-content flex min-w-0 flex-1 flex-col gap-0.5 leading-snug", className)}
          {...props}
        />
      )
    }
    
    /**
     * The field's label (a `Label`). Wrapping a whole `Field` in it makes a
     * choice card: an A well that turns pink when its control is checked.
     */
    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-1.5 leading-5 group-data-[disabled=true]/field:text-muted-foreground",
            "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:items-stretch has-[>[data-slot=field]]:rounded-[10px] has-[>[data-slot=field]]:bg-card has-[>[data-slot=field]]:font-normal has-[>[data-slot=field]]:text-foreground has-[>[data-slot=field]]:inset-ring-1 has-[>[data-slot=field]]:inset-ring-border has-[>[data-slot=field]]:transition-[background-color,box-shadow] has-[>[data-slot=field]]:duration-150 *:data-[slot=field]:px-3 *:data-[slot=field]:py-2.5",
            "has-[>[data-slot=field]]:not-has-[:disabled]:hover:inset-ring-anibt-wash-line",
            // Picked (a radio or a checkbox inside): the pink wash. A switch is a
            // setting, not a pick, so an "on" settings card stays paper.
            "has-[>[data-slot=field]]:has-[[role=radio][data-state=checked]]:bg-accent has-[>[data-slot=field]]:has-[[role=radio][data-state=checked]]:inset-ring-anibt-wash-line has-[>[data-slot=field]]:has-[[role=checkbox][data-state=checked]]:bg-accent has-[>[data-slot=field]]:has-[[role=checkbox][data-state=checked]]:inset-ring-anibt-wash-line",
            className
          )}
          {...props}
        />
      )
    }
    
    /** A label-tier title that labels nothing (a choice card's head). */
    function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
      return (
        <div
          data-slot="field-label"
          className={cn(
            "flex w-fit items-center gap-1.5 text-[13px] leading-5 font-semibold text-foreground group-data-[disabled=true]/field:text-muted-foreground",
            className
          )}
          {...props}
        />
      )
    }
    
    /** The hint under a control (or under a title): 12px, muted. */
    function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
      return (
        <p
          data-slot="field-description"
          className={cn(
            "text-[12px] leading-normal font-normal text-muted-foreground [[data-variant=legend]+&]:-mt-1",
            "[&>a]:text-accent-foreground [&>a]:underline-offset-4 [&>a:hover]:underline",
            className
          )}
          {...props}
        />
      )
    }
    
    /** A stitched rule between fields, with an optional word in the middle. */
    function FieldSeparator({
      children,
      className,
      ...props
    }: React.ComponentProps<"div"> & {
      children?: React.ReactNode
    }) {
      return (
        <div
          data-slot="field-separator"
          data-content={!!children}
          className={cn("relative -my-1 h-5 text-[12px]", className)}
          {...props}
        >
          <div aria-hidden="true" className="anibt-stitch absolute inset-x-0 top-1/2" />
          {children ? (
            <span
              data-slot="field-separator-content"
              className="relative mx-auto block w-fit bg-card px-2 leading-5 text-muted-foreground"
            >
              {children}
            </span>
          ) : null}
        </div>
      )
    }
    
    /**
     * The error under a control: 12px `--destructive` behind its glyph,
     * announced politely. Takes `children`, or shadcn's `errors` list (a
     * validator's `{ message }` objects; duplicates are dropped).
     */
    function FieldError({
      className,
      children,
      errors,
      ...props
    }: React.ComponentProps<"div"> & {
      errors?: Array<{ message?: string } | undefined>
    }) {
      const content = React.useMemo(() => {
        if (children) return children
        if (!errors?.length) return null
    
        const unique = [...new Map(errors.map((error) => [error?.message, error])).values()]
        if (unique.length === 1) return unique[0]?.message
    
        return (
          <ul className="ml-4 flex list-disc flex-col gap-1">
            {unique.map((error, index) => (error?.message ? <li key={index}>{error.message}</li> : null))}
          </ul>
        )
      }, [children, errors])
    
      if (!content) return null
    
      return (
        <div
          data-slot="field-error"
          aria-live="polite"
          className={cn("flex items-start gap-1 text-[12px] leading-normal font-normal text-destructive", className)}
          {...props}
        >
          <AlertCircleIcon className="mt-px size-3.5 shrink-0" aria-hidden="true" />
          <div className="min-w-0">{content}</div>
        </div>
      )
    }
    
    /** AniBT's name for the hint under a control. */
    const FieldHint = FieldDescription
    
    type FieldTagTone = "neutral" | "accent" | "ok" | "warn" | "danger"
    
    const TAG_TONE: Record<FieldTagTone, string> = {
      neutral: "",
      accent: "anibt-tag-lang",
      ok: "anibt-tag-ok",
      warn: "anibt-tag-warn",
      danger: "anibt-tag-danger",
    }
    
    /**
     * A small tag beside a label: a fact about the field (a source, 已锁定,
     * 只读), not a control. Never tilted — form rows stay straight.
     */
    function FieldTag({
      icon: Icon,
      tone = "neutral",
      className,
      children,
      ...props
    }: React.ComponentProps<"span"> & {
      icon?: React.ComponentType<{ className?: string; "aria-hidden"?: boolean | "true" }>
      tone?: FieldTagTone
    }) {
      return (
        <span data-slot="field-tag" data-tone={tone} className={cn("anibt-tag shrink-0", TAG_TONE[tone], className)} {...props}>
          {Icon ? <Icon className="size-3 shrink-0" aria-hidden="true" /> : null}
          {children}
        </span>
      )
    }
    
    /**
     * Short fields side by side — only once the row itself is wide enough (30rem
     * for two, 34rem for three), stacked otherwise. Titles, URLs and prose take
     * a row of their own.
     */
    function FieldRow({ columns = 2, className, ...props }: React.ComponentProps<"div"> & { columns?: 2 | 3 }) {
      return (
        <div className="@container/field-row min-w-0">
          <div
            data-slot="field-row"
            className={cn(
              "grid min-w-0 gap-4",
              columns === 3 ? "@[34rem]/field-row:grid-cols-3" : "@[30rem]/field-row:grid-cols-2",
              className
            )}
            {...props}
          />
        </div>
      )
    }
    
    export {
      Field,
      FieldLabel,
      FieldDescription,
      FieldError,
      FieldGroup,
      FieldLegend,
      FieldSeparator,
      FieldSet,
      FieldContent,
      FieldTitle,
      FieldControl,
      FieldHint,
      FieldRow,
      FieldTag,
      fieldVariants,
    }
    export type { FieldProps, FieldGroupProps }

版式

一个表单只有一套版式:

  • 标签在控件上方:13/600 ink2,离控件 6px。必填是粉色 *,读屏念「必填」。
  • 标签旁只放小签(FieldTag,20 高、不倾斜):数据来源、已锁定、只读。标签行右端放元信息(字数、接管人、变更时间);窄的时候元信息折到下一行,不挤标签。
  • 控件下方:12px 的说明(muted-foreground),或 12px 的红色错误(带图标、aria-live),二者只出一个。
  • 节奏:字段之间 16px;一组字段是一张卡(FieldGroup 带 title:纸面、缝线头、14/700 标题、16px 内边距),卡与卡之间 20px。
  • 两列只给短字段(集数、年份、类型、状态……),而且由这一行自己的宽度决定(FieldRow 是容器查询:够 30rem 才两列)。标题、网址、正文永远独占一行。

用法

components/anime-form.tsx
import { LockIcon } from "lucide-react"

import { Field, FieldControl, FieldGroup, FieldRow, FieldTag } from "@/components/ui/field"
import { Input } from "@/components/ui/input"

export function AnimeForm() {
  return (
    <FieldGroup title="基本信息" note="短字段两两成行">
      <Field label="中文标题" required meta="2 天前修改">
        <FieldControl>
          <Input defaultValue="药屋少女的呢喃 第二季" />
        </FieldControl>
      </Field>
      <FieldRow>
        <Field label="集数" tags={<FieldTag icon={LockIcon}>已锁定</FieldTag>}>
          <FieldControl>
            <Input type="number" className="anibt-num" />
          </FieldControl>
        </Field>
        <Field label="官网" error="请输入以 https:// 开头的网址">
          <FieldControl>
            <Input type="url" />
          </FieldControl>
        </Field>
      </FieldRow>
    </FieldGroup>
  )
}

FieldControl 把字段的 id、aria-describedby(指向说明或错误)和 aria-invalid 交给唯一的子控件,标签的 for 也指向它。给一组控件(分段控件、筛选签)命名时用 labelFor="group":标签不再用 for,控件改用 aria-labelledby。

选项行与选择卡

设置

锁字段(同步不会覆盖)

保存时会自动锁定所有被修改的字段;被锁定的字段不会被同步覆盖。

  • orientation="horizontal" 是选项行:控件在前,选项文字是 13/400 墨色正文,不是字段标签。
  • 用 FieldLabel 包住整个 Field 就是一张选择卡:1px 边的井,悬停边变粉;里面的单选或勾选被选中时整张卡变成粉色淡底。开关是设置不是选择,开着的设置卡仍是纸面。
  • FieldSet + FieldLegend 给一组勾选框起标题;FieldLegend 默认是 14/700 的小标题,variant="label" 是 13/600 的标签档。

组合写法

订阅 RSS

新资源发布后,你的阅读器会自动收到。

只收这个字幕组的发布。

关键词至少两个字,例如「简中」或「1080」。
推送

shadcn 的 field 写法原样可用:FieldSet、FieldLegend、FieldGroup、Field、FieldLabel、FieldContent、FieldTitle、FieldDescription、FieldError、FieldSeparator 的导出名和属性都相同,替换 components/ui/field.tsx 后原来的代码继续能编译。差别只在样子:A 的字号、间距和缝线。

orientation="responsive" 在 FieldGroup 宽于 28rem 时把标签放到控件左边。A 的表单标签一律在上方,这一档只为兼容 shadcn 的写法。

配合 react-hook-form

不需要额外的 form 组件:用 react-hook-form 的 Controller,把 fieldState 交给 Field。

用法
<Controller
  name="title"
  control={form.control}
  render={({ field, fieldState }) => (
    <Field label="中文标题" required error={fieldState.error?.message}>
      <FieldControl>
        <Input {...field} />
      </FieldControl>
    </Field>
  )}
/>

属性

Field
labelReactNode默认 —一次写完的写法:控件上方的标签。不给就是 shadcn 的组合写法
requiredboolean默认 false标签后的粉色 *,读屏念 requiredLabel
readOnlyboolean默认 false标签旁加一个「只读」小签
tagsReactNode默认 —标签旁的小签(FieldTag、StatusBadge)
metaReactNode默认 —标签行右端的元信息,12px muted
hintReactNode默认 —控件下的说明;有错误时不显示
errorReactNode默认 —控件下的错误,同时把控件标成 aria-invalid
idstring默认 —控件的 id;不给就自动生成
labelFor"control" | "group"默认 "control"group:标签给一组控件命名(aria-labelledby)
orientation"vertical" | "horizontal" | "responsive"默认 "vertical"horizontal 是选项行;responsive 只为兼容 shadcn
requiredLabelstring默认 "必填"读屏念的「必填」
readOnlyLabelstring默认 "只读"只读小签的字
FieldGroup
titleReactNode默认 —给了就是一张卡:缝线头加 14/700 标题;不给就是 16px 间距的一摞字段
noteReactNode默认 —标题旁 12px 的备注(卡片形式)
actionsReactNode默认 —卡片头右端的工具(卡片形式)
bodyClassNamestring默认 —卡片正文的类名
FieldRow
columns2 | 3默认 2这一行自己够宽(30rem / 34rem)时并排几列,否则一列
FieldTag
iconComponentType默认 —12px 图标,例如 LockIcon
tone"neutral" | "accent" | "ok" | "warn" | "danger"默认 "neutral"中性 / 粉 / 薄荷 / 琥珀 / 红
FieldError
childrenReactNode默认 —错误文字
errorsArray<{ message?: string } | undefined>默认 —校验器给的错误列表;去重后一条直接显示,多条成列表

其余导出:FieldControl(把 id 和描述交给子控件的 Slot)、FieldLabel、FieldTitle、FieldContent、FieldDescription(别名 FieldHint)、FieldSet、FieldLegend(variant 为 legend / label)、FieldSeparator(缝线分隔,可在中间放一个词)、fieldVariants。