Skip to content
Lenso UI

v3.0.0-alpha.34

用 Form、TextField、RadioGroup、Label、Input、Fieldset 等简洁 API 构建表单的核心组件。

2025 年 10 月 15 日

此版本引入了一系列基于表单的组件、表单字段 token,重新组织了 Storybook,并对各组件之间的 data-slot 标识做了统一对齐。

安装

升级到最新版本:

npm i @lenso/tokens@alpha @lenso/ui@alpha

正在使用 AI 助手? 只需对它说一句「Hey Cursor,把 HeroUI 升级到最新版本」,AI 助手就会自动对比版本并应用必要的变更。了解更多请参阅 HeroUI MCP 服务器。

新增功能

基于表单的组件

我们引入了一整套基于 React Aria Components 构建的表单类组件,为构建表单提供了无障碍且可组合的基础构件。这些组件包括 Description、FieldError、Fieldset、Form、Input、Label、RadioGroup、TextField 与 TextArea。

Description

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Description, Input, Label } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { useId } from "react";import { demoStyles } from "../../demo.stylex";
export function Basic() {  const id = useId();  return (    <div {...stylex.props(demoStyles.column)}>      <Label htmlFor={id}>Email</Label>      <Input        aria-describedby={`${id}-description`}        xstyle={demoStyles.field}        id={id}        placeholder="[email protected]"        type="email"      />      <Description id={`${id}-description`}>        We'll never share your email with anyone else.      </Description>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

FieldError

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { FieldError, Input, Label, TextField } from "@lenso/ui";import { useState } from "react";import { demoStyles } from "../../demo.stylex";
export function Basic() {  const [value, setValue] = useState("jr");  const invalid = value.length > 0 && value.length < 3;  return (    <TextField xstyle={demoStyles.field} invalid={invalid}>      <Label>Username</Label>      <Input placeholder="Enter username" value={value} onValueChange={setValue} />      {invalid && <FieldError match>Username must be at least 3 characters</FieldError>}    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Fieldset

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { FloppyDisk } from "@gravity-ui/icons";import {  Button,  Description,  FieldError,  FieldGroup,  Fieldset,  Form,  Input,  Label,  TextArea,  TextField,} from "@lenso/ui";import { useId, type FormEvent } from "react";import { demoStyles } from "../../demo.stylex";
export function Basic() {  const descriptionId = useId();  function onSubmit(event: FormEvent<HTMLFormElement>) {    event.preventDefault();    alert("Form submitted successfully!");  }  return (    <Form xstyle={demoStyles.wideColumn} onSubmit={onSubmit}>      <Fieldset aria-describedby={descriptionId}>        <Fieldset.Legend>Profile Settings</Fieldset.Legend>        <Description id={descriptionId}>Update your profile information.</Description>        <FieldGroup>          <TextField            name="name"            validate={(value) =>              String(value).length < 3 ? "Name must be at least 3 characters" : null            }          >            <Label required>Name</Label>            <Input required placeholder="John Doe" />            <FieldError />          </TextField>          <TextField name="email">            <Label required>Email</Label>            <Input required type="email" placeholder="[email protected]" />            <FieldError />          </TextField>          <TextField            name="bio"            validate={(value) =>              String(value).length < 10 ? "Bio must be at least 10 characters" : null            }          >            <Label required>Bio</Label>            <TextArea required placeholder="Tell us about yourself..." />            <Description>Minimum 10 characters</Description>            <FieldError />          </TextField>        </FieldGroup>        <Fieldset.Actions>          <Button type="submit">            <FloppyDisk aria-hidden="true" />            Save changes          </Button>          <Button type="reset" variant="secondary">            Cancel          </Button>        </Fieldset.Actions>      </Fieldset>    </Form>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

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", 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.

Input

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
import { Input } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
// HeroUI v3.2.6, e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e, Apache-2.0.const styles = stylex.create({ field: { width: 256 } });export function Basic() {  return <Input aria-label="Name" xstyle={styles.field} placeholder="Enter your name" />;}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Label

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Input, Label } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { useId } from "react";import { demoStyles } from "../../demo.stylex";
export function Basic() {  const id = useId();  return (    <div {...stylex.props(demoStyles.column)}>      <Label htmlFor={id}>Name</Label>      <Input xstyle={demoStyles.field} id={id} placeholder="Enter your name" type="text" />    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

RadioGroup

此预览复用英文版适配,不代表中文源示例已完成本地实现。

export { Basic } from "./examples";

Local adaptation source above. Derived from HeroUI v3.2.6 source.

TextField

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { Input, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 256 } });
export function Basic() {  return (    <TextField name="email" xstyle={styles.field}>      <Label>Email</Label>      <Input type="email" placeholder="Enter your email" />    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

TextArea

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { TextArea } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ field: { width: 384, height: 128 } });export function Basic() {  return (    <TextArea      aria-label="Quick project update"      xstyle={styles.field}      placeholder="Share a quick project update..."    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

表单字段 token

引入 --field-* 表单字段 token,确保各表单组件之间样式保持一致。--field-* 变量的具体说明请参阅 主题。

Storybook 重新组织

按类别重新组织了 Storybook,方便导航与组件查找。

Skeleton 动画 token

🚧 破坏性变更: 为了与其他组件 token 保持一致,Skeleton 中的 --skeleton-default-animation-type 已重命名为 --skeleton-animation。

data-slot 对齐

我们统一了各组件的 data-slot 标识,使样式与定制更加一致。这项标准化让通过 CSS 选择器定位特定组件部件变得更容易,整体上也优化了自定义组件样式时的开发体验。

组件现在使用一致的 data-slot 属性,例如:

  • data-slot="base" —— 用于根元素
  • data-slot="label" —— 用于标签文本
  • data-slot="description" —— 用于描述文本
  • data-slot="error" —— 用于错误信息

这样在所有表单组件中都能用可预期的方式进行 CSS 定位:

.radio {  [data-slot="label"] {    /* Styles apply to radio labels */  }}

文档改进

组件文档

迁移指南

Skeleton 组件迁移

  1. 更新动画 token:
    • 将 --skeleton-default-animation-type 替换为 --skeleton-animation

链接

贡献者

感谢每一位为本次发布做出贡献的开发者!