Files
2026-07-13 21:36:22 +08:00

4.9 KiB
Raw Permalink Blame History

表单与输入

目录

  • 表单使用 FieldGroup + Field
  • InputGroup 需配合 InputGroupInput/InputGroupTextarea
  • 输入框内的按钮使用 InputGroup + InputGroupAddon
  • 选项集合(2–7 个选项)使用 ToggleGroup
  • FieldSet + FieldLegend 用于对相关字段进行分组
  • 字段校验与禁用状态

表单使用 FieldGroup + Field

始终使用 FieldGroup + Field —— 切勿使用 space-y-* 的原始 div

<FieldGroup>
  <Field>
    <FieldLabel htmlFor="email">邮箱</FieldLabel>
    <Input id="email" type="email" />
  </Field>
  <Field>
    <FieldLabel htmlFor="password">密码</FieldLabel>
    <Input id="password" type="password" />
  </Field>
</FieldGroup>

在设置页面中使用 Field orientation="horizontal"。对于视觉上隐藏的标签,使用 FieldLabel className="sr-only"

选择表单控件:

  • 简单文本输入 → Input
  • 带预定义选项的下拉框 → Select
  • 可搜索下拉框 → Combobox
  • 原生 HTML 选择(无 JS)→ native-select
  • 布尔切换 → Switch(用于设置)或 Checkbox(用于表单)
  • 少数选项中的单选 → RadioGroup
  • 在 25 个选项之间切换 → ToggleGroup + ToggleGroupItem
  • OTP/验证码 → InputOTP
  • 多行文本 → Textarea

InputGroup 需配合 InputGroupInput/InputGroupTextarea

切勿在 InputGroup 内部使用原始的 InputTextarea

错误示例:

<InputGroup>
  <Input placeholder="搜索..." />
</InputGroup>

正确示例:

import { InputGroup, InputGroupInput } from "@/components/ui/input-group"

<InputGroup>
  <InputGroupInput placeholder="搜索..." />
</InputGroup>

输入框内的按钮使用 InputGroup + InputGroupAddon

切勿通过自定义定位将 Button 直接放在 Input 内部或紧邻其旁。

错误示例:

<div className="relative">
  <Input placeholder="搜索..." className="pr-10" />
  <Button className="absolute right-0 top-0" size="icon">
    <SearchIcon />
  </Button>
</div>

正确示例:

import { InputGroup, InputGroupInput, InputGroupAddon } from "@/components/ui/input-group"

<InputGroup>
  <InputGroupInput placeholder="搜索..." />
  <InputGroupAddon>
    <Button size="icon">
      <SearchIcon data-icon="inline-start" />
    </Button>
  </InputGroupAddon>
</InputGroup>

选项集合(2–7 个选项)使用 ToggleGroup

不要手动循环 Button 组件并自行管理激活状态。

错误示例:

const [selected, setSelected] = useState("daily")

<div className="flex gap-2">
  {["daily", "weekly", "monthly"].map((option) => (
    <Button
      key={option}
      variant={selected === option ? "default" : "outline"}
      onClick={() => setSelected(option)}
    >
      {option}
    </Button>
  ))}
</div>

正确示例:

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

<ToggleGroup spacing={2}>
  <ToggleGroupItem value="daily">每日</ToggleGroupItem>
  <ToggleGroupItem value="weekly">每周</ToggleGroupItem>
  <ToggleGroupItem value="monthly">每月</ToggleGroupItem>
</ToggleGroup>

结合 Field 使用,创建带标签的切换组:

<Field orientation="horizontal">
  <FieldTitle id="theme-label">主题</FieldTitle>
  <ToggleGroup aria-labelledby="theme-label" spacing={2}>
    <ToggleGroupItem value="light">浅色</ToggleGroupItem>
    <ToggleGroupItem value="dark">深色</ToggleGroupItem>
    <ToggleGroupItem value="system">跟随系统</ToggleGroupItem>
  </ToggleGroup>
</Field>

注意: defaultValuetype/multiple 属性在 base 与 radix 之间存在差异。详见 base-vs-radix.md


FieldSet + FieldLegend 用于对相关字段进行分组

对相关的复选框、单选按钮或开关,使用 FieldSet + FieldLegend —— 而不是在 div 中加一个标题:

<FieldSet>
  <FieldLegend variant="label">偏好设置</FieldLegend>
  <FieldDescription>请选择所有适用的选项。</FieldDescription>
  <FieldGroup className="gap-3">
    <Field orientation="horizontal">
      <Checkbox id="dark" />
      <FieldLabel htmlFor="dark" className="font-normal">深色模式</FieldLabel>
    </Field>
  </FieldGroup>
</FieldSet>

字段校验与禁用状态

两个属性都需要 —— data-invalid/data-disabled 用于设置字段的样式(标签、描述),而 aria-invalid/disabled 用于设置控件的样式。

// 无效状态
<Field data-invalid>
  <FieldLabel htmlFor="email">邮箱</FieldLabel>
  <Input id="email" aria-invalid />
  <FieldDescription>邮箱地址无效。</FieldDescription>
</Field>

// 禁用状态
<Field data-disabled>
  <FieldLabel htmlFor="email">邮箱</FieldLabel>
  <Input id="email" disabled />
</Field>

适用于所有控件:InputTextareaSelectCheckboxRadioGroupItemSwitchSliderNativeSelectInputOTP