
Spree Dashboard 表单开发规范基于 shadcn FieldGroup / InputGroup / ToggleGroup 的表单与输入组件实战指南【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree导读本文以 Spree 仓库中 shadcn 技能包的 表单与输入规范 为核心系统讲解在 Spree Dashboardpackages/dashboard-ui这类 shadcn/ui 组件体系中编写表单的正确姿势何时用FieldGroup Field、何时用InputGroup InputGroupInput、27 个选项如何用ToggleGroup组织、相关复选项如何用FieldSet FieldLegend分组以及验证与禁用状态的正确标记方式。读完本文你将能写出结构统一、可访问、可维护的表单代码并理解这些约束背后的组件源码设计与实现依据。为什么表单要用 FieldGroup Field而不是裸 divshadcn 表单的第一条铁律表单布局永远使用FieldGroupField组合禁止用裸divspace-y-*手动堆叠。这样做的直接原因是布局、间距、错误态、禁用态的对齐全部由组件统一接管而不是散落在每一处手写的 className 里。正确的入门写法FieldGroup Field FieldLabel htmlForemailEmail/FieldLabel Input idemail typeemail / /Field Field FieldLabel htmlForpasswordPassword/FieldLabel Input idpassword typepassword / /Field /FieldGroup两个高频变体设置页用横向布局Field orientationhorizontal标签在左、控件在右适合设置类页面视觉隐藏标签FieldLabel classNamesr-only仅保留无障碍语义视觉上不占空间。源码层面的印证在 packages/dashboard-ui/src/ui/field.tsx 中可以看到这套组合的完整实现FieldGroup是带data-slotfield-group的容器默认flex flex-col gap-5并且通过container/field-group建立了容器查询上下文供Field的responsive方向使用Field通过cva定义了三种orientation变体vertical默认上下堆叠、horizontal行内、标签自动flex-auto占位、responsive在容器宽度足够时自动从纵向切换为横向依赖md/field-group容器查询FieldContent提供控件区域的包裹层用于容纳说明文字与错误信息FieldLabel在标签内嵌整个Field即“卡片式标签”时自动切换为可点击目标具备cursor-pointer与 hover 高亮说明这套组件对点击区域做了专门的样式处理FieldError支持透传errors数组如 react-hook-form 的errors.name会自动去重多条错误渲染为ul列表并带rolealert无障碍语义。仓库真实用法分类编辑表单packages/dashboard/src/components/spree/categories/category-form.tsx 是规范落地的直接示例——它与 react-hook-form 配合一个Field对应一个字段错误通过FieldError统一渲染FieldGroup Field FieldLabel htmlForcategory-name{t(admin.fields.name.label)}/FieldLabel Input idcategory-name aria-invalid{!!errors.name || undefined} {...form.register(name)} / FieldError errors{[errors.name]} / /Field {/* 其他字段 ... */} /FieldGroup注意这里aria-invalid由 react-hook-form 的errors.name驱动错误消息统一交给FieldError展示——这正是“控件管状态、Field 管呈现”的分层思想。选择正确的表单控件规范给出的控件选择决策表直接决定你该引哪个组件需求使用组件简单文本输入Input预定义选项下拉Select可搜索下拉Combobox原生 HTML select无 JSnative-select布尔开关设置页Switch布尔勾选表单页Checkbox少量选项单选RadioGroup25 个选项间切换ToggleGroupToggleGroupItemOTP / 验证码InputOTP多行文本Textarea判断要点开关看语义——是“开启某项能力”用Switch是“在表单里勾选同意”用Checkbox选项看数量——几个互斥选项优先RadioGroup25 个分段式选择用ToggleGroup。这些控件在 packages/dashboard-ui/src/ui 下均有对应实现switch.tsx、checkbox.tsx、radio-group.tsx、input-otp.tsx等。InputGroup 内部必须用 InputGroupInput / InputGroupTextareaInputGroup是带边框、聚焦态、禁用态的整体容器但它不接受裸Input/Textarea作为子元素——否则会导致样式错位、边框重叠、聚焦环失效。// 错误裸 Input 塞进 InputGroup InputGroup Input placeholderSearch... / /InputGroup// 正确 import { InputGroup, InputGroupInput } from /components/ui/input-group InputGroup InputGroupInput placeholderSearch... / /InputGroup源码依据同样在 packages/dashboard-ui/src/ui/input-group.tsxInputGroup自身携带圆角边框与has-[[data-slotinput-group-control]:focus-visible]聚焦环、has-disabled禁用态、[data-slot][aria-invalidtrue]错误边框等状态样式InputGroupInput内部渲染的仍是Input但通过data-slotinput-group-control被剥掉自身的边框border-0 rounded-none shadow-none ring-0并设为flex-1由InputGroup统一接管外观InputGroupTextarea同理额外去掉resize适合组内多行输入。一句话总结边框、聚焦、禁用等“外壳样式”归InputGroup输入控件只负责纯输入行为。输入框内的按钮要用 InputGroup InputGroupAddon搜索框、密码显隐、金额单位这类“输入框内嵌按钮/图标”的需求禁止用relative定位 绝对定位按钮手搓// 错误手写定位 div classNamerelative Input placeholderSearch... classNamepr-10 / Button classNameabsolute right-0 top-0 sizeicon SearchIcon / /Button /div// 正确 import { InputGroup, InputGroupInput, InputGroupAddon } from /components/ui/input-group InputGroup InputGroupInput placeholderSearch... / InputGroupAddon Button sizeicon SearchIcon>// 错误手写循环 active 状态 const [selected, setSelected] useState(daily) div classNameflex 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 valuedailyDaily/ToggleGroupItem ToggleGroupItem valueweeklyWeekly/ToggleGroupItem ToggleGroupItem valuemonthlyMonthly/ToggleGroupItem /ToggleGroup配合Field可以做出带标题的分段选择Field orientationhorizontal FieldTitle idtheme-labelTheme/FieldTitle ToggleGroup aria-labelledbytheme-label spacing{2} ToggleGroupItem valuelightLight/ToggleGroupItem ToggleGroupItem valuedarkDark/ToggleGroupItem ToggleGroupItem valuesystemSystem/ToggleGroupItem /ToggleGroup /Field注意一个容易踩坑的 API 差异ToggleGroup的defaultValue与type/multiple属性在base与radix两套底层实现之间并不相同详细对照见 base-vs-radix.md 中的 ToggleGroup 一节核心差异是行为base 实现radix 实现单选无type属性defaultValue恒为数组如defaultValue{[daily]}需要typesingledefaultValue为字符串daily多选multiple布尔属性typemultiple受控单选value{[value]}配合onValueChange{(v) setValue(v[0])}包装/解包数组value{value}直接传递字符串具体采用哪套 API先通过npx shadcnlatest info查看项目配置里的base字段base还是radix再决定切勿凭记忆硬写。相关字段分组用 FieldSet FieldLegend一组语义相关的复选/单选/开关例如“偏好设置”下多个开关应该用FieldSetFieldLegend包裹而不是div 标题FieldSet FieldLegend variantlabelPreferences/FieldLegend FieldDescriptionSelect all that apply./FieldDescription FieldGroup classNamegap-3 Field orientationhorizontal Checkbox iddark / FieldLabel htmlFordark classNamefont-normalDark mode/FieldLabel /Field /FieldGroup /FieldSet源码层面field.tsxFieldSet渲染原生fieldset自带rounded-md border bg-card p-3的卡片外观FieldLegend是legendvariant支持legendtext-base默认与labeltext-sm两种字号FieldSet内部检测到checkbox-group/radio-group时还会自动收紧间距has-[[data-slotcheckbox-group]]:gap-3。原生fieldset/legend本身还带来无障碍红利屏幕阅读器会把组内控件与图例关联起来。仓库实例报告构建器 packages/dashboard/src/components/spree/reporting/report-builder.tsx 用两个FieldSet分别组织“指标metrics”与“过滤器filters”两组复选字段FieldLegend variantlabel做组标题、Field orientationhorizontalCheckboxFieldLabel排列每一项是这一规范在生产页面中的典型落地。验证与禁用状态双属性各司其职验证与禁用需要两个属性配合data-invalid/data-disabled作用于Field负责给标签、描述等“字段外壳”上样式aria-invalid/disabled作用于具体控件负责给输入控件本身加状态。// 验证失败 Field contenteditable="false">【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考