Input 输入框
YueInput 是一个原生单行 <input>:v-model、尺寸、状态、前后置内容,以及一个真正的清空按钮。它不负责 label、帮助文本和错误文案——那是 YueField 的职责。
每个预览框的右上角都有自己的外观工具条(浅色 / 深色 / Accent / 密度 / 重置),改的是 Token,页面上的每一个示例都会跟着变。
基础输入与 v-model
值永远是字符串。基础输入框不会猜 '42' 是不是数字,需要转换时由消费者或将来的类型化字段负责。
值是:
尺寸
三档尺寸复用 Button 的控制尺寸契约(ComponentSize),不是输入框自己的一套像素。不写 size 时回落应用级配置。
size | 高度 | 横向内边距 | 字号 | 前后置图标 |
|---|---|---|---|---|
sm | --input-height-sm | --input-padding-inline-sm | --input-font-size-sm | --input-icon-size-sm |
md | --input-height-md | --input-padding-inline-md | --input-font-size-md | --input-icon-size-md |
lg | --input-height-lg | --input-padding-inline-lg | --input-font-size-lg | --input-icon-size-lg |
disabled / readonly / invalid
三者是三件不同的事,视觉上也必须不同:
| 状态 | 原生行为 | 视觉语义 |
|---|---|---|
disabled | 原生 disabled:不可聚焦、不提交、读屏不当可编辑 | 禁用背景 + 禁用文字 + 不可操作光标 |
readonly | 原生 readonly:仍可聚焦、可选中复制、仍会提交 | 更安静的背景,文字保持可读 |
invalid | aria-invalid="true" | 错误边界 |
readonly 与 disabled 的区别不是风格问题
只读字段的用途是「看得见、选得中、复制得走,但改不了」——比如订单号。用 disabled 去表达它,会让值变得不可选中、不可复制,也不会随表单提交。两者的对比度门禁也不同:禁用态在 WCAG 里是豁免的,只读态不是,所以只读文字仍然被审计要求达到 4.5:1。
invalid 只表达「校验没过」,不解释原因
它设置 aria-invalid="true" 并切换错误边界,但不渲染任何错误文案。错在哪里、怎么提示,是 YueField 的事——两个组件各写一份错误文案,就有了两个需要同步的真相来源。
异常状态下的焦点优先级见指南。
placeholder 与原生属性
name、autocomplete、inputmode、required、maxlength、minlength、pattern、aria-*、id 都是原生属性透传,不是 Yue 的 Props。它们在内部真正的 <input> 上,所以 label for、表单提交、浏览器校验和自动填充照常工作。
我们会用它发送登录链接,不会公开。
下面的表格说明了每个属性最终落在哪个元素上——这是组件的公开 DOM 契约:
| 属性 | 落在 | 为什么 |
|---|---|---|
id、name、autocomplete、inputmode、required、maxlength、minlength、pattern、aria-* | 内部 <input> | 它们是表单控件语义;放在 <div> 上等于没写 |
class、style、data-* | 外层 .yue-input | 它们是「整个组件」的身份与测试钩子 |
prefix / suffix
插槽,不绑定任何图标库。装饰图标请自己写 aria-hidden="true";交互控件不要放进装饰 span 里——需要按钮时用 clearable 或自建独立控件。
插槽只增加装饰:它不会改变值、选区或键盘行为,也不会生成额外的焦点目标。
clearable
清空控件是真正的 <button type="button">:可以被 Tab 到达、有可访问名称、有独立 hover 和 focus-visible 样式。清空之后焦点留在输入框里——清空是「重新输入」的开始,不是编辑的结束。
清空控件不会出现在禁用、只读或空值时:对一个改不了的输入框提供「清空」是在说谎。
无障碍名称
清空按钮的 aria-label 来自 Yue 的 locale catalog(key input.clear),默认英文 Clear。它不是组件 Prop——一条文案一个 Prop 只服务一个组件、要在每个调用点重复,还会把「翻译」变成「改标记」。
上面三个字段的标记完全一样,区别只在于外面那层 LocaleScope 把子树切到了哪个 locale。组件不知道「语言」存在,它只读 t('input.clear');verify:visual 会在真实浏览器里断言三个 aria-label:默认读页面语言,第二个等于 Clear,第三个同样等于 Clear——但第三条是兜底链在起作用:仓库里没有 en-GB 语言包,en-GB 沿 en-GB → en → en-US 回落到默认包,而不是报错或渲染出 key。
provideLocale() 从 @yue-ui/vue/locale 导出,完整契约见国际化指南。
密码
第一版只要求 type="password" 正确透传。明文切换是消费者自己的 suffix 控件——基础输入框不会偷偷注入图标和内部状态。
浅色 / 深色 / Accent
输入框的所有颜色都来自 Token,所以切换主题时不需要 JS 参与:--input-background、--input-border-color*、--input-color 和 --input-placeholder-color 各自在浅色与深色下解析成不同值。
深色模式下 Error 边界会换成更深色阶上的浅红,保证它仍然看得见——这些取值都在 pnpm audit:tokens 的 110 项门禁里(含只读文字、焦点边界、错误边界、前后置文字和清空控件)。断言不只在 Token 层:verify:visual 会在浅色、深色和中性 Accent 三种环境下各量一遍浏览器里真实渲染的结果。
组件不知道主题
YueInput 里没有一行 JS 判断当前是浅色还是深色,也没有 theme Prop。主题是 Token 解析的结果,不是组件的状态——所以服务端渲染不会因为主题不同而产出不同的 HTML。
状态矩阵
键盘焦点不依赖颜色
:focus-visible 会在整个字段外面画一圈焦点环(--input-focus-ring-*),而不是只把边框换个颜色。异常状态叠加焦点时,错误边界保持错误色,焦点提示由环承担——所以键盘用户不会因为聚焦而失去「这里错了」的信息。
窄屏
字段宽度是 100%,并允许在 flex / grid 列里收缩(min-width: 0),所以窄屏只会受容器约束,不会把页面撑出横向滚动。
RTL
dir 属于字段本身而不是它内部的控件:外层才是排布前后置内容的元素。所以 dir 落在 .yue-input 上,内部 <input> 继承它——整块字段一起镜像,而不是只有输入的文字镜像、前后置图标还留在原顺序。
把 dir 写在祖先容器上也一样:内部的 <input> 会继承方向,外层照旧是排布者。
什么时候不用它
- 需要 label、帮助文本、错误文案和必填标记 → 等
YueField,不要把表单布局塞回输入框; - 需要多行 →
YueTextarea,它的行高和 resize 与单行不同; - 需要数字增减、日期、下拉、标签输入 → 各自独立组件,不用一个
type参数全包; - 需要数字格式化 → 先做独立的 formatter,再考虑接到输入框上。
选哪一个「不可编辑」状态
这三个状态经常被混用,但它们的原生行为完全不同:
| 想问的问题 | 用 |
|---|---|
| 用户改错了,需要重新输入 | invalid |
| 值由系统给出,用户只能看和复制 | readonly |
| 这个字段当前完全不适用 | disabled |