跳转到内容

Form 表单 ​

管理一组字段的值、校验、提交和重置。

用法 ​

提交并校验表单

组件结构 ​

加粗的是必需部件。

data-scope="form":root · field-group · error-summary · error-summary-item · submit-trigger · reset-trigger

示例 ​

校验时机 ​

在失焦或输入时校验

状态 ​

禁用与只读表单

整表禁用

只读:能提交,改不动

异步校验 ​

提交前检查用户名

提交时先问一次服务端,占用的名字会被挡下

声明式规则 ​

配置字段校验规则

布局 ​

设置纵向、横向、行内或网格布局

设计指引 ​

何时使用 ​

  • 多个字段需要一起提交或校验。
  • 需要统一管理错误信息和校验时机。

何时不用 ​

  • 只有一个立即生效的控件时直接处理其值。
  • 只需要标签、说明和错误信息时使用表单字段。

特性 ​

  • validateOn 设置输入、失焦或提交时校验。
  • 支持声明式规则、自定义校验和异步校验。
  • 字段值、错误和校验状态均可受控。
  • 错误汇总可跳转到对应字段。
  • 支持纵向、横向、行内和网格布局。
  • 嵌套字段与字段数组使用显式 FormPath。

组合 ​

最佳实践 ​

  • 首次校验优先放在失焦或提交时。
  • 提交失败后聚焦第一个错误字段。
  • 异步校验期间显示明确的加载状态。

反模式 ​

  • 在用户尚未尝试提交时持续显示全部错误。
  • 只在前端执行关键业务校验。

API 参考 ​

产物 ​

层值
自定义元素<xh-form>
Vue 组件XhFormErrorSummary XhFormErrorSummaryItem XhFormFieldGroup XhFormResetTrigger XhFormRoot XhFormSubmitTrigger
组合式函数useForm
状态机formMachine
皮肤@xihan-ui/styles/form.css

Props ​

属性类型必填说明
valuesFormValues受控值表;提供即受控:cell 直读 prop,写入只发 onValuesChange 不落内部值。
defaultValuesFormValues非受控初值,同时也是 reset 的落点。
errorsFormErrorPatch受控错误表;提供即受控。空串会被清理(空串不是一条错误)。
defaultErrorsFormErrorPatch
validate(values: FormValues) => FormErrorPatch | Promise<FormErrorPatch>校验函数。返回字段名 → 错误文案,无错的字段给空串或省略; 允许返回 Promise(远程校验),期间 validating 置真。 与 rules 并用时同字段两边都报错按 rules 的文案计算。
rulesFormRules声明式校验规则:字段名 → 一条或一组规则,与 validate 可并用。
validateMessagesFormValidateMessages规则文案模板,{name}/{min}/{max} 现场代入;未提供时使用内置英文模板。
validateOnFormValidateOn校验时机,默认 submit。
layoutFormLayout排布,默认 vertical。
columnsFormColumnsgrid 排布下的列数:1 至 4 的整数,未提供时按一列排列;范围外的值也按一列排列。 也接受断点对象 { base, sm, md, lg, xl },逐档写各自的列数,未写的档沿用更窄的一档。 其余三档排布下不参与排版。
labelWidthnumber | stringhorizontal 下标签列宽(number 视作 px),整表统一、字段据此对齐。
labelAlign'start' | 'end'horizontal 下标签文字的对齐缘,默认 end(贴近控件)。
disabledboolean整个表单禁用:提交、重置、写值一概不发生,两个按钮带原生 disabled。
readOnlyboolean只读:写值与重置不发生,但仍可提交。
onValuesChange(details: FormValuesChangeDetails) => void值表变化意图回调;受控时是唯一出口,非受控时随内部写入一并通知。
onErrorsChange(details: FormErrorsChangeDetails) => void错误表变化意图回调;受控时是唯一出口。
onSubmit(details: FormSubmitDetails) => void校验通过才调用。
onInvalid(details: FormInvalidDetails) => void校验不通过时调用,附带拦截的整张错误表。
onValidationError(details: FormValidationErrorDetails) => void校验器抛错或拒绝 Promise 时调用;不触发 onInvalid 或 onSubmit。

事件 ​

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
values-changeFormValuesChangeDetails值表变化;detail 为 { values }
errors-changeFormErrorsChangeDetails错误表变化;detail 为 { errors }
submitFormSubmitDetails校验通过才派发;detail 为 { values }
invalidFormInvalidDetails校验不通过时派发;detail 为 { errors, values }
validation-errorFormValidationErrorDetails校验器执行异常;detail 为 { cause, values, field },field 为 null 表示整表提交

插槽 ​

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhFormErrorSummarydefaultFormErrorSummarySlotProps
XhFormErrorSummaryItemdefaultFormErrorSummaryItemSlotProps
XhFormFieldGroupdefaultFormFieldGroupSlotProps
XhFormRootdefaultFormRootSlotProps

React 适配器 props ​

只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

React 组件属性类型必填说明
XhFormErrorSummarychildrenSlotChildren<FormErrorSummarySlotProps>
XhFormErrorSummaryItemnameFormPath是该条指向哪个字段路径。
XhFormErrorSummaryItemchildrenSlotChildren<FormErrorSummaryItemSlotProps>
XhFormFieldGroupnameFormPath是字段路径;字符串含点仍是单键,数组才表示层级。
XhFormFieldGroupspanFormFieldSpangrid 排布下该格占多宽:1 至 4 跨相应列数,'full' 占满整行;未写时占一列。
XhFormFieldGroupchildrenSlotChildren<FormFieldGroupSlotProps>
XhFormRootchildrenSlotChildren<FormRootSlotProps>

状态 ​

公开状态写入 data-state。

部件取值
root'invalid' | 'idle'
error-summary'invalid' | 'idle'

以下名称仅用于内部状态机。

状态:idle · invalid

事件:SUBMIT · RESET · VALIDATION.PASS · VALIDATION.FAIL · FIELD.SET · FIELD.ARRAY.MUTATE · FIELD.BLUR · ERROR.SET · ERRORS.CLEAR · ERROR.FOCUS · PRESS.START · PRESS.END

判据:isEnabled · isEditable · isValidationSnapshotCurrent · canPress

connect API ​

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
valuesFormValues当前的值表。
errorsFormErrors当前的错误表(已清理)。
errorNamesFormPath[]出错的字段名,插入顺序。
errorCountnumber
invalidboolean错误表非空。与是否提交失败过无关,挂载时作者预置的错误也计入。
submitFailedboolean上一次提交被拦截:错误摘要据此显示。
validatingboolean异步校验进行中(提交或逐字段都计入)。
validationErrorFormValidationErrorDetails | null校验服务异常;null 表示没有异常,字段错误仍从 errors 读取。
disabledboolean
readOnlyboolean
validateOnFormValidateOn
layoutFormLayout当前的排布档。
getFieldId(name: FormPath) => string字段容器的 DOM id;错误摘要的链接指向它。
getFieldValue(name: FormPath) => unknown
getFieldError(name: FormPath) => string | undefined该字段当前的错误文案;无错时为 undefined。
isFieldInvalid(name: FormPath) => boolean
isFieldRequired(name: FormPath) => boolean该字段的规则中声明了 required:字段的必填标记由此推导。
setFieldValue(name: FormPath, value: unknown) => void写一个字段的值;禁用或只读时不生效。
setFieldError(name: FormPath, message?: string) => void写一个字段的错误;未提供文案(或提供空串)即清除该条。
clearErrors() => void
submit() => void执行完整的校验与提交流程,与用户按提交键走同一路径。
reset() => void值与错误都回到初始;禁用或只读时不生效。
getRootProps() => T['element']
getFieldGroupProps(props: FormFieldGroupProps) => T['element']
getErrorSummaryProps() => T['element']
getErrorSummaryItemProps(props: FormErrorSummaryItemProps) => T['element']
getSubmitTriggerProps() => T['button']
getResetTriggerProps() => T['button']

无障碍 ​

键盘 ​

规格出处:W3C APG

按键生效条件行为
Enter / Spaceheld on submit-trigger / reset-trigger / error-summary-item, not disabled, no async validation in flight按住期间该部件投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下,异步校验开跑(提交在途)或条目所指字段改好时一并撤下

ARIA ​

以下属性由 connect 生成。

部件属性值
error-summaryaria-atomic'true'
error-summaryaria-live'assertive'
error-summaryrole'alert'

样式参考 ​

皮肤 ​

@xihan-ui/styles/form.css 使用 [data-scope="form"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides。

forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。

数据属性 ​

由 connect 生成;条件不成立时不输出无值属性。

部件属性值
rootdata-columnscolumns.base
rootdata-columns-lgcolumns.lg
rootdata-columns-mdcolumns.md
rootdata-columns-smcolumns.sm
rootdata-columns-xlcolumns.xl
rootdata-disabled''(条件成立时才出现)
rootdata-invalid''(条件成立时才出现)
rootdata-label-alignprops.labelAlign
rootdata-layoutprops.layout
rootdata-readonly''(条件成立时才出现)
rootdata-state'invalid' | 'idle'
field-groupdata-disabled''(条件成立时才出现)
field-groupdata-invalid''(条件成立时才出现)
field-groupdata-readonly''(条件成立时才出现)
field-groupdata-spanfieldSpan(field.span)
error-summarydata-countString(errorCount)
error-summarydata-state'invalid' | 'idle'
error-summary-itemdata-invalid''(条件成立时才出现)
error-summary-itemdata-pressed''(条件成立时才出现)
submit-triggerdata-disabled''(条件成立时才出现)
submit-triggerdata-pressed''(条件成立时才出现)
submit-triggerdata-xh-action-control''
submit-triggerdata-xh-action-display'always'
submit-triggerdata-xh-action-profile'text'
submit-triggerdata-xh-action-size'md'
submit-triggerdata-xh-action-variant'solid'
submit-triggerdata-xh-ink-surface''
reset-triggerdata-disabled''(条件成立时才出现)
reset-triggerdata-pressed''(条件成立时才出现)
reset-triggerdata-xh-action-control''
reset-triggerdata-xh-action-display'always'
reset-triggerdata-xh-action-profile'text'
reset-triggerdata-xh-action-size'md'
reset-triggerdata-xh-action-variant'outline'

CSS 变量 ​

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-form-field-gapfield-groupgapdefault--xh-space-1form 的 field-group 部件 gap 覆盖槽。
--xh-form-field-invalid-borderfield-groupborder-inline-startinvalid--xh-border-invalidform 的 field-group 部件 border-inline-start 覆盖槽。
--xh-form-field-invalid-pxfield-grouppadding-inline-startinvalid--xh-space-2form 的 field-group 部件 padding-inline-start 覆盖槽。
--xh-form-gaprootgapdefault--xh-stack-gap-mdform 的 root 部件 gap 覆盖槽。
--xh-form-inline-gaprootcolumn-gaplayout=inline--xh-space-4form 的 root 部件 column-gap 覆盖槽。
--xh-form-label-wrootgrid-template-columnslayout=horizontal30%form 的 root 部件 grid-template-columns 覆盖槽。
--xh-form-submit-bgsubmit-trigger--xh-ink-surface
background-color
default
xh-ink-surface
--xh-_action-variant-bg-restform 的 submit-trigger 部件 --xh-ink-surface、background-color 覆盖槽。
--xh-form-submit-bg-activesubmit-triggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-bg-pressedform 的 submit-trigger 部件 background-color 覆盖槽。
--xh-form-submit-bg-hoversubmit-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hoverform 的 submit-trigger 部件 background-color 覆盖槽。
--xh-form-submit-bordersubmit-triggerborderdefault--xh-_action-variant-border-restform 的 submit-trigger 部件 border 覆盖槽。
--xh-form-submit-border-activesubmit-triggerborder-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-border-pressedform 的 submit-trigger 部件 border-color 覆盖槽。
--xh-form-submit-border-hoversubmit-triggerborder-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-border-hoverform 的 submit-trigger 部件 border-color 覆盖槽。
--xh-form-submit-fgsubmit-triggercolordefault
disabled
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-fg-hover
--xh-_action-variant-fg-pressed
--xh-_action-variant-fg-rest
form 的 submit-trigger 部件 color 覆盖槽。
--xh-form-submit-shadowsubmit-triggerbox-shadowdefault
disabled
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_highlight-brandform 的 submit-trigger 部件 box-shadow 覆盖槽。
--xh-form-summary-bgerror-summarybackgrounddefault--xh-bg-surfaceform 的 error-summary 部件 background 覆盖槽。
--xh-form-summary-bordererror-summaryborderdefault--xh-border-invalidform 的 error-summary 部件 border 覆盖槽。
--xh-form-summary-fgerror-summarycolordefault--xh-fg-dangerform 的 error-summary 部件 color 覆盖槽。
--xh-form-summary-font-sizeerror-summaryfont-sizedefault--xh-text-body-sizeform 的 error-summary 部件 font-size 覆盖槽。
--xh-form-summary-gaperror-summarygapdefault--xh-space-1_5form 的 error-summary 部件 gap 覆盖槽。
--xh-form-summary-item-bgerror-summary-itembackgrounddefaulttransparentform 的 error-summary-item 部件 background 覆盖槽。
--xh-form-summary-item-bg-hovererror-summary-itembackgroundhover--xh-bg-subtleform 的 error-summary-item 部件 background 覆盖槽。
--xh-form-summary-item-bg-pressederror-summary-itembackgroundis(:active, [data-pressed])
pressed
--xh-bg-subtle-hoverform 的 error-summary-item 部件 background 覆盖槽。
--xh-form-summary-item-fg-hovererror-summary-itemcolorhover
is(:active, [data-pressed])
pressed
--xh-fg-danger-hoverform 的 error-summary-item 部件 color 覆盖槽。
--xh-form-summary-item-font-sizeerror-summary-itemfont-sizedefault--xh-text-secondary-sizeform 的 error-summary-item 部件 font-size 覆盖槽。
--xh-form-summary-item-pxerror-summary-itempadding-inlinedefault--xh-space-1form 的 error-summary-item 部件 padding-inline 覆盖槽。
--xh-form-summary-item-radiuserror-summary-itemborder-radiusdefault--xh-shape-controlform 的 error-summary-item 部件 border-radius 覆盖槽。
--xh-form-summary-item-underline-offseterror-summary-itemtext-underline-offsetdefault--xh-space-0_5form 的 error-summary-item 部件 text-underline-offset 覆盖槽。
--xh-form-summary-pxerror-summarypadding-inlinedefault--xh-control-px-mdform 的 error-summary 部件 padding-inline 覆盖槽。
--xh-form-summary-pyerror-summarypadding-blockdefault--xh-space-3form 的 error-summary 部件 padding-block 覆盖槽。
--xh-form-summary-radiuserror-summaryborder-radiusdefault--xh-shape-surfaceform 的 error-summary 部件 border-radius 覆盖槽。
--xh-form-summary-shadowerror-summarybox-shadowdefaultnoneform 的 error-summary 部件 box-shadow 覆盖槽。
--xh-form-trigger-bgreset-trigger--xh-ink-surface
background-color
default
xh-ink-surface
--xh-_action-variant-bg-restform 的 reset-trigger 部件 --xh-ink-surface、background-color 覆盖槽。
--xh-form-trigger-bg-activereset-triggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-bg-pressedform 的 reset-trigger 部件 background-color 覆盖槽。
--xh-form-trigger-bg-disabledreset-trigger
submit-trigger
--xh-ink-surface
background-color
disabled
xh-ink-surface
--xh-_action-variant-bg-disabledform 的 reset-trigger、submit-trigger 部件 --xh-ink-surface、background-color 覆盖槽。
--xh-form-trigger-bg-hoverreset-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hoverform 的 reset-trigger 部件 background-color 覆盖槽。
--xh-form-trigger-borderreset-triggerborderdefault--xh-_action-variant-border-restform 的 reset-trigger 部件 border 覆盖槽。
--xh-form-trigger-border-disabledreset-trigger
submit-trigger
border-colordisabled--xh-_action-variant-border-disabledform 的 reset-trigger、submit-trigger 部件 border-color 覆盖槽。
--xh-form-trigger-border-hoverreset-triggerborder-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-border-hoverform 的 reset-trigger 部件 border-color 覆盖槽。
--xh-form-trigger-fgreset-triggercolordefault
disabled
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-fg-hover
--xh-_action-variant-fg-pressed
--xh-_action-variant-fg-rest
form 的 reset-trigger 部件 color 覆盖槽。
--xh-form-trigger-font-sizereset-trigger
submit-trigger
font-sizedefault--xh-text-body-sizeform 的 reset-trigger、submit-trigger 部件 font-size 覆盖槽。
--xh-form-trigger-hreset-trigger
submit-trigger
block-sizedefault--xh-_action-profile-visual-sizeform 的 reset-trigger、submit-trigger 部件 block-size 覆盖槽。
--xh-form-trigger-pxreset-trigger
submit-trigger
padding-inlinedefault--xh-_action-profile-padding-inlineform 的 reset-trigger、submit-trigger 部件 padding-inline 覆盖槽。
--xh-form-trigger-radiusreset-trigger
submit-trigger
border-radiusdefault--xh-shape-controlform 的 reset-trigger、submit-trigger 部件 border-radius 覆盖槽。

动效 ​

动效角色:按压 · 状态 · 出现(见动效规范)。

共享关键帧 xh-drop-in 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立;background-color · color 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

响应式 ​

皮肤按视口分档:min-width: 1024px · min-width: 1280px · min-width: 640px · min-width: 768px。

RTL ​

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。

Released under The MIT License