表单
控件是页面里的「凹槽」:底用 --zz-bg-deep,比页面更深一层,1px--zz-border-strong 描边,与按钮的 --zz-surface 面明确区分。四个尺寸档位 与按钮严格等高,因此一行里输入框、选择器、按钮的上下边缘永远对齐。
示例
一个最小可用的表单:每个字段由 Field 组合 label、控件与提示文案,错误文案落在最底, 出现错误时整块自动带上 .zz-field--invalid。
尺寸
高度 2 / 2.25 / 2.5 / 2.75rem(xs / sm / md / lg),横向内距 0.625rem(xs)与 0.75rem(其余)。 固定高度的控件不写纵向内距 —— 文字位置由 height 决定,写死纵向内距会在 xs 档裁字。 文本域的尺寸改由最小高度表达:4 / 4.5 / 5.5 / 6.5rem。
状态
悬停只把描边加深一档;聚焦把描边换成品牌色,外描边仍是全局的 2px 焦点环;无效态转--zz-danger;禁用态压到 50% 不透明度并移除指针事件。文本类控件不做位移反馈 —— 缩放会让正在输入的文字抖动。
用于接收构建失败通知。
邮箱格式不正确。
可以稍后在设置里修改。
勾选与开关
勾选框与单选框是 1rem 的直角方块,选中时填品牌色、勾与点用 --zz-brand-ink; 开关是 2.25 × 1.25rem 的直角轨道,滑块纯色,整条轨道只有一个 16px 的位移。三者内部都是真实<input>,键盘与表单提交走原生行为。
类名
| 类名 | 作用 |
|---|---|
.zz-input | 文本控件底座(input / textarea / select 共用):1px --zz-border-strong 描边、--zz-bg-deep 凹槽底、等宽字体、150ms 过渡 |
.zz-input--xs | sm | md | lg | 高度 2 / 2.25 / 2.5 / 2.75rem,与按钮同阶梯;xs 横向内距 0.625rem,其余 0.75rem;lg 字号 0.9375rem |
.zz-input--invalid / [aria-invalid='true'] | 描边转 --zz-danger;焦点态也保持危险色 |
.zz-textarea | 纵向内距 0.625rem、行高 1.5、可纵向拉伸;尺寸改控制最小高度 4 / 4.5 / 5.5 / 6.5rem |
.zz-select | 选择器外层:相对定位,承载自绘箭头,class 也落在这里 |
.zz-select__control | 原生 select:appearance: none、右侧留 2rem 箭头位、cursor: pointer |
.zz-select::after | 自绘 1px 箭头:两条 1px 直角边旋转 45°,不依赖背景图与图片资源 |
.zz-select--xs | 箭头内距收窄到 0.625rem(其余尺寸共用 0.75rem) |
.zz-select--invalid / .zz-select--disabled | 箭头转危险色 / 移除指针事件并把箭头压到 50% |
.zz-field | 纵向组合:label + 控件 + hint + 错误文案,间距 0.375rem;单选组可以直接放在 <fieldset> 上,legend 的下间距由 .zz-field > legend 单独补(gap 对 legend 无效) |
.zz-field__label | 0.75rem,--zz-text-secondary |
.zz-field__required | 必填星号,--zz-danger,带 aria-hidden(必填由控件自身的 required 表达) |
.zz-field__hint | 0.75rem,--zz-text-muted,空内容自动隐藏 |
.zz-field__error | 错误文案:0.75rem,--zz-danger |
.zz-field--invalid | 整块声明无效,内部控件统一转危险色描边 |
.zz-check / .zz-radio | 选项行:flex、0.5rem 间距、user-select: none、按下时视觉层 scale(0.96);width: fit-content 让行宽贴合内容,落在纵向 flex 里也不会被撑满整行 |
.zz-check__input / .zz-radio__input / .zz-switch__input | 真实 input:透明但保留焦点、键盘与表单行为 |
.zz-check__box | 1rem 直角方块,底 --zz-bg-deep;选中填 --zz-brand,勾用 --zz-brand-ink |
.zz-radio__box | 同上,但 50% 圆形(这套语言里唯一允许的圆角例外),选中态是品牌反色圆点 |
.zz-switch__track | 2.25 × 1.25rem 直角轨道,底 --zz-bg-deep;选中填 --zz-brand(开关行同样 width: fit-content) |
.zz-switch__thumb | 0.875rem 纯色滑块,选中位移动 1rem 并转 --zz-brand-ink |
.zz-check--disabled / .zz-radio--disabled / .zz-switch--disabled | opacity: .5 + pointer-events: none + cursor: not-allowed |
token
控件不新增变量:颜色全部来自主题 token,因此深色与亮色两套主题共用一份实现。
| token | 用在哪 |
|---|---|
--zz-bg-deep | 控件底、勾选框底、开关轨道底(凹槽) |
--zz-border-strong | 控件 1px 描边;悬停时与 --zz-text 混合一档加深 |
--zz-text | 输入的文字、选项标签 |
--zz-text-muted | placeholder、选择器箭头、hint、关闭态滑块 |
--zz-text-secondary | label |
--zz-brand | 聚焦描边、选中填充、开关开启态轨道 |
--zz-brand-ink | 选中态的勾、圆点与滑块 |
--zz-danger | 无效描边、错误文案、必填星号 |
--zz-dur / --zz-ease | 150ms 与统一缓动,只用在下列属性上 |
用法
---
import Field from '@/components/ui/Field.astro'
import Input from '@/components/ui/Input.astro'
import Select from '@/components/ui/Select.astro'
---
<Field label="项目名称" for="name" hint="会出现在侧栏里。">
<Input id="name" name="name" size="md" placeholder="zz-design-system" aria-describedby="name-hint" />
</Field>
<Field label="主题" for="theme" error="必须选一个主题。">
<Select id="theme" name="theme" placeholder="选择一个主题">
<option value="tokyo-night">Tokyo Night</option>
<option value="flexoki">Flexoki</option>
</Select>
</Field>可访问性
- label 与控件必须用
for/id关联。Field在给出for时会把 hint、错误文案的 id 推导为<id>-hint与<id>-error,控件要自己挂上对应的aria-describedby。 - 错误不要只靠颜色:同时给文案(错误文案插槽)与
aria-invalid="true",屏幕阅读器才会播报。 - 单选框组用
<fieldset>+<legend>(或role="radiogroup"+aria-labelledby),组内各项name相同。 - 勾选框、单选框、开关内部都是真实
<input>:空格与方向键、表单提交、required校验全部是原生行为;视觉层只负责画方块与勾,因此标记aria-hidden。 - 全局的 2px 品牌色焦点环保留。勾选类控件的真实 input 是透明的,焦点环会被转发到视觉层 —— 这是让它可见,不是抹掉它。
- 开关用
role="switch",读屏会播报「开关 / 已打开」而不是「复选框」。 - 禁用态只是视觉与指针层面(
opacity: .5+pointer-events: none), 真正的禁用与校验仍由原生disabled/required属性承担。 prefers-reduced-motion: reduce下所有过渡关闭,开关的位移瞬时完成、不做补间。- placeholder 只作示例,不能替代 label。