news 2026/9/19 23:56:46

Yeti 表单控件 Field 组件完全指南:标签、提示与自显现错误的状态驱动表单

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Yeti 表单控件 Field 组件完全指南:标签、提示与自显现错误的状态驱动表单

Yeti 表单控件 Field 组件完全指南:标签、提示与自显现错误的状态驱动表单

【免费下载链接】yetiA CSS-first, native, zero-build layout and styling framework for web designers.项目地址: https://gitcode.com/gh_mirrors/fo/yeti

Yeti 是一个 CSS-first、原生、零构建的布局与样式框架(项目说明),而field是其中负责"一个表单控件及其附属信息"的组件:它把一个 label、一个原生控件、一段可选 hint 提示和一段错误信息捆绑为一个整体。本文以 docs/field.md 为骨架,结合 field.css 源码、manifest.json 声明与 field.spec.js 测试,完整讲解 field 的用法、属性、子元素、设计令牌与无障碍细节,让你能直接用纯 HTML + 类名搭建无需 JavaScript 的校验型表单。

Field 是什么:一个控件的一切

Yeti 的 Field 组件定义非常凝练:一个表单控件(text、email、number、select、textarea、checkbox、radio、switch、range 等任意一种),配上它的标签、可选的帮助文本,以及一个"当控件无效时自己显现出来"的错误信息

它解决的是裸 HTML 表单做不到的三件事:

  1. label 与控件的关联forid配对);
  2. 帮助文本(hint)的排版与语义挂载;
  3. 错误信息的显示时机——既不在页面加载时全红一片,也不需要一行脚本。

在 Yeti 的实践里,一个表单通常是stack布局下若干 field 与一个 button 的纵向组合;field 负责承接 HTML 语义本身无法表达的那部分。

最小示例:一个带提示与错误的邮箱输入

官方文档给出如下最典型的结构(example.html 中完全一致):

<div class="field"> <label for="email">Email</label> <input id="email" type="email" required aria-describedby="email-hint email-error"> <p id="email-hint">.field { display: flex; flex-direction: column; gap: var(--yeti-field-gap); } .field > * { margin: 0; } .field > :is(label, legend) { font-weight: var(--yeti-weight-strong); font-size: var(--_yeti-size-text); }

label 与 legend 使用粗体(--yeti-weight-strong),字号跟随内部令牌--_yeti-size-text(由data-size决定)。

2. 控件读取"控件令牌",主题一处改动处处生效

文本类控件(input 的非特殊类型、select、textarea)直接以原生元素配合令牌样式:

.field :is(input:not(:where([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"])), select, textarea) { min-block-size: calc(var(--yeti-control-size) + max(0px, var(--_yeti-size-space) - var(--yeti-space-sm))); padding: 0 var(--_yeti-size-space); font: inherit; font-size: var(--_yeti-size-text); color: var(--yeti-color-text); background-color: var(--yeti-control-surface); border: var(--yeti-border-width) solid var(--yeti-control-border); border-radius: var(--yeti-control-radius); transition: border-color var(--yeti-duration-fast) var(--yeti-ease); }

这正是文档强调的:控件由--yeti-control-*一组令牌统一样式,主题只要改了--yeti-control-radius,所有输入框的圆角随之改变。例如 soft.css 主题正是通过--yeti-control-radius: var(--yeti-radius-md);一行让控件更圆润。

特殊处理还包括:

  • textarea额外设置padding-blockmin-block-size: 4lh(约 4 行文本高度,这里用到了lh单位,见"浏览器支持"一节);
  • select使用appearance: none去掉原生外观,把背景替换为--yeti-control-chevron这个内联 SVG 箭头图像,并预留右侧 padding 防止文字压到箭头;
  • :focus-visible时边框变为--yeti-color-border-strong,键盘焦点清晰可辨。

3. 错误显现的时机:::user-invalidaria-invalid

错误默认隐藏,仅在两种情况下显现:

.field > [data-error] { display: none; ... } .field:has(:user-invalid, [aria-invalid="true"]) > [data-error] { display: block; }
  • 客户端校验:控件:user-invalid命中——即用户已经"触碰"过控件且值无效(email输入nope后失焦即触发),错误显现;
  • 服务端往返:服务器返回后你设置aria-invalid="true",错误立即显现,无需用户再操作。

同时,无效控件自身的边框会转为告警色(--yeti-color-alert),且只红自己的控件:

.field:has(> :user-invalid, > [aria-invalid="true"], > .affix :is(:user-invalid, [aria-invalid="true"])) > :is(input, select, textarea), .field:has(> .affix :is(:user-invalid, [aria-invalid="true"])) > .affix > :is(input, select) { border-color: var(--yeti-color-alert); }

测试 field.spec.js 验证了这套行为:#email-error初始display: none;填入nope并 blur 后变为block且边框色改变;而#name-error(夹具中通过aria-invalid="true"模拟服务端错误)在页面打开时就直接显示。另一个用例专门验证"一个无效控件只红自己的控件"——给#p-aaria-invalid="true"后,兄弟#p-b的边框保持不变。

4. required 的视觉标记

.field:has([required]) > :is(label, legend)::after { content: " *"; color: var(--yeti-color-alert-text); }

required控件会在 label(或 legend)后追加一个星号标记,颜色为告警文字色。测试通过读取getComputedStyle(..., '::after').content确认它包含*。注意:星号只是装饰,真正被辅助技术朗读的是required属性本身(见"无障碍"一节)。

5. checkbox 与 radio:自动内联 + 变体色勾选

checkbox 与 radio 使用appearance: none重绘,尺寸为1.25em,勾选时:

.field input:is([type="checkbox"], [type="radio"]):checked { background-color: var(--_yeti-variant); border-color: var(--_yeti-variant); box-shadow: inset 0 0 0 0.2em var(--yeti-control-surface); }

变体色实心中心 + 表面色内环的视觉效果(radio 圆角为 50%)。测试会验证勾选后背景色等于--_yeti-variant解析出的颜色、取消勾选后背景回到--yeti-control-surface

布局上,checkbox/radio 所在的 field自动变成内联行(label 在控件右侧),不需要任何属性:

.field:is([data-inline], :has(> input:is([type="checkbox"], [type="radio"]))) { flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--yeti-space-xs); }

测试用几何断言确认了"label 左侧大于控件右侧、二者垂直居中"。内联时 hint 与 error 通过flex-basis: 100%换行独占一行。

6. fieldset 分组:多个控件共享一个 legend

当需要给一组选项(如一组 radio)命名时,把field类直接用在fieldset上:

<fieldset class="field"> <legend>Notify me by</legend> <div class="field"><input id="n-email" type="checkbox" name="notify" value="email"><label for="n-email">Email</label></div> <div class="field"><input id="n-sms" type="checkbox" name="notify" value="sms"><label for="n-sms">Text message</label></div> <p>fieldset.field { padding: var(--yeti-space-sm) var(--yeti-space-md); border: var(--yeti-border-width) solid var(--yeti-control-border); border-radius: var(--yeti-control-radius); } fieldset.field > legend { padding-inline: var(--yeti-space-xs); }

7. switch:一个 checkbox 加上role="switch"

checkbox 加role="switch"就变成开关:轨道 + 滑块,打开时滑块滑到末端且轨道取变体色:

<div class="field"><input id="dark" type="checkbox" role="switch"><label for="dark">Dark mode</label></div>

源码实现是inline-size: 2.25em的胶囊轨道(--yeti-radius-full),用radial-gradient画圆形滑块,background-position0% 50%移到100% 50%完成滑动,勾选时背景变体色、滑块环消失(box-shadow: none)。测试断言了轨道宽度大于高度的 1.5 倍、切换前后背景色与位置均变化。

8. range:细轨道 + 圆滑块

<div class="field"><label for="volume">Volume</label><input id="volume" type="range" min="0" max="100" value="40"></div>

range 输入本身是一个控件的完整高度block-size: var(--yeti-control-size)),保证滑块是一个舒适的点击目标;轨道只有0.25em高、圆角胶囊状、颜色为--yeti-control-border;滑块为 1.25em 圆形、变体色带边框。CSS 同时书写::-webkit-slider-*::-moz-range-*两套伪元素。测试指出轨道"不会填充到当前值"——因为 CSS 读不到控件的值,这是文档明确说明的设计取舍。

无障碍(Accessibility)

文档对无障碍给出明确要求,且这些要求被 validator 与测试双重约束:

  • label 的for必须匹配控件的id,Yeti 的 validator 会拒绝缺少配对的示例;
  • hint 与 error 的 id 放进控件的aria-describedby,屏幕阅读器就能随控件朗读帮助文本,并在错误出现的瞬间朗读错误;
  • 服务端发现的错误aria-invalid="true"呈现;
  • required 标记只是装饰,辅助技术真正读到的是required属性本身;
  • switch 就是带role="switch"的 checkbox,它的 label 即开关的名称;
  • range 和其他控件一样需要 label,当数值不是人能直接说出的内容时,应提供aria-valuetext

这些行为在 manifest.json 的a11y.notes中原文记录,并在 field.spec.js 中通过 axe 无障碍扫描("has no accessibility violations")与明暗两种配色方案下的 AA 对比度测试("text meets AA in light/dark")得到验证。

属性(Attributes)

属性类型取值默认说明
data-sizeenumsm,md,lgmd缩放控件的高度与文字。
data-inlineboolean把 label 放到控件旁边。checkbox 与 radio 不需要它本身就内联。
data-variantenumprimary,secondary,success,warning,alert,neutralprimary勾选后的 checkbox / radio 的颜色。

属性背后的机制

从源码结构看,这些属性不是 field 特有的规则,而是复用 attributes.css 中一套通用的"布局属性"解析:

  • data-variant为元素挂载--_yeti-variant等一组变体色阶梯变量,取值集合定义在 vocabulary.json 的"variant"词条中;field 通过:not([data-variant])设置默认的 primary 变体,所以不会继承父元素的变体
  • data-size解析为两个内部令牌--_yeti-size-text(文字步进)与--_yeti-size-space(间距步进),例如lg对应--yeti-text-lg--yeti-space-mdsm则更紧凑。field.css 中控件的min-block-size与 padding 都读取这两个令牌,从而整体缩放;
  • data-inline是布尔属性,仅用于把 label 排到控件旁边(对 checkbox/radio 属冗余写法,因为:has()已自动处理)。

size-control词条位于 vocabulary.json 中,值为["sm", "md", "lg"]

子元素(Children)

manifest.json> selector形式声明了 field 允许的子元素结构(每个最多一个):

  • > label:0~1 个。label,for指向控件的 id;除非是带 legend 的 fieldset,否则必须存在
  • > legend:0~1 个。当 field 是分组多个控件的 fieldset 时使用。
  • > input:0~1 个。控件。
  • > select:0~1 个。控件。
  • > textarea:0~1 个。控件。
  • > .affix:0~1 个。控件槽位交给 affix 组件——即"带附属物的控件"或"两个控件拼接"(参见 affix 组件 与 affix.css)。
  • > [data-hint]:0~1 个。帮助文本,由控件的aria-describedby引用。
  • > [data-error]:0~1 个。错误消息,控件无效前保持隐藏。

hint 与 error 的样式在源码中分别为--yeti-text-sm字号,hint 用--yeti-color-text-muted弱化,error 用--yeti-color-alert-text告警色。

设计令牌(Tokens)

令牌说明
--yeti-field-gaplabel、控件与 hint 之间的间距。
--yeti-control-size控件的最小高度。
--yeti-control-radius控件的圆角。
--yeti-control-border控件静止时的边框。
--yeti-control-surface控件的背景。
--yeti-control-chevronselect 的箭头图像。
--yeti-color-alert无效控件的边框颜色。

默认值与主题化

这些令牌的默认值定义在 surface.css 与 components.css(并汇总于 tokens.json 的control分组):

  • --yeti-control-size: 2.5remmd尺寸下按钮或输入的最小块高);
  • --yeti-control-radius: var(--yeti-radius-sm)
  • --yeti-control-border: var(--yeti-color-border)
  • --yeti-control-surface: var(--yeti-color-surface)
  • --yeti-control-chevron:一个中灰色内联 SVG 箭头(主题可为深色控件表面提供更亮的箭头,如 tokens.json 中的说明);
  • --yeti-field-gap: var(--yeti-space-xs)

--yeti-color-alert定义于 color.css,是一个随明暗模式切换的light-dark()颜色(浅色下约#af3c3a)。正如文档所说,主题只要覆盖--yeti-control-radius(如 soft.css 改为radius-md)即可让全部控件变圆润——这正是"控件读取控制令牌"设计带来的杠杆效应。

内部令牌(可能在次要版本间变化)

以下令牌以下划线开头,是内部实现细节,不建议在主题中依赖

  • --_yeti-variant
  • --_yeti-on-variant
  • --_yeti-size-text
  • --_yeti-size-space

浏览器支持

  • 无守卫直接使用::has():user-invalidappearance: nonelh单位;
  • @supports守卫之后:无。

这意味着 field 依赖现代浏览器的原生能力(:user-invalid是 Chromium 111 / Firefox 88 / Safari 16.5+ 支持的伪类),但换来的是零脚本的校验体验。若目标浏览器较旧,需要自行评估降级方案。

JavaScript

无。该组件纯 CSS 实现(CSS only),自 7.0.0 版本起可用。错误显隐、开关滑块、range 滑块全部由 CSS 状态与伪元素完成,这也是 field 在 field.spec.js 中能通过纯浏览器断言验证(无 mock、无注入脚本)的原因。

小结

Yeti 的 field 组件展示了"CSS-first"表单设计的完整路径:用:has()+:user-invalid把校验状态从控件本身映射到错误文本与边框,用appearance: none重绘 checkbox、radio、switch、range,用控制令牌统一所有控件的尺寸与圆角,再以 validator 强制 label↔id 配对保证无障碍底线。按本文的示例与属性说明,你可以直接用语义化 HTML 搭建出无脚本、可访问、可主题化的完整表单。

【免费下载链接】yetiA CSS-first, native, zero-build layout and styling framework for web designers.项目地址: https://gitcode.com/gh_mirrors/fo/yeti

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 23:56:39

ImageGlass 2026路线图前瞻:下一版本的规划、方向与社区支持

ImageGlass 2026路线图前瞻&#xff1a;下一版本的规划、方向与社区支持 【免费下载链接】ImageGlass &#x1f3de; A fast, open-source, modern image viewer for 90 formats – including WEBP, GIF, SVG, AVIF, JXL, HEIC and more – built for smooth browsing across W…

作者头像 李华
网站建设 2026/9/19 23:54:31

CodeBuddy 插件实测:VSCode AI 补全与云端协同开发配置指南

1. 为什么我最终把 CodeBuddy 留在了 VSCode 里先说结论&#xff1a;我日常主力编辑器就是 VSCode&#xff0c;前后装过、卸过的 AI 编程插件没有二十也有十五个。CodeBuddy 是少数几个我用了两周之后没有卸载、反而把它固定到侧边栏的插件。原因不复杂——它把「AI 代码补全」…

作者头像 李华
网站建设 2026/9/19 23:52:28

智能学术写作系统:从选题到框架的全流程优化

1. 项目背景与痛点分析高校科研工作的起点往往是从开题报告开始的&#xff0c;但据调查显示&#xff0c;超过78%的研究生在开题阶段会遇到不同程度的困难。这些困难主要集中在选题方向不明确、文献综述难以系统化、研究方法选择困难、写作框架搭建耗时等方面。传统的人工撰写方…

作者头像 李华
网站建设 2026/9/19 23:51:38

高春牛组合源码解析:通达信副图选股与无未来函数验证

简介&#xff1a;这是一份通达信指标公式源码文档&#xff0c;聚焦“高春牛组合”副图与选股公式&#xff0c;适合股票技术分析爱好者、量化研究者及通达信用户参考借鉴。文档为doc格式&#xff0c;共1个文件&#xff0c;压缩包约425KB&#xff0c;内容围绕指标公式底层逻辑展开…

作者头像 李华
网站建设 2026/9/19 23:50:02

BrewUI:给Homebrew装上可视化仪表盘,让包管理不再全靠命令行

1. BrewUI 是什么&#xff1a;给命令行包管理器做一层“仪表盘”如果你长期在 macOS 上开发&#xff0c;大概率对brew install这类命令不陌生。Homebrew 几乎成了 macOS 上安装开发工具的默认入口&#xff1a;装 Node、Python、Redis、PostgreSQL&#xff0c;甚至一些 GUI 应用…

作者头像 李华
网站建设 2026/9/19 23:49:52

代码审查进化论:用规则引擎和AST打造硬核Code Review流程

code review 这件事&#xff0c;很多团队口号喊得震天响&#xff0c;执行起来却稀碎。要么流于形式&#xff0c;合并请求上的“1”点得比谁都快&#xff1b;要么变成代码风格吵架现场&#xff0c;把技术评审搞成了口味审判。我自己折腾 open-code-review 这套流程&#xff0c;最…

作者头像 李华