Form 表单
用于表单校验与提交处理的包裹组件
用法
import { Form } from '@lenso/ui';此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e, Apache-2.0.import { Check } from "@gravity-ui/icons";import { Button, Description, FieldError, Form, Input, Label, TextField } from "@lenso/ui";import type { FormEvent } from "react";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ form: { display: "flex", flexDirection: "column", width: 384, maxWidth: "100%", gap: 16 }, actions: { display: "flex", gap: 8 },});
export function Basic() { function onSubmit(event: FormEvent<HTMLFormElement>) { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget).entries()); alert(`Form submitted with: ${JSON.stringify(data, null, 2)}`); } return ( <Form xstyle={styles.form} onSubmit={onSubmit}> <TextField name="email" validate={(value) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(String(value)) ? null : "Please enter a valid email address" } > <Label required>Email</Label> <Input required type="email" placeholder="[email protected]" /> <FieldError /> </TextField> <TextField name="password" validate={(value) => { if (String(value).length < 8) return "Password must be at least 8 characters"; if (!/[A-Z]/.test(String(value))) return "Password must contain at least one uppercase letter"; if (!/[0-9]/.test(String(value))) return "Password must contain at least one number"; return null; }} > <Label required>Password</Label> <Input required minLength={8} type="password" placeholder="Enter your password" /> <Description>Must be at least 8 characters with 1 uppercase and 1 number</Description> <FieldError /> </TextField> <div {...stylex.props(styles.actions)}> <Button type="submit"> <Check aria-hidden="true" /> Submit </Button> <Button type="reset" variant="secondary"> Reset </Button> </div> </Form> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
组件结构
import {Form, Button} from '@lenso/ui';
export default () => ( <Form> {/* Form fields go here */} <Button type="submit"/> <Button type="reset"/> </Form>)示例
渲染函数
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e, Apache-2.0.import { Check } from "@gravity-ui/icons";import { Button, Description, FieldError, Form, Input, Label, TextField } from "@lenso/ui";import type { FormEvent } from "react";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ form: { display: "flex", width: 384, maxWidth: "100%", flexDirection: "column", gap: 16 }, actions: { display: "flex", gap: 8 },});
export function RenderFunction() { function onSubmit(event: FormEvent<HTMLFormElement>) { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget).entries()); alert(`Form submitted with: ${JSON.stringify(data, null, 2)}`); } return ( <Form xstyle={styles.form} render={(props) => <form {...props} data-custom="foo" />} onSubmit={onSubmit} > <TextField name="email" validate={(value) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(String(value)) ? null : "Please enter a valid email address" } > <Label required>Email</Label> <Input required type="email" placeholder="[email protected]" /> <FieldError /> </TextField> <TextField name="password" validate={(value) => { const password = String(value); if (password.length < 8) return "Password must be at least 8 characters"; if (!/[A-Z]/.test(password)) return "Password must contain at least one uppercase letter"; if (!/[0-9]/.test(password)) return "Password must contain at least one number"; return null; }} > <Label required>Password</Label> <Input required minLength={8} type="password" placeholder="Enter your password" /> <Description>Must be at least 8 characters with 1 uppercase and 1 number</Description> <FieldError /> </TextField> <div {...stylex.props(styles.actions)}> <Button type="submit"> <Check aria-hidden="true" /> Submit </Button> <Button type="reset" variant="secondary"> Reset </Button> </div> </Form> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
自定义样式
Tailwind CSS
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e, Apache-2.0.import { Button, Form, Input, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ form: { display: "flex", width: 320, maxWidth: "100%", flexDirection: "column", gap: 12, borderRadius: 12, borderWidth: 1, borderStyle: "solid", borderColor: "color-mix(in oklch, var(--border) 80%, transparent)", backgroundColor: "var(--surface)", padding: 16, boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", }, input: { backgroundColor: "var(--field-background)" }, button: { width: "100%" },});
export function CustomStyles() { return ( <Form xstyle={styles.form} onSubmit={(event) => event.preventDefault()}> <TextField name="email"> <Label required>Work email</Label> <Input required type="email" xstyle={styles.input} placeholder="[email protected]" /> </TextField> <Button xstyle={styles.button} type="submit"> Continue </Button> </Form> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
全局 CSS
要自定义表单布局与间距,可在 <Form> 上使用 className prop,或通过 @layer components 添加项目级类。
了解更多。
Form 渲染原生 <form> 元素,聚焦校验与提交。@lenso/tokens 中无专用 BEM 类——从全局 CSS 应用容器样式,控件级自定义请使用字段组件。
@layer components { .form-layout { @apply flex flex-col gap-4 rounded-xl border border-border bg-surface p-4 shadow-sm; }}<Form className="form-layout" onSubmit={handleSubmit}> {/* TextField, Input, Button, etc. */}</Form>分组字段与共享布局请使用 Fieldset,并定位 .fieldset、.fieldset__legend 等相关类。单个控件请参阅 TextField、Input、Label、FieldError 的 Global CSS 部分。
样式参考
HeroUI 对在 @lenso/tokens 中提供样式的组件遵循 BEM 方法论。
Form 渲染原生 <form> 元素,无专用 BEM 类。通过 className 应用布局、间距与表面样式。字段外观与校验状态来自 TextField、Input、Label、Description、FieldError 等子组件。结构化多字段布局请与 Fieldset 组合使用。
API 参考
Form
Form 组件是 React Aria Form 原语的包裹层,提供表单校验与提交处理能力。
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
action | string | FormHTMLAttributes['action'] | - | 提交表单数据的 URL |
className | string | - | 应用于 form 元素的 Tailwind CSS 类 |
children | React.ReactNode | - | 表单内容(字段、按钮等) |
encType | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain' | - | 表单数据提交的编码类型 |
method | 'get' | 'post' | - | 提交表单时使用的 HTTP 方法 |
onInvalid | (event: FormEvent<HTMLFormElement>) => void | - | 表单校验失败时调用。默认聚焦第一个无效字段。使用 preventDefault() 可自定义聚焦行为 |
onReset | (event: FormEvent<HTMLFormElement>) => void | - | 表单重置时调用 |
onSubmit | (event: FormEvent<HTMLFormElement>) => void | - | 表单提交时调用 |
target | '_self' | '_blank' | '_parent' | '_top' | - | 提交后显示响应的位置 |
validationBehavior | 'native' | 'aria' | 'native' | 使用原生 HTML 校验还是 ARIA 校验。native 阻止提交,aria 实时显示错误 |
validationErrors | ValidationErrors | - | 按字段名映射的服务端校验错误。立即显示,用户修改字段时清除 |
aria-label | string | - | 表单的无障碍标签 |
aria-labelledby | string | - | 标注表单的元素 ID。提供时创建 form landmark |
render | DOMRenderFunction<keyof React.JSX.IntrinsicElements, undefined> | - | 使用自定义 render 函数覆盖默认 DOM 元素 |
Form Validation
Form 组件集成 React Aria 校验系统,支持:
- 使用内置 HTML5 校验属性(
required、minLength、pattern等) - 在 TextField 组件上提供自定义校验函数
- 使用 FieldError 组件展示校验错误
- 在正确校验后处理表单提交
- 通过
validationErrorsprop 提供服务端校验错误
Validation Behavior
validationBehavior prop 控制校验展示方式:
native(默认):使用原生 HTML 校验,有错误时阻止提交aria:使用 ARIA 属性校验,用户输入时实时显示错误,不阻止提交
可在表单级或单个字段级设置此行为。
Form Submission
表单可通过多种方式提交:
- 传统提交:设置
actionprop 提交到 URL - JavaScript 处理:使用
onSubmit处理表单数据 - FormData API:在 submit 处理函数中使用 FormData API 访问表单数据
FormData 示例:
function handleSubmit(e: FormEvent<HTMLFormElement>) { e.preventDefault(); const formData = new FormData(e.currentTarget); const data = Object.fromEntries(formData); console.log('Form data:', data);}Integration with Form Fields
Form 组件与 HeroUI 表单字段组件无缝协作:
- TextField:带标签与校验的文本输入
- Checkbox:布尔选择
- RadioGroup:多选一
- Switch:开关控件
- Button:提交与重置操作
所有字段组件放在 Form 内时会自动集成 Form 的校验与提交行为。
Advanced Usage
更高级用法包括:
- 自定义校验上下文
- 表单 context provider
- 与第三方库集成
- 校验错误时的自定义焦点管理
请参阅 React Aria Form 文档。
无障碍
使用 React Aria 组件时,表单默认可访问。主要特性包括:
- 原生
<form>元素语义 - 使用
aria-label或aria-labelledby创建 form landmark - 校验错误时自动焦点管理
- 使用
validationBehavior="aria"时的 ARIA 校验属性
相关案例
See upstream Form showcases. Product showcases are not part of the local component runtime.